Merge "[Contact] Ported AddressBook.find() code from wrt-plugins-tizen." into tizen_2.4
[platform/core/api/webapi-plugins.git] / src / messaging / messaging_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 validator_ = xwalk.utils.validator;
18 var types_ = validator_.Types;
19 var T_ = xwalk.utils.type;
20 var bridge = xwalk.utils.NativeBridge(extension, true);
21
22 function throwException_(err) {
23     throw new WebAPIException(err.code, err.name, err.message);
24 }
25
26 var Property = {
27     W: 1 << 0,   // WRITABLE
28     E: 1 << 1,   // ENUMERABLE
29     C: 1 << 2    // CONFIGURABLE
30 }
31
32 function addTypeToFilter_(data)
33 {
34     var filter = {};
35
36     for(var field in data) {
37         filter[field] = data[field];
38     }
39
40     if (data instanceof tizen.AttributeFilter) {
41         filter.filterType = "AttributeFilter";
42         //convert to string
43         filter.matchValue = String(filter.matchValue);
44     } else if (data instanceof tizen.AttributeRangeFilter) {
45         filter.filterType = "AttributeRangeFilter";
46     } else if (data instanceof tizen.CompositeFilter) {
47         filter.filterType = "CompositeFilter";
48         // recursively convert all sub-filters
49         filter.filters = [];
50         for (var i = 0; i < data.filters.length; ++i) {
51             filter.filters[i] = addTypeToFilter_(data.filters[i]);
52         }
53     } else {
54         filter.filterType = "Unknown";
55     }
56
57     return filter;
58 }
59
60 /**
61  * Example usage:
62  * function Messaging () {
63  *     propertyFactory_(this, 'ids', [2,3,4], Property.W | Property.E | Property.C);
64  *     propertyFactory_(this, 'name', 'Name', Property.E);
65  *     propertyFactory_(this, 'age', 25, Property.W);
66  *     propertyFactory_(this, 'something', 1);
67  *     propertyFactory_(this, 'getSomething', Property.E, {get: function(){return 100;}});
68  * }
69  * Will produce:
70  * var m = new Messaging();
71  * {
72  *     id: [2,3,4],
73  *     name: 'Name',
74  *     age: 25
75  * }
76  *
77  * m.name = 'A brand new name';
78  * console.log(m.name); // Name
79  */
80 function propertyFactory_(that, name, value, flags, options) {
81     flags = flags || 0;
82     if (options === null || typeof options !== 'object') {
83         options = {};
84     }
85     if (!(options.get) && !(options.set)) {
86         options.value = value;
87     }
88     if ((flags & Property.W) != 0) { options.writable     = true; }
89     if ((flags & Property.E) != 0) { options.enumerable   = true; }
90     if ((flags & Property.C) != 0) { options.configurable = true; }
91     Object.defineProperty(
92         that,
93         name,
94         options
95     );
96 }
97
98 function InternalValues_(data) {
99     if (!(this instanceof InternalValues_)) {
100         return new InternalValues_(data);
101     }
102     for(var key in data) {
103         if (data.hasOwnProperty(key)) {
104             this[key] = data[key];
105         }
106     }
107 }
108
109 function updateInternal_(internal, data) {
110     var values = new InternalValues_(data);
111     for(var key in data) {
112         if (values.hasOwnProperty(key) && internal.hasOwnProperty(key)) {
113             internal[key] = values;
114         }
115     }
116 }
117
118 /**
119  * Specifies the Messaging service tags.
120  */
121 var MessageServiceTag = ['messaging.sms', 'messaging.mms', 'messaging.email'];
122
123 function Message(type, data) {
124     if (!(this instanceof Message)) {
125         throw new TypeError("Constructor called like a function");
126     }
127     if (MessageServiceTag.indexOf(type) === -1) {
128         throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
129     }
130     if ( !data || typeof data !== 'object') { // 'data' is optional
131         data = {};
132     }
133
134     // set initial data from internal MessageInit_ object or to default values
135     var internal       = data instanceof MessageInit_,
136         id             = internal ? data.id             : null,
137         conversationId = internal ? data.conversationId : null,
138         folderId       = internal ? data.folderId       : null,
139         timestamp      = internal ? data.timestamp      : null,
140         from           = internal ? data.from           : null,
141         hasAttachment  = internal ? data.hasAttachment  : false,
142         isRead         = internal ? data.isRead         : false,
143         inResponseTo   = internal ? data.inResponseTo   : null;
144     // create MessageBody object
145     var body = new MessageBody({messageId: id, plainBody: data.plainBody, htmlBody: data.htmlBody});
146     // check 'to', 'cc' and 'bcc' fields
147     var to = data.to;
148     if (!(to instanceof Array)) {
149         to = [];
150     }
151     var cc = data.cc;
152     if (!(cc instanceof Array)) {
153         cc = [];
154     }
155     var bcc = data.bcc;
156     if (!(bcc instanceof Array)) {
157         bcc = [];
158     }
159     // 'attachments' private variable, getter and setter
160     var attachments = (internal ? data.attachments : []) || [];
161
162     var _internal = {
163         id: id || null,
164         conversationId: conversationId || null,
165         folderId: folderId || null,
166         type: type,
167         timestamp: timestamp || null,
168         from: from,
169         to: to || [],
170         cc: cc || [],
171         bcc: bcc || [],
172         body: body,
173         isRead: isRead || false,
174         hasAttachment: hasAttachment || false,
175         isHighPriority: data.isHighPriority || false,
176         subject: data.subject || '',
177         inResponseTo: inResponseTo || null,
178         attachments: attachments
179     };
180     // id
181     Object.defineProperty(
182         this,
183         'id',
184         {
185             get: function () {return _internal.id;},
186             set: function (value) { if (value instanceof InternalValues_) _internal.id = value.id;},
187             enumerable: true
188         }
189     );
190
191     //conversationId
192     Object.defineProperty(
193         this,
194         'conversationId',
195         {
196             get: function () {return _internal.conversationId;},
197             set: function (value) {
198                 if (value instanceof InternalValues_)
199                     _internal.conversationId = value.conversationId;
200             },
201             enumerable: true
202         }
203     );
204
205     // folderId
206     Object.defineProperty(
207         this,
208         'folderId',
209         {
210             get: function () {return _internal.folderId;},
211             set: function (value) {
212                 if (value instanceof InternalValues_) _internal.folderId = value.folderId;
213             },
214             enumerable: true
215         }
216     );
217
218     // type
219     Object.defineProperty(
220         this,
221         'type',
222         {
223             get: function () {return _internal.type;},
224             set: function (value) {return;},
225             enumerable: true
226         }
227     );
228
229     // timestamp
230     Object.defineProperty(
231         this,
232         'timestamp',
233         {
234             get: function () {
235                 return _internal.timestamp ? new Date(_internal.timestamp * 1000) : _internal.timestamp;
236             },
237             set: function (value) {
238                 if (value instanceof InternalValues_) {
239                     _internal.timestamp = value.timestamp;
240                 }
241             },
242             enumerable: true
243         }
244     );
245
246     // from
247     Object.defineProperty(
248         this,
249         'from',
250         {
251             get: function () {return _internal.from;},
252             set: function (value) {
253                 if (value instanceof InternalValues_) _internal.from = value.from;
254             },
255             enumerable: true
256         }
257     );
258
259     // to
260     Object.defineProperty(
261         this,
262         'to',
263         {
264             get: function () {return _internal.to;},
265             set: function (value) {
266                 if (value instanceof InternalValues_) value = value.to;
267                 if (value instanceof Array) _internal.to = value;
268             },
269             enumerable: true
270         }
271     );
272
273     // cc
274     Object.defineProperty(
275         this,
276         'cc',
277         {
278             get: function () {return _internal.cc;},
279             set: function (value) {
280                 if (value instanceof InternalValues_) value = value.cc;
281                 if (value instanceof Array) _internal.cc = value;
282             },
283             enumerable: true
284         }
285     );
286
287     // bcc
288     Object.defineProperty(
289         this,
290         'bcc',
291         {
292             get: function () {return _internal.bcc;},
293             set: function (value) {
294                 if (value instanceof InternalValues_) value = value.bcc;
295                 if (value instanceof Array) _internal.bcc = value;
296             },
297             enumerable: true
298         }
299     );
300
301     // body
302     Object.defineProperty(
303         this,
304         'body',
305         {
306             get: function () {return _internal.body;},
307             set: function (value) {
308                 if (value instanceof InternalValues_) _internal.body = new MessageBody(value.body);
309                 if (value instanceof MessageBody) _internal.body = value;
310             },
311             enumerable: true
312         }
313     );
314
315     // isRead
316     Object.defineProperty(
317         this,
318         'isRead',
319         {
320             get: function () {return _internal.isRead;},
321             set: function (value) {
322                 if (value instanceof InternalValues_) {value = value.isRead;}
323                 _internal.isRead = !!value;
324             },
325             enumerable: true
326         }
327     );
328
329     // hasAttachment
330     Object.defineProperty(
331         this,
332         'hasAttachment',
333         {
334             get: function () {return _internal.attachments.length > 0;},
335             set: function (value) {
336                 if (value instanceof InternalValues_)
337                     _internal.hasAttachment = value.hasAttachment;
338             },
339             enumerable: true
340         }
341     );
342
343     // isHighPriority
344     Object.defineProperty(
345         this,
346         'isHighPriority',
347         {
348             get: function () {return _internal.isHighPriority;},
349             set: function (value) {
350                 if (value instanceof InternalValues_) value = value.isHighPriority;
351                 _internal.isHighPriority = !!value;
352             },
353             enumerable: true
354         }
355     );
356
357     // subject
358     Object.defineProperty(
359         this,
360         'subject',
361         {
362             get: function () {return _internal.subject;},
363             set: function (value) {
364                 if (value instanceof InternalValues_) value = value.subject;
365                 _internal.subject = String(value);
366             },
367             enumerable: true
368         }
369     );
370
371     // inResponseTo
372     Object.defineProperty(
373         this,
374         'inResponseTo',
375         {
376             get: function () {return _internal.inResponseTo;},
377             set: function (value) {
378                 if (value instanceof InternalValues_) _internal.inResponseTo = value.inResponseTo;
379             },
380             enumerable: true
381         }
382     );
383
384     // messageStatus
385     Object.defineProperty(
386         this,
387         'messageStatus',
388         {
389             get: function () {
390                 if (_internal.id) {
391                     return bridge.sync({
392                         cmd: 'Message_messageStatus',
393                         args: {
394                             id: _internal.id,
395                             type: _internal.type
396                         }
397                     });
398                 } else {
399                     return '';
400                 }
401             },
402             set: function (value) {return;},
403             enumerable: true
404         }
405     );
406
407     // attachments
408     Object.defineProperty(
409         this,
410         'attachments',
411         {
412             get: function () {return _internal.attachments;},
413             set: function(value) {
414                 if (value instanceof InternalValues_) {
415                     value = value.attachments;
416                     for (var k = 0; k < value.length; ++k) {
417                         if (!(value[k] instanceof tizen.MessageAttachment)) {
418                             if (_internal.attachments[k]) {
419                                 updateInternal_(_internal.attachments[k], value[k]);
420                             } else {
421                                 _internal.attachments[k] = new MessageAttachment(
422                                         new InternalValues_(value[k]));
423                             }
424                         } else {
425                             _internal.attachments[k] = value[k];
426                         }
427                     }
428                     // if new array is shorter than the old one, remove excess elements
429                     if (value.length < _internal.length) {
430                         _internal.splice(value.length, _internal.length - value.length);
431                     }
432                 } else if (T_.isArray(value)) {
433                     for (var k = 0; k < value.length; ++k) {
434                         if (!(value[k] instanceof tizen.MessageAttachment)) {
435                             return;
436                         }
437                     }
438                     _internal.attachments = value;
439                 }
440             },
441             enumerable: true
442         }
443     );
444 };
445
446 function MessageInit(data) {
447     if (!(this instanceof MessageInit)) {
448         return new MessageInit(data);
449     }
450     if (data === null || typeof data !== 'object') {
451         data = {};
452     }
453     propertyFactory_(this, 'subject'       , data.subject        || ''   , Property.E | Property.W);
454     propertyFactory_(this, 'to'            , data.to             || []   , Property.E | Property.W);
455     propertyFactory_(this, 'cc'            , data.cc             || []   , Property.E | Property.W);
456     propertyFactory_(this, 'bcc'           , data.bcc            || []   , Property.E | Property.W);
457     propertyFactory_(this, 'plainBody'     , data.plainBody      || ''   , Property.E | Property.W);
458     propertyFactory_(this, 'htmlBody'      , data.htmlBody       || ''   , Property.E | Property.W);
459     propertyFactory_(this, 'isHighPriority', data.isHighPriority || false, Property.E | Property.W);
460 };
461
462 function MessageInit_(data) {
463     if (!(this instanceof MessageInit_)) {
464         return new MessageInit_(data);
465     }
466     if ( !data || typeof data !== 'object') {
467         data = {};
468     }
469     this.id             = data.id             || null;
470     this.conversationId = data.conversationId || null;
471     this.folderId       = data.folderId       || null;
472     this.timestamp      = data.timestamp      || null;
473     this.from           = data.from           || '';
474     this.to             = data.to             || [];
475     this.cc             = data.cc             || [];
476     this.bcc            = data.bcc            || [];
477     this.isRead         = data.isRead         || false;
478     this.hasAttachment  = data.hasAttachment  || null;
479     this.isHighPriority = data.isHighPriority || false;
480     this.subject        = data.subject        || '';
481     this.inResponseTo   = data.inResponseTo   || null;
482     this.attachments = [];
483     this.plainBody      = data.body ? data.body.plainBody : '';
484     this.htmlBody       = data.body ? data.body.htmlBody : '';
485
486     var self = this;
487     if (data.attachments && data.attachments.constructor === Array) {
488         data.attachments.forEach(function(el) {
489            if (!el) return;
490
491            if (el.constructor === MessageAttachment) {
492                self.attachments.push(el);
493            } else {
494                self.attachments.push(new MessageAttachment(new InternalValues_(el)));
495            }
496         });
497     }
498 };
499
500 function MessageBody(data) {
501     if (!this instanceof MessageBody) {
502         return new MessageBody(data);
503     }
504     if (data === null || typeof data !== 'object') {
505         data = {};
506     }
507
508     var _internal = {
509         messageId: data.messageId || null,
510         loaded: data.loaded || false,
511         plainBody: data.plainBody || '',
512         htmlBody: data.htmlBody || '',
513         inlineAttachments: data.inlineAttachments || []
514     };
515
516     // messageId
517     Object.defineProperty(
518         this,
519         'messageId',
520         {
521             get: function () {return _internal.messageId;},
522             set: function (value) {
523                 if (value instanceof InternalValues_) _internal.messageId = value.messageId;
524             },
525             enumerable: true
526         }
527     );
528
529     // loaded
530     Object.defineProperty(
531         this,
532         'loaded',
533         {
534             get: function () {return _internal.loaded;},
535             set: function (value) {
536                 if (value instanceof InternalValues_) _internal.loaded = value.loaded;
537             },
538             enumerable: true
539         }
540     );
541
542     // plainBody
543     Object.defineProperty(
544         this,
545         'plainBody',
546         {
547             get: function () {return _internal.plainBody;},
548             set: function (value) {
549                 if (value instanceof InternalValues_) {
550                     _internal.plainBody = String(value.plainBody);
551                 } else {
552                     _internal.plainBody = String(value);
553                 }
554             },
555             enumerable: true
556         }
557     );
558
559     // htmlBody
560     Object.defineProperty(
561         this,
562         'htmlBody',
563         {
564             get: function () {return _internal.htmlBody;},
565             set: function (value) {
566                 if (value instanceof InternalValues_) {
567                     _internal.htmlBody = String(value.htmlBody);
568                 } else {
569                     _internal.htmlBody = String(value);
570                 }
571             },
572             enumerable: true
573         }
574     );
575
576     // inlineAttachments
577     Object.defineProperty(
578         this,
579         'inlineAttachments',
580         {
581             get: function () {return _internal.inlineAttachments;},
582             set: function (value) {
583                 if (value instanceof InternalValues_) {
584                     _internal.inlineAttachments = value.inlineAttachments;
585                 } else if (T_.isArray(value)) {
586                     _internal.inlineAttachments = value;
587                 }
588             },
589             enumerable: true
590         }
591     );
592 };
593
594 var messageAttachmentsLoaded = {};
595
596 function MessageAttachment(first, second) {
597     validator_.isConstructorCall(this, MessageAttachment);
598     if (!this instanceof MessageAttachment) {
599         return new MessageAttachment(data);
600     }
601
602     var internalConstructor = first instanceof InternalValues_;
603     var _internal = {
604         messageId: internalConstructor ? first.messageId : null,
605         id: internalConstructor ? first.id : null,
606         mimeType: internalConstructor ? first.mimeType : (undefined == second ? null : second),
607         filePath: internalConstructor ? first.filePath : first,
608     };
609
610     // messageId
611     Object.defineProperty(
612         this,
613         'messageId',
614         {
615             get: function () {return _internal.messageId;},
616             set: function (value) {
617                 if (value instanceof InternalValues_) _internal.messageId = value.messageId;
618             },
619             enumerable: true
620         }
621     );
622     // id
623     Object.defineProperty(
624         this,
625         'id',
626         {
627             get: function () {return _internal.id;},
628             set: function (value) {
629                 if (value instanceof InternalValues_) _internal.id = value.id;
630             },
631             enumerable: true
632         }
633     );
634     // mimeType
635     Object.defineProperty(
636         this,
637         'mimeType',
638         {
639             get: function () {return _internal.mimeType;},
640             set: function (value) {
641                 if (value instanceof InternalValues_) _internal.mimeType = value.mimeType;
642             },
643             enumerable: true
644         }
645     );
646     // filePath
647     Object.defineProperty(
648         this,
649         'filePath',
650         {
651             get: function () {
652                 if (_internal.id && !messageAttachmentsLoaded[_internal.id]) {
653                     return null;
654                 }
655
656                 return _internal.filePath;
657             },
658             set: function (value) {
659                 if (value instanceof InternalValues_) _internal.filePath = value.filePath;
660             },
661             enumerable: true
662         }
663     );
664 };
665
666 function Messaging() {};
667
668 /**
669  * Gets the messaging service of a given type for a given account.
670  * @param {!MessageServiceTag} messageServiceType Type of the services to be retrieved.
671  * @param {!MessageServiceArraySuccessCallback} successCallback Callback function that is called
672  *     when the services are successfully retrieved.
673  * @param {ErrorCallback} errorCallback Callback function that is called when an error occurs.
674  */
675 Messaging.prototype.getMessageServices = function () {
676     var args = validator_.validateArgs(arguments, [
677         {name: 'messageServiceType', type: types_.ENUM, values: MessageServiceTag},
678         {name: 'successCallback', type: types_.FUNCTION},
679         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
680     ]);
681
682     bridge.async({
683         cmd: 'Messaging_getMessageServices',
684         args: {
685             messageServiceType: args.messageServiceType
686         }
687     }).then({
688         success: function (data) {
689             var servicesArr = [];
690             data.forEach(function(e){
691                 servicesArr.push(new MessageService(e));
692             });
693             args.successCallback.call(null, servicesArr);
694         },
695         error: function (e) {
696             if (args.errorCallback) {
697                 args.errorCallback.call(
698                     null,
699                     new WebAPIException(e.error)
700                 )
701             }
702         }
703     });
704 }
705 function MessageStorage(){};
706 function MessageService(data) {
707     propertyFactory_(this, 'id', data.id, Property.E);
708     propertyFactory_(this, 'type', data.type, Property.E);
709     propertyFactory_(this, 'name', data.name, Property.E);
710     propertyFactory_(this, 'messageStorage', new MessageStorage(this), Property.E);
711 };
712
713 MessageService.prototype.sendMessage = function () {
714     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
715
716     var args = validator_.validateArgs(arguments, [
717         {name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message},
718         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
719         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
720         {name: 'simIndex', type: types_.LONG, optional: true, nullable: true}
721     ]);
722
723     if (args.message.type != this.type) {
724         throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
725     }
726
727     var self = this;
728     bridge.async({
729         cmd: 'MessageService_sendMessage',
730         args: {
731             message: args.message,
732             simIndex: args.simIndex || 1,
733             serviceId: self.id
734         }
735     }).then({
736         success: function (data) {
737             var message = data.message;
738             if (message) {
739                 var body = message.body;
740                 if (body) {
741                     updateInternal_(args.message.body, body)
742                     delete message.body;
743                 }
744                 updateInternal_(args.message, message);
745             }
746
747             if (args.successCallback) {
748                 args.successCallback.call(null, data.recipients);
749             }
750         },
751         error: function (e) {
752             if (args.errorCallback) {
753                 args.errorCallback.call(
754                     null,
755                     new WebAPIException(e.error)
756                 )
757             }
758         }
759     });
760 };
761
762 MessageService.prototype.loadMessageBody = function () {
763     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
764
765     var args = validator_.validateArgs(arguments, [
766         {name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message},
767         {name: 'successCallback', type: types_.FUNCTION},
768         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
769     ]);
770
771     if (args.message.type != this.type) {
772         throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
773     }
774
775     var self = this;
776
777     bridge.async({
778         cmd: 'MessageService_loadMessageBody',
779         args: {
780             message: args.message,
781             serviceId: self.id
782         }
783     }).then({
784         success: function (data) {
785             var body = data.messageBody;
786             if (body) {
787                 updateInternal_(args.message.body, body)
788             }
789
790             args.successCallback.call(
791                 null,
792                 args.message
793             );
794         },
795         error: function (e) {
796             if (args.errorCallback) {
797                 args.errorCallback.call(
798                     null,
799                     new WebAPIException(e.error)
800                 )
801             }
802         }
803     });
804 };
805 MessageService.prototype.loadMessageAttachment = function () {
806     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
807
808     var args = validator_.validateArgs(arguments, [
809         {name: 'attachment', type: types_.PLATFORM_OBJECT, values: MessageAttachment},
810         {name: 'successCallback', type: types_.FUNCTION},
811         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
812     ]);
813
814     var self = this;
815     var firstCall = false;
816     if (!messageAttachmentsLoaded[args.attachment.id]) {
817         firstCall = true;
818         messageAttachmentsLoaded[args.attachment.id] = true;
819     }
820
821     bridge.async({
822         cmd: 'MessageService_loadMessageAttachment',
823         args: {
824             attachment: args.attachment,
825             serviceId: self.id
826         }
827     }).then({
828         success: function (data) {
829             if (args.successCallback) {
830                 var messageAttachment = data.messageAttachment;
831                 if (messageAttachment) {
832                     updateInternal_(args.attachment, messageAttachment);
833                 }
834
835                 args.successCallback.call(
836                     null,
837                     args.attachment
838                 );
839             }
840         },
841         error: function (e) {
842             if (firstCall) {
843                 messageAttachmentsLoaded[args.attachment.id] = false;
844             }
845             if (args.errorCallback) {
846                 args.errorCallback.call(
847                     null,
848                     new WebAPIException(e.error)
849                 )
850             }
851         }
852     });
853 };
854
855 MessageService.prototype.sync = function () {
856     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
857
858     var args = validator_.validateArgs(arguments, [
859         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
860         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
861         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
862     ]);
863
864     var self = this;
865     var cid = bridge.listener({
866         success: function () {
867             if (args.successCallback) {
868                 args.successCallback.call(null);
869             }
870         },
871         error: function (e) {
872             if (args.errorCallback) {
873                 args.errorCallback.call(
874                     null,
875                     new WebAPIException(e.error)
876                 )
877             }
878         }
879     });
880
881     var result = bridge.sync({
882         cmd: 'MessageService_sync',
883         cid: cid,
884         args: {
885             id: self.id,
886             limit: args.limit || null
887         }
888     });
889
890     return result;
891 };
892
893 MessageService.prototype.syncFolder = function () {
894     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
895
896     var args = validator_.validateArgs(arguments, [
897         {name: 'folder', type: types_.PLATFORM_OBJECT, values: MessageFolder},
898         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
899         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
900         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
901     ]);
902
903     var self = this;
904     var cid = bridge.listener({
905         success: function () {
906             if (args.successCallback) {
907                 args.successCallback.call(null);
908             }
909         },
910         error: function (e) {
911             if (args.errorCallback) {
912                 args.errorCallback.call(
913                     null,
914                     new WebAPIException(e.error)
915                 )
916             }
917         }
918     });
919
920     var result = bridge.sync({
921         cmd: 'MessageService_syncFolder',
922         cid: cid,
923         args: {
924             id: self.id,
925             folder: args.folder,
926             limit: args.limit || null
927         }
928     });
929
930     return result;
931 };
932
933 MessageService.prototype.stopSync = function () {
934     var args = validator_.validateArgs(arguments, [
935         {name: 'opId', type: types_.LONG}
936     ]);
937
938     var self = this;
939     bridge.sync({
940         cmd: 'MessageService_stopSync',
941         args: {
942             id: self.id,
943             opId: args.opId
944         }
945     });
946 };
947
948 function MessageStorage(service) {
949     propertyFactory_(this, 'service', service);
950 };
951
952 MessageStorage.prototype.addDraftMessage = function () {
953     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
954
955     var args = validator_.validateArgs(arguments, [
956         {name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message},
957         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
958         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
959     ]);
960
961     if (args.message.type != this.service.type) {
962         throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
963     }
964
965     var self = this;
966     bridge.async({
967         cmd: 'MessageStorage_addDraftMessage',
968         args: {
969             message: args.message,
970             serviceId: self.service.id
971         }
972     }).then({
973         success: function (data) {
974             var message = data.message;
975             if (message) {
976                 var body = message.body;
977                 if (body) {
978                     updateInternal_(args.message.body, body)
979                     delete message.body;
980                 }
981                 var attachments = message.attachments;
982                 if (attachments) {
983                     for (var i = 0; i < attachments.length; i++) {
984                         messageAttachmentsLoaded[attachments[i].id] = true;
985                     }
986                 }
987                 updateInternal_(args.message, message);
988             }
989
990             if (args.successCallback) {
991                 args.successCallback.call(null);
992             }
993         },
994         error: function (e) {
995             if (args.errorCallback) {
996                 args.errorCallback.call(
997                     null,
998                     new WebAPIException(e.error)
999                 )
1000             }
1001         }
1002     });
1003 };
1004
1005 MessageStorage.prototype.findMessages = function () {
1006     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1007
1008     var args = validator_.validateArgs(arguments, [
1009         {
1010             name: 'filter',
1011             type: types_.PLATFORM_OBJECT,
1012             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter]
1013         },
1014         {name: 'successCallback', type: types_.FUNCTION},
1015         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
1016         {name: 'sort', type: types_.PLATFORM_OBJECT, values: tizen.SortMode, optional: true,
1017                 nullable: true},
1018         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true},
1019         {name: 'offset', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
1020     ]);
1021
1022     var self = this;
1023
1024     bridge.async({
1025         cmd: 'MessageStorage_findMessages',
1026         args: {
1027             filter: addTypeToFilter_(args.filter) || null,
1028             sort: args.sort || null,
1029             limit: args.limit || null,
1030             offset: args.offset || null,
1031             serviceId: self.service.id,
1032             type: self.service.type
1033         }
1034     }).then({
1035         success: function (data) {
1036             var messages = [];
1037             data.forEach(function (el) {
1038                 messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1039             });
1040             args.successCallback.call(null, messages);
1041         },
1042         error: function (e) {
1043             if (args.errorCallback) {
1044                 args.errorCallback.call(
1045                     null,
1046                     new WebAPIException(e.error)
1047                 )
1048             }
1049         }
1050     });
1051 };
1052
1053 MessageStorage.prototype.removeMessages = function () {
1054     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
1055
1056     var args = validator_.validateArgs(arguments, [
1057         {name: 'messages', type: types_.ARRAY, values: Message},
1058         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
1059         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1060     ]);
1061
1062     var self = this;
1063
1064     args.messages.forEach(function(msg) {
1065         if (msg.type != self.service.type) {
1066             throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
1067         }
1068     });
1069
1070     bridge.async({
1071         cmd: 'MessageStorage_removeMessages',
1072         args: {
1073             messages: args.messages,
1074             serviceId: self.service.id,
1075             type: self.service.type
1076         }
1077     }).then({
1078         success: function () {
1079             if (args.successCallback) {
1080                 args.successCallback.call(null);
1081             }
1082         },
1083         error: function (e) {
1084             if (args.errorCallback) {
1085                 args.errorCallback.call(
1086                     null,
1087                     new WebAPIException(e.error)
1088                 )
1089             }
1090         }
1091     });
1092 };
1093
1094 MessageStorage.prototype.updateMessages = function () {
1095     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
1096
1097     var args = validator_.validateArgs(arguments, [
1098         {name: 'messages', type: types_.ARRAY, values: Message},
1099         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
1100         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1101     ]);
1102
1103     var self = this;
1104
1105     args.messages.forEach(function(msg) {
1106         if (msg.type != self.service.type) {
1107             throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
1108         }
1109     });
1110
1111     bridge.async({
1112         cmd: 'MessageStorage_updateMessages',
1113         args: {
1114             messages: args.messages,
1115             serviceId: self.service.id
1116         }
1117     }).then({
1118         success: function (data) {
1119             var originals = {},
1120                     i = args.messages.length,
1121                     m;
1122             while (i--) {
1123                 m = args.messages[i];
1124                 if (m.id) {
1125                     originals[m.id] = m;
1126                 }
1127             }
1128
1129             i = data.length;
1130             while (i--) {
1131                 m = data[i];
1132                 if (originals[m.oldId]) {
1133                     var body = m.body;
1134                     if (body) {
1135                         updateInternal_(originals[m.oldId].body, body)
1136                         delete m.body;
1137                     }
1138                     updateInternal_(originals[m.oldId], m);
1139                 }
1140             }
1141
1142             if (args.successCallback) {
1143                 args.successCallback.call(null);
1144             }
1145         },
1146         error: function (e) {
1147             if (args.errorCallback) {
1148                 args.errorCallback.call(
1149                     null,
1150                     new WebAPIException(e.error)
1151                 )
1152             }
1153         }
1154     });
1155 };
1156
1157 MessageStorage.prototype.findConversations = function () {
1158     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1159
1160     var args = validator_.validateArgs(arguments, [
1161         {
1162             name: 'filter',
1163             type: types_.PLATFORM_OBJECT,
1164             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter]
1165         },
1166         {name: 'successCallback', type: types_.FUNCTION},
1167         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
1168         {name: 'sort', type: types_.PLATFORM_OBJECT, values: tizen.SortMode, optional: true,
1169                 nullable: true},
1170         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true},
1171         {name: 'offset', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
1172     ]);
1173
1174     var self = this;
1175
1176     bridge.async({
1177         cmd: 'MessageStorage_findConversations',
1178         args: {
1179             filter: addTypeToFilter_(args.filter),
1180             sort: args.sort || null,
1181             limit: args.limit || null,
1182             offset: args.offset || null,
1183             serviceId: self.service.id
1184         }
1185     }).then({
1186         success: function (data) {
1187             var conversations = [];
1188             data.forEach(function (el) {
1189                 conversations.push(new MessageConversation(el));
1190             });
1191             args.successCallback.call(null, conversations);
1192         },
1193         error: function (e) {
1194             if (args.errorCallback) {
1195                 args.errorCallback.call(
1196                     null,
1197                     new WebAPIException(e.error)
1198                 )
1199             }
1200         }
1201     });
1202 };
1203
1204 MessageStorage.prototype.removeConversations = function () {
1205     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
1206
1207     var args = validator_.validateArgs(arguments, [
1208         {name: 'conversations', type: types_.ARRAY},
1209         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
1210         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1211     ]);
1212
1213     args.conversations.forEach(function (el) {
1214         if (!el || el.constructor !== MessageConversation) {
1215             throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
1216         }
1217     });
1218
1219     var self = this;
1220
1221     bridge.async({
1222         cmd: 'MessageStorage_removeConversations',
1223         args: {
1224             conversations: args.conversations,
1225             serviceId: self.service.id,
1226             type: self.service.type
1227         }
1228     }).then({
1229         success: function () {
1230             if (args.successCallback) {
1231                 args.successCallback.call(null);
1232             }
1233         },
1234         error: function (e) {
1235             if (args.errorCallback) {
1236                 args.errorCallback.call(
1237                     null,
1238                     new WebAPIException(e.error)
1239                 )
1240             }
1241         }
1242     });
1243 };
1244
1245 MessageStorage.prototype.findFolders = function () {
1246     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1247
1248     var args = validator_.validateArgs(arguments, [
1249         {
1250             name: 'filter',
1251             type: types_.PLATFORM_OBJECT,
1252             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter]
1253         },
1254         {name: 'successCallback', type: types_.FUNCTION},
1255         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1256     ]);
1257
1258     var self = this;
1259
1260     bridge.async({
1261         cmd: 'MessageStorage_findFolders',
1262         args: {
1263             filter: addTypeToFilter_(args.filter),
1264             sort: args.sort || null,
1265             limit: args.limit || null,
1266             offset: args.offset || null,
1267             serviceId: self.service.id
1268         }
1269     }).then({
1270         success: function (data) {
1271             var folders = [];
1272             data.forEach(function (el) {
1273                 folders.push(new MessageFolder(el));
1274             });
1275             args.successCallback.call(null, folders);
1276         },
1277         error: function (e) {
1278             if (args.errorCallback) {
1279                 args.errorCallback.call(
1280                     null,
1281                     new WebAPIException(e.error)
1282                 )
1283             }
1284         }
1285     });
1286 };
1287
1288 MessageStorage.prototype.addMessagesChangeListener = function () {
1289     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1290
1291     var args = validator_.validateArgs(arguments, [
1292         {name: 'messagesChangeCallback', type: types_.LISTENER,
1293                 values: ['messagesadded', 'messagesupdated', 'messagesremoved']},
1294         {
1295             name: 'filter',
1296             type: types_.PLATFORM_OBJECT,
1297             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter],
1298             optional: true,
1299             nullable: true
1300         }
1301     ]);
1302
1303     var self = this;
1304
1305     var cid = bridge.listener({
1306         messagesadded: function (data) {
1307             if (args.messagesChangeCallback.messagesadded) {
1308                 var messages = [];
1309                 data.forEach(function (el) {
1310                     messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1311                 });
1312                 args.messagesChangeCallback.messagesadded.call(null, messages);
1313             }
1314         },
1315         messagesupdated: function (data) {
1316             if (args.messagesChangeCallback.messagesupdated) {
1317                 var messages = [];
1318                 data.forEach(function (el) {
1319                     messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1320                 });
1321                 args.messagesChangeCallback.messagesupdated.call(null, messages);
1322             }
1323         },
1324         messagesremoved: function (data) {
1325             if (args.messagesChangeCallback.messagesremoved) {
1326                 var messages = [];
1327                 data.forEach(function (el) {
1328                     messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1329                 });
1330                 args.messagesChangeCallback.messagesremoved.call(null, messages);
1331             }
1332         }
1333     });
1334
1335     var result = bridge.sync({
1336         cmd: 'MessageStorage_addMessagesChangeListener',
1337         cid: cid,
1338         args: {
1339             filter: args.filter ? addTypeToFilter_(args.filter) : null,
1340             serviceId: self.service.id
1341         }
1342     });
1343
1344     bridge.attach(cid, 'watchId', result);
1345     return result;
1346 };
1347
1348 MessageStorage.prototype.addConversationsChangeListener = function () {
1349     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1350
1351     var args = validator_.validateArgs(arguments, [
1352         {name: 'conversationsChangeCallback', type: types_.LISTENER,
1353                 values: ['conversationsadded', 'conversationsupdated', 'conversationsremoved']},
1354         {
1355             name: 'filter',
1356             type: types_.PLATFORM_OBJECT,
1357             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter],
1358             optional: true,
1359             nullable: true
1360         }
1361     ]);
1362
1363     var self = this;
1364
1365     var cid = bridge.listener({
1366         conversationsadded: function (data) {
1367             if (args.conversationsChangeCallback.conversationsadded) {
1368                 var conversations = [];
1369                 data.forEach(function (el) {
1370                     conversations.push(new MessageConversation(el));
1371                 });
1372                 args.conversationsChangeCallback.conversationsadded.call(null, conversations);
1373             }
1374         },
1375         conversationsupdated: function (data) {
1376             if (args.conversationsChangeCallback.conversationsupdated) {
1377                 var conversations = [];
1378                 data.forEach(function (el) {
1379                    conversations.push(new MessageConversation(el));
1380                 });
1381                 args.conversationsChangeCallback.conversationsupdated.call(null, conversations);
1382             }
1383         },
1384         conversationsremoved: function (data) {
1385             if (args.conversationsChangeCallback.conversationsremoved) {
1386                 var conversations = [];
1387                 data.forEach(function (el) {
1388                     conversations.push(new MessageConversation(el));
1389                 });
1390                 args.conversationsChangeCallback.conversationsremoved.call(null, conversations);
1391             }
1392         }
1393     });
1394
1395     var result = bridge.sync({
1396         cmd: 'MessageStorage_addConversationsChangeListener',
1397         cid: cid,
1398         args: {
1399             filter: args.filter ? addTypeToFilter_(args.filter) : null,
1400             serviceId: self.service.id
1401         }
1402     });
1403
1404     bridge.attach(cid, 'watchId', result);
1405     return result;
1406 };
1407
1408 MessageStorage.prototype.addFoldersChangeListener = function () {
1409     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1410
1411     var args = validator_.validateArgs(arguments, [
1412         {name: 'foldersChangeCallback', type: types_.LISTENER,
1413                 values: ['foldersadded', 'foldersupdated', 'foldersremoved']},
1414         {
1415             name: 'filter',
1416             type: types_.PLATFORM_OBJECT,
1417             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter],
1418             optional: true,
1419             nullable: true
1420         }
1421     ]);
1422
1423     var self = this;
1424
1425     var cid = bridge.listener({
1426         foldersadded: function (data) {
1427             if (args.foldersChangeCallback.foldersadded) {
1428                 var folders = [];
1429                 data.forEach(function (el) {
1430                     folders.push(new MessageFolder(el));
1431                 });
1432                 args.foldersChangeCallback.foldersadded.call(null, folders);
1433             }
1434         },
1435         foldersupdated: function (data) {
1436             if (args.foldersChangeCallback.foldersupdated) {
1437                 var folders = [];
1438                 data.forEach(function (el) {
1439                     folders.push(new MessageFolder(el));
1440                 });
1441                 args.foldersChangeCallback.foldersupdated.call(null, folders);
1442             }
1443         },
1444         foldersremoved: function (data) {
1445             if (args.foldersChangeCallback.foldersremoved) {
1446                 var folders = [];
1447                 data.forEach(function (el) {
1448                     folders.push(new MessageFolder(el));
1449                 });
1450                 args.foldersChangeCallback.foldersremoved.call(null, folders);
1451             }
1452         }
1453     });
1454
1455     var result = bridge.sync({
1456         cmd: 'MessageStorage_addFoldersChangeListener',
1457         cid: cid,
1458         args: {
1459             filter: args.filter ? addTypeToFilter_(args.filter) : null,
1460             serviceId: self.service.id
1461         }
1462     });
1463
1464     bridge.attach(cid, 'watchId', result);
1465     return result;
1466 };
1467
1468 MessageStorage.prototype.removeChangeListener = function () {
1469     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1470
1471     var args = validator_.validateArgs(arguments, [
1472         {name: 'watchId', type: types_.LONG}
1473     ]);
1474
1475     var self = this;
1476
1477     var result = bridge.sync({
1478         cmd: 'MessageStorage_removeChangeListener',
1479         args: {
1480             watchId: args.watchId,
1481             serviceId: self.service.id
1482         }
1483     });
1484
1485     bridge.find('watchId', args.watchId).forEach(function (e) {
1486         bridge.remove(e.id);
1487     });
1488     return result;
1489 };
1490
1491 function MessageConversation(data) {
1492     propertyFactory_(this, 'id'            , data.id             || null , Property.E);
1493     propertyFactory_(this, 'type'          , data.type           || ''   , Property.E);
1494     propertyFactory_(this, 'timestamp'     , data.timestamp ? new Date(data.timestamp * 1000) : null , Property.E);
1495     propertyFactory_(this, 'messageCount'  , data.messageCount   || 0    , Property.E);
1496     propertyFactory_(this, 'unreadMessages', data.unreadMessages || 0    , Property.E);
1497     propertyFactory_(this, 'preview'       , data.preview        || ''   , Property.E);
1498     propertyFactory_(this, 'subject'       , data.subject        || ''   , Property.E);
1499     propertyFactory_(this, 'isRead'        , data.isRead         || false, Property.E);
1500     propertyFactory_(this, 'from'          , data.from           || null , Property.E);
1501     propertyFactory_(this, 'to'            , data.to             || []   , Property.E);
1502     propertyFactory_(this, 'cc'            , data.cc             || []   , Property.E);
1503     propertyFactory_(this, 'bcc'           , data.bcc            || []   , Property.E);
1504     propertyFactory_(this, 'lastMessageId' , data.lastMessageId  || null , Property.E);
1505 };
1506
1507 function MessageFolder(data) {
1508     var _internal = {
1509             id: data.id || null,
1510             parentId: data.parentId || null,
1511             serviceId: data.serviceId || '',
1512             contentType: data.contentType || '',
1513             name: data.name || '',
1514             path: data.path || '',
1515             type: data.type || '',
1516             synchronizable: data.synchronizable || false
1517         };
1518
1519         Object.defineProperty(
1520             this,
1521             'id',
1522             {
1523                 get: function () {return _internal.id;},
1524                 enumerable: true
1525             }
1526         );
1527
1528         Object.defineProperty(
1529                 this,
1530                 'parentId',
1531                 {
1532                     get: function () {return _internal.parentId;},
1533                     enumerable: true
1534                 }
1535         );
1536
1537         Object.defineProperty(
1538                 this,
1539                 'serviceId',
1540                 {
1541                     get: function () {return _internal.serviceId;},
1542                     enumerable: true
1543                 }
1544         );
1545
1546         Object.defineProperty(
1547                 this,
1548                 'contentType',
1549                 {
1550                     get: function () {return _internal.contentType;},
1551                     enumerable: true
1552                 }
1553         );
1554
1555         Object.defineProperty(
1556                 this,
1557                 'name',
1558                 {
1559                     get: function () {return _internal.name;},
1560                     set: function (value) { if (value) _internal.name = value;},
1561                     enumerable: true
1562                 }
1563         );
1564
1565         Object.defineProperty(
1566                 this,
1567                 'path',
1568                 {
1569                     get: function () {return _internal.path;},
1570                     enumerable: true
1571                 }
1572         );
1573
1574         Object.defineProperty(
1575                 this,
1576                 'type',
1577                 {
1578                     get: function () {return _internal.type;},
1579                     enumerable: true
1580                 }
1581         );
1582
1583         Object.defineProperty(
1584                 this,
1585                 'synchronizable',
1586                 {
1587                     get: function () {return _internal.synchronizable;},
1588                     set: function (value) { _internal.synchronizable = Boolean(value);},
1589                     enumerable: true
1590                 }
1591         );
1592 };
1593
1594 tizen.Message = Message;
1595
1596 tizen.MessageAttachment = MessageAttachment;
1597
1598 exports = new Messaging();