Merge branch 'tizen_3.0' into tizen
[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                     // TODO create CPP layer
392                     /*
393                      *return bridge.sync({
394                      *    cmd: 'Message_messageStatus',
395                      *    args: {
396                      *        id: _internal.id
397                      *    }
398                      *});
399                      */
400                     return _internal.messageStatus;
401                 } else {
402                     return '';
403                 }
404             },
405             set: function (value) {return;},
406             enumerable: true
407         }
408     );
409
410     // attachments
411     Object.defineProperty(
412         this,
413         'attachments',
414         {
415             get: function () {return _internal.attachments;},
416             set: function(value) {
417                 if (value instanceof InternalValues_) {
418                     value = value.attachments;
419                     for (var k = 0; k < value.length; ++k) {
420                         if (!(value[k] instanceof tizen.MessageAttachment)) {
421                             if (_internal.attachments[k]) {
422                                 updateInternal_(_internal.attachments[k], value[k]);
423                             } else {
424                                 _internal.attachments[k] = new MessageAttachment(
425                                         new InternalValues_(value[k]));
426                             }
427                         } else {
428                             _internal.attachments[k] = value[k];
429                         }
430                     }
431                     // if new array is shorter than the old one, remove excess elements
432                     if (value.length < _internal.length) {
433                         _internal.splice(value.length, _internal.length - value.length);
434                     }
435                 } else if (T_.isArray(value)) {
436                     for (var k = 0; k < value.length; ++k) {
437                         if (!(value[k] instanceof tizen.MessageAttachment)) {
438                             return;
439                         }
440                     }
441                     _internal.attachments = value;
442                 }
443             },
444             enumerable: true
445         }
446     );
447 };
448
449 function MessageInit(data) {
450     if (!(this instanceof MessageInit)) {
451         return new MessageInit(data);
452     }
453     if (data === null || typeof data !== 'object') {
454         data = {};
455     }
456     propertyFactory_(this, 'subject'       , data.subject        || ''   , Property.E | Property.W);
457     propertyFactory_(this, 'to'            , data.to             || []   , Property.E | Property.W);
458     propertyFactory_(this, 'cc'            , data.cc             || []   , Property.E | Property.W);
459     propertyFactory_(this, 'bcc'           , data.bcc            || []   , Property.E | Property.W);
460     propertyFactory_(this, 'plainBody'     , data.plainBody      || ''   , Property.E | Property.W);
461     propertyFactory_(this, 'htmlBody'      , data.htmlBody       || ''   , Property.E | Property.W);
462     propertyFactory_(this, 'isHighPriority', data.isHighPriority || false, Property.E | Property.W);
463 };
464
465 function MessageInit_(data) {
466     if (!(this instanceof MessageInit_)) {
467         return new MessageInit_(data);
468     }
469     if ( !data || typeof data !== 'object') {
470         data = {};
471     }
472     this.id             = data.id             || null;
473     this.conversationId = data.conversationId || null;
474     this.folderId       = data.folderId       || null;
475     this.timestamp      = data.timestamp      || null;
476     this.from           = data.from           || '';
477     this.to             = data.to             || [];
478     this.cc             = data.cc             || [];
479     this.bcc            = data.bcc            || [];
480     this.isRead         = data.isRead         || false;
481     this.hasAttachment  = data.hasAttachment  || null;
482     this.isHighPriority = data.isHighPriority || false;
483     this.subject        = data.subject        || '';
484     this.inResponseTo   = data.inResponseTo   || null;
485     this.attachments = [];
486     this.plainBody      = data.body ? data.body.plainBody : '';
487     this.htmlBody       = data.body ? data.body.htmlBody : '';
488
489     var self = this;
490     if (data.attachments && data.attachments.constructor === Array) {
491         data.attachments.forEach(function(el) {
492            if (!el) return;
493
494            if (el.constructor === MessageAttachment) {
495                self.attachments.push(el);
496            } else {
497                self.attachments.push(new MessageAttachment(new InternalValues_(el)));
498            }
499         });
500     }
501 };
502
503 function MessageBody(data) {
504     if (!this instanceof MessageBody) {
505         return new MessageBody(data);
506     }
507     if (data === null || typeof data !== 'object') {
508         data = {};
509     }
510
511     var _internal = {
512         messageId: data.messageId || null,
513         loaded: data.loaded || false,
514         plainBody: data.plainBody || '',
515         htmlBody: data.htmlBody || '',
516         inlineAttachments: data.inlineAttachments || []
517     };
518
519     // messageId
520     Object.defineProperty(
521         this,
522         'messageId',
523         {
524             get: function () {return _internal.messageId;},
525             set: function (value) {
526                 if (value instanceof InternalValues_) _internal.messageId = value.messageId;
527             },
528             enumerable: true
529         }
530     );
531
532     // loaded
533     Object.defineProperty(
534         this,
535         'loaded',
536         {
537             get: function () {return _internal.loaded;},
538             set: function (value) {
539                 if (value instanceof InternalValues_) _internal.loaded = value.loaded;
540             },
541             enumerable: true
542         }
543     );
544
545     // plainBody
546     Object.defineProperty(
547         this,
548         'plainBody',
549         {
550             get: function () {return _internal.plainBody;},
551             set: function (value) {
552                 if (value instanceof InternalValues_) {
553                     _internal.plainBody = String(value.plainBody);
554                 } else {
555                     _internal.plainBody = String(value);
556                 }
557             },
558             enumerable: true
559         }
560     );
561
562     // htmlBody
563     Object.defineProperty(
564         this,
565         'htmlBody',
566         {
567             get: function () {return _internal.htmlBody;},
568             set: function (value) {
569                 if (value instanceof InternalValues_) {
570                     _internal.htmlBody = String(value.htmlBody);
571                 } else {
572                     _internal.htmlBody = String(value);
573                 }
574             },
575             enumerable: true
576         }
577     );
578
579     // inlineAttachments
580     Object.defineProperty(
581         this,
582         'inlineAttachments',
583         {
584             get: function () {return _internal.inlineAttachments;},
585             set: function (value) {
586                 if (value instanceof InternalValues_) {
587                     _internal.inlineAttachments = value.inlineAttachments;
588                 } else if (T_.isArray(value)) {
589                     _internal.inlineAttachments = value;
590                 }
591             },
592             enumerable: true
593         }
594     );
595 };
596
597 var messageAttachmentsLoaded = {};
598
599 function MessageAttachment(first, second) {
600     validator_.isConstructorCall(this, MessageAttachment);
601     if (!this instanceof MessageAttachment) {
602         return new MessageAttachment(data);
603     }
604
605     var internalConstructor = first instanceof InternalValues_;
606     var _internal = {
607         messageId: internalConstructor ? first.messageId : null,
608         id: internalConstructor ? first.id : null,
609         mimeType: internalConstructor ? first.mimeType : (undefined == second ? null : second),
610         filePath: internalConstructor ? first.filePath : first,
611     };
612
613     // messageId
614     Object.defineProperty(
615         this,
616         'messageId',
617         {
618             get: function () {return _internal.messageId;},
619             set: function (value) {
620                 if (value instanceof InternalValues_) _internal.messageId = value.messageId;
621             },
622             enumerable: true
623         }
624     );
625     // id
626     Object.defineProperty(
627         this,
628         'id',
629         {
630             get: function () {return _internal.id;},
631             set: function (value) {
632                 if (value instanceof InternalValues_) _internal.id = value.id;
633             },
634             enumerable: true
635         }
636     );
637     // mimeType
638     Object.defineProperty(
639         this,
640         'mimeType',
641         {
642             get: function () {return _internal.mimeType;},
643             set: function (value) {
644                 if (value instanceof InternalValues_) _internal.mimeType = value.mimeType;
645             },
646             enumerable: true
647         }
648     );
649     // filePath
650     Object.defineProperty(
651         this,
652         'filePath',
653         {
654             get: function () {
655                 if (_internal.id && !messageAttachmentsLoaded[_internal.id]) {
656                     return null;
657                 }
658
659                 return _internal.filePath;
660             },
661             set: function (value) {
662                 if (value instanceof InternalValues_) _internal.filePath = value.filePath;
663             },
664             enumerable: true
665         }
666     );
667 };
668
669 function Messaging() {};
670
671 /**
672  * Gets the messaging service of a given type for a given account.
673  * @param {!MessageServiceTag} messageServiceType Type of the services to be retrieved.
674  * @param {!MessageServiceArraySuccessCallback} successCallback Callback function that is called
675  *     when the services are successfully retrieved.
676  * @param {ErrorCallback} errorCallback Callback function that is called when an error occurs.
677  */
678 Messaging.prototype.getMessageServices = function () {
679     var args = validator_.validateArgs(arguments, [
680         {name: 'messageServiceType', type: types_.ENUM, values: MessageServiceTag},
681         {name: 'successCallback', type: types_.FUNCTION},
682         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
683     ]);
684
685     bridge.async({
686         cmd: 'Messaging_getMessageServices',
687         args: {
688             messageServiceType: args.messageServiceType
689         }
690     }).then({
691         success: function (data) {
692             var servicesArr = [];
693             data.forEach(function(e){
694                 servicesArr.push(new MessageService(e));
695             });
696             args.successCallback.call(null, servicesArr);
697         },
698         error: function (e) {
699             if (args.errorCallback) {
700                 args.errorCallback.call(
701                     null,
702                     new WebAPIException(e.error)
703                 )
704             }
705         }
706     });
707 }
708 function MessageStorage(){};
709 function MessageService(data) {
710     propertyFactory_(this, 'id', data.id, Property.E);
711     propertyFactory_(this, 'type', data.type, Property.E);
712     propertyFactory_(this, 'name', data.name, Property.E);
713     propertyFactory_(this, 'messageStorage', new MessageStorage(this), Property.E);
714 };
715
716 MessageService.prototype.sendMessage = function () {
717     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
718
719     var args = validator_.validateArgs(arguments, [
720         {name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message},
721         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
722         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
723         {name: 'simIndex', type: types_.LONG, optional: true, nullable: true}
724     ]);
725
726     if (args.message.type != this.type) {
727         throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
728     }
729
730     var self = this;
731     bridge.async({
732         cmd: 'MessageService_sendMessage',
733         args: {
734             message: args.message,
735             simIndex: args.simIndex || 1,
736             serviceId: self.id
737         }
738     }).then({
739         success: function (data) {
740             var message = data.message;
741             if (message) {
742                 var body = message.body;
743                 if (body) {
744                     updateInternal_(args.message.body, body)
745                     delete message.body;
746                 }
747                 updateInternal_(args.message, message);
748             }
749
750             if (args.successCallback) {
751                 args.successCallback.call(null, data.recipients);
752             }
753         },
754         error: function (e) {
755             if (args.errorCallback) {
756                 args.errorCallback.call(
757                     null,
758                     new WebAPIException(e.error)
759                 )
760             }
761         }
762     });
763 };
764
765 MessageService.prototype.loadMessageBody = function () {
766     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
767
768     var args = validator_.validateArgs(arguments, [
769         {name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message},
770         {name: 'successCallback', type: types_.FUNCTION},
771         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
772     ]);
773
774     if (args.message.type != this.type) {
775         throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
776     }
777
778     var self = this;
779
780     bridge.async({
781         cmd: 'MessageService_loadMessageBody',
782         args: {
783             message: args.message,
784             serviceId: self.id
785         }
786     }).then({
787         success: function (data) {
788             var body = data.messageBody;
789             if (body) {
790                 updateInternal_(args.message.body, body)
791             }
792
793             args.successCallback.call(
794                 null,
795                 args.message
796             );
797         },
798         error: function (e) {
799             if (args.errorCallback) {
800                 args.errorCallback.call(
801                     null,
802                     new WebAPIException(e.error)
803                 )
804             }
805         }
806     });
807 };
808 MessageService.prototype.loadMessageAttachment = function () {
809     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
810
811     var args = validator_.validateArgs(arguments, [
812         {name: 'attachment', type: types_.PLATFORM_OBJECT, values: MessageAttachment},
813         {name: 'successCallback', type: types_.FUNCTION},
814         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
815     ]);
816
817     var self = this;
818     var firstCall = false;
819     if (!messageAttachmentsLoaded[args.attachment.id]) {
820         firstCall = true;
821         messageAttachmentsLoaded[args.attachment.id] = true;
822     }
823
824     bridge.async({
825         cmd: 'MessageService_loadMessageAttachment',
826         args: {
827             attachment: args.attachment,
828             serviceId: self.id
829         }
830     }).then({
831         success: function (data) {
832             if (args.successCallback) {
833                 var messageAttachment = data.messageAttachment;
834                 if (messageAttachment) {
835                     updateInternal_(args.attachment, messageAttachment);
836                 }
837
838                 args.successCallback.call(
839                     null,
840                     args.attachment
841                 );
842             }
843         },
844         error: function (e) {
845             if (firstCall) {
846                 messageAttachmentsLoaded[args.attachment.id] = false;
847             }
848             if (args.errorCallback) {
849                 args.errorCallback.call(
850                     null,
851                     new WebAPIException(e.error)
852                 )
853             }
854         }
855     });
856 };
857
858 MessageService.prototype.sync = function () {
859     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
860
861     var args = validator_.validateArgs(arguments, [
862         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
863         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
864         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
865     ]);
866
867     var self = this;
868     var cid = bridge.listener({
869         success: function () {
870             if (args.successCallback) {
871                 args.successCallback.call(null);
872             }
873         },
874         error: function (e) {
875             if (args.errorCallback) {
876                 args.errorCallback.call(
877                     null,
878                     new WebAPIException(e.error)
879                 )
880             }
881         }
882     });
883
884     var result = bridge.sync({
885         cmd: 'MessageService_sync',
886         cid: cid,
887         args: {
888             id: self.id,
889             limit: args.limit || null
890         }
891     });
892
893     return result;
894 };
895
896 MessageService.prototype.syncFolder = function () {
897     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
898
899     var args = validator_.validateArgs(arguments, [
900         {name: 'folder', type: types_.PLATFORM_OBJECT, values: MessageFolder},
901         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
902         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
903         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
904     ]);
905
906     var self = this;
907     var cid = bridge.listener({
908         success: function () {
909             if (args.successCallback) {
910                 args.successCallback.call(null);
911             }
912         },
913         error: function (e) {
914             if (args.errorCallback) {
915                 args.errorCallback.call(
916                     null,
917                     new WebAPIException(e.error)
918                 )
919             }
920         }
921     });
922
923     var result = bridge.sync({
924         cmd: 'MessageService_syncFolder',
925         cid: cid,
926         args: {
927             id: self.id,
928             folder: args.folder,
929             limit: args.limit || null
930         }
931     });
932
933     return result;
934 };
935
936 MessageService.prototype.stopSync = function () {
937     var args = validator_.validateArgs(arguments, [
938         {name: 'opId', type: types_.LONG}
939     ]);
940
941     // Additionally to pass the MessageService_email_stopSync_opId_TypeMismatch test
942     if (!isFinite(arguments[0])) {
943         throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
944     }
945
946     var self = this;
947     bridge.sync({
948         cmd: 'MessageService_stopSync',
949         args: {
950             id: self.id,
951             opId: args.opId
952         }
953     });
954 };
955
956 function MessageStorage(service) {
957     propertyFactory_(this, 'service', service);
958 };
959
960 MessageStorage.prototype.addDraftMessage = function () {
961     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
962
963     var args = validator_.validateArgs(arguments, [
964         {name: 'message', type: types_.PLATFORM_OBJECT, values: tizen.Message},
965         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
966         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
967     ]);
968
969     if (args.message.type != this.service.type) {
970         throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
971     }
972
973     var self = this;
974     bridge.async({
975         cmd: 'MessageStorage_addDraftMessage',
976         args: {
977             message: args.message,
978             serviceId: self.service.id
979         }
980     }).then({
981         success: function (data) {
982             var message = data.message;
983             if (message) {
984                 var body = message.body;
985                 if (body) {
986                     updateInternal_(args.message.body, body)
987                     delete message.body;
988                 }
989                 var attachments = message.attachments;
990                 if (attachments) {
991                     for (var i = 0; i < attachments.length; i++) {
992                         messageAttachmentsLoaded[attachments[i].id] = true;
993                     }
994                 }
995                 updateInternal_(args.message, message);
996             }
997
998             if (args.successCallback) {
999                 args.successCallback.call(null);
1000             }
1001         },
1002         error: function (e) {
1003             if (args.errorCallback) {
1004                 args.errorCallback.call(
1005                     null,
1006                     new WebAPIException(e.error)
1007                 )
1008             }
1009         }
1010     });
1011 };
1012
1013 MessageStorage.prototype.findMessages = function () {
1014     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1015
1016     var args = validator_.validateArgs(arguments, [
1017         {
1018             name: 'filter',
1019             type: types_.PLATFORM_OBJECT,
1020             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter]
1021         },
1022         {name: 'successCallback', type: types_.FUNCTION},
1023         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
1024         {name: 'sort', type: types_.PLATFORM_OBJECT, values: tizen.SortMode, optional: true,
1025                 nullable: true},
1026         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true},
1027         {name: 'offset', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
1028     ]);
1029
1030     var self = this;
1031
1032     bridge.async({
1033         cmd: 'MessageStorage_findMessages',
1034         args: {
1035             filter: addTypeToFilter_(args.filter) || null,
1036             sort: args.sort || null,
1037             limit: args.limit || null,
1038             offset: args.offset || null,
1039             serviceId: self.service.id,
1040             type: self.service.type
1041         }
1042     }).then({
1043         success: function (data) {
1044             var messages = [];
1045             data.forEach(function (el) {
1046                 messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1047             });
1048             args.successCallback.call(null, messages);
1049         },
1050         error: function (e) {
1051             if (args.errorCallback) {
1052                 args.errorCallback.call(
1053                     null,
1054                     new WebAPIException(e.error)
1055                 )
1056             }
1057         }
1058     });
1059 };
1060
1061 MessageStorage.prototype.removeMessages = function () {
1062     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
1063
1064     var args = validator_.validateArgs(arguments, [
1065         {name: 'messages', type: types_.ARRAY, values: Message},
1066         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
1067         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1068     ]);
1069
1070     var self = this;
1071
1072     args.messages.forEach(function(msg) {
1073         if (msg.type != self.service.type) {
1074             throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
1075         }
1076     });
1077
1078     bridge.async({
1079         cmd: 'MessageStorage_removeMessages',
1080         args: {
1081             messages: args.messages,
1082             serviceId: self.service.id,
1083             type: self.service.type
1084         }
1085     }).then({
1086         success: function () {
1087             if (args.successCallback) {
1088                 args.successCallback.call(null);
1089             }
1090         },
1091         error: function (e) {
1092             if (args.errorCallback) {
1093                 args.errorCallback.call(
1094                     null,
1095                     new WebAPIException(e.error)
1096                 )
1097             }
1098         }
1099     });
1100 };
1101
1102 MessageStorage.prototype.updateMessages = function () {
1103     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
1104
1105     var args = validator_.validateArgs(arguments, [
1106         {name: 'messages', type: types_.ARRAY, values: Message},
1107         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
1108         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1109     ]);
1110
1111     var self = this;
1112
1113     args.messages.forEach(function(msg) {
1114         if (msg.type != self.service.type) {
1115             throw new tizen.WebAPIException(tizen.WebAPIException.TYPE_MISMATCH_ERR);
1116         }
1117     });
1118
1119     bridge.async({
1120         cmd: 'MessageStorage_updateMessages',
1121         args: {
1122             messages: args.messages,
1123             serviceId: self.service.id
1124         }
1125     }).then({
1126         success: function (data) {
1127             var originals = {},
1128                     i = args.messages.length,
1129                     m;
1130             while (i--) {
1131                 m = args.messages[i];
1132                 if (m.id) {
1133                     originals[m.id] = m;
1134                 }
1135             }
1136
1137             i = data.length;
1138             while (i--) {
1139                 m = data[i];
1140                 if (originals[m.oldId]) {
1141                     var body = m.body;
1142                     if (body) {
1143                         updateInternal_(originals[m.oldId].body, body)
1144                         delete m.body;
1145                     }
1146                     updateInternal_(originals[m.oldId], m);
1147                 }
1148             }
1149
1150             if (args.successCallback) {
1151                 args.successCallback.call(null);
1152             }
1153         },
1154         error: function (e) {
1155             if (args.errorCallback) {
1156                 args.errorCallback.call(
1157                     null,
1158                     new WebAPIException(e.error)
1159                 )
1160             }
1161         }
1162     });
1163 };
1164
1165 MessageStorage.prototype.findConversations = function () {
1166     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1167
1168     var args = validator_.validateArgs(arguments, [
1169         {
1170             name: 'filter',
1171             type: types_.PLATFORM_OBJECT,
1172             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter]
1173         },
1174         {name: 'successCallback', type: types_.FUNCTION},
1175         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true},
1176         {name: 'sort', type: types_.PLATFORM_OBJECT, values: tizen.SortMode, optional: true,
1177                 nullable: true},
1178         {name: 'limit', type: types_.UNSIGNED_LONG, optional: true, nullable: true},
1179         {name: 'offset', type: types_.UNSIGNED_LONG, optional: true, nullable: true}
1180     ]);
1181
1182     var self = this;
1183
1184     bridge.async({
1185         cmd: 'MessageStorage_findConversations',
1186         args: {
1187             filter: addTypeToFilter_(args.filter),
1188             sort: args.sort || null,
1189             limit: args.limit || null,
1190             offset: args.offset || null,
1191             serviceId: self.service.id
1192         }
1193     }).then({
1194         success: function (data) {
1195             var conversations = [];
1196             data.forEach(function (el) {
1197                 conversations.push(new MessageConversation(el));
1198             });
1199             args.successCallback.call(null, conversations);
1200         },
1201         error: function (e) {
1202             if (args.errorCallback) {
1203                 args.errorCallback.call(
1204                     null,
1205                     new WebAPIException(e.error)
1206                 )
1207             }
1208         }
1209     });
1210 };
1211
1212 MessageStorage.prototype.removeConversations = function () {
1213     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_WRITE);
1214
1215     var args = validator_.validateArgs(arguments, [
1216         {name: 'conversations', type: types_.ARRAY},
1217         {name: 'successCallback', type: types_.FUNCTION, optional: true, nullable: true},
1218         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1219     ]);
1220
1221     args.conversations.forEach(function (el) {
1222         if (!el || el.constructor !== MessageConversation) {
1223             throw new WebAPIException(WebAPIException.TYPE_MISMATCH_ERR);
1224         }
1225     });
1226
1227     var self = this;
1228
1229     bridge.async({
1230         cmd: 'MessageStorage_removeConversations',
1231         args: {
1232             conversations: args.conversations,
1233             serviceId: self.service.id,
1234             type: self.service.type
1235         }
1236     }).then({
1237         success: function () {
1238             if (args.successCallback) {
1239                 args.successCallback.call(null);
1240             }
1241         },
1242         error: function (e) {
1243             if (args.errorCallback) {
1244                 args.errorCallback.call(
1245                     null,
1246                     new WebAPIException(e.error)
1247                 )
1248             }
1249         }
1250     });
1251 };
1252
1253 MessageStorage.prototype.findFolders = function () {
1254     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1255
1256     var args = validator_.validateArgs(arguments, [
1257         {
1258             name: 'filter',
1259             type: types_.PLATFORM_OBJECT,
1260             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter]
1261         },
1262         {name: 'successCallback', type: types_.FUNCTION},
1263         {name: 'errorCallback', type: types_.FUNCTION, optional: true, nullable: true}
1264     ]);
1265
1266     var self = this;
1267
1268     bridge.async({
1269         cmd: 'MessageStorage_findFolders',
1270         args: {
1271             filter: addTypeToFilter_(args.filter),
1272             sort: args.sort || null,
1273             limit: args.limit || null,
1274             offset: args.offset || null,
1275             serviceId: self.service.id
1276         }
1277     }).then({
1278         success: function (data) {
1279             var folders = [];
1280             data.forEach(function (el) {
1281                 folders.push(new MessageFolder(el));
1282             });
1283             args.successCallback.call(null, folders);
1284         },
1285         error: function (e) {
1286             if (args.errorCallback) {
1287                 args.errorCallback.call(
1288                     null,
1289                     new WebAPIException(e.error)
1290                 )
1291             }
1292         }
1293     });
1294 };
1295
1296 MessageStorage.prototype.addMessagesChangeListener = function () {
1297     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1298
1299     var args = validator_.validateArgs(arguments, [
1300         {name: 'messagesChangeCallback', type: types_.LISTENER,
1301                 values: ['messagesadded', 'messagesupdated', 'messagesremoved']},
1302         {
1303             name: 'filter',
1304             type: types_.PLATFORM_OBJECT,
1305             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter],
1306             optional: true,
1307             nullable: true
1308         }
1309     ]);
1310
1311     var self = this;
1312
1313     var cid = bridge.listener({
1314         messagesadded: function (data) {
1315             if (args.messagesChangeCallback.messagesadded) {
1316                 var messages = [];
1317                 data.forEach(function (el) {
1318                     messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1319                 });
1320                 args.messagesChangeCallback.messagesadded.call(null, messages);
1321             }
1322         },
1323         messagesupdated: function (data) {
1324             if (args.messagesChangeCallback.messagesupdated) {
1325                 var messages = [];
1326                 data.forEach(function (el) {
1327                     messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1328                 });
1329                 args.messagesChangeCallback.messagesupdated.call(null, messages);
1330             }
1331         },
1332         messagesremoved: function (data) {
1333             if (args.messagesChangeCallback.messagesremoved) {
1334                 var messages = [];
1335                 data.forEach(function (el) {
1336                     messages.push(new tizen.Message(el.type, new MessageInit_(el)));
1337                 });
1338                 args.messagesChangeCallback.messagesremoved.call(null, messages);
1339             }
1340         }
1341     });
1342
1343     var result = bridge.sync({
1344         cmd: 'MessageStorage_addMessagesChangeListener',
1345         cid: cid,
1346         args: {
1347             filter: args.filter ? addTypeToFilter_(args.filter) : null,
1348             serviceId: self.service.id
1349         }
1350     });
1351
1352     bridge.attach(cid, 'watchId', result);
1353     return result;
1354 };
1355
1356 MessageStorage.prototype.addConversationsChangeListener = function () {
1357     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1358
1359     var args = validator_.validateArgs(arguments, [
1360         {name: 'conversationsChangeCallback', type: types_.LISTENER,
1361                 values: ['conversationsadded', 'conversationsupdated', 'conversationsremoved']},
1362         {
1363             name: 'filter',
1364             type: types_.PLATFORM_OBJECT,
1365             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter],
1366             optional: true,
1367             nullable: true
1368         }
1369     ]);
1370
1371     var self = this;
1372
1373     var cid = bridge.listener({
1374         conversationsadded: function (data) {
1375             if (args.conversationsChangeCallback.conversationsadded) {
1376                 var conversations = [];
1377                 data.forEach(function (el) {
1378                     conversations.push(new MessageConversation(el));
1379                 });
1380                 args.conversationsChangeCallback.conversationsadded.call(null, conversations);
1381             }
1382         },
1383         conversationsupdated: function (data) {
1384             if (args.conversationsChangeCallback.conversationsupdated) {
1385                 var conversations = [];
1386                 data.forEach(function (el) {
1387                    conversations.push(new MessageConversation(el));
1388                 });
1389                 args.conversationsChangeCallback.conversationsupdated.call(null, conversations);
1390             }
1391         },
1392         conversationsremoved: function (data) {
1393             if (args.conversationsChangeCallback.conversationsremoved) {
1394                 var conversations = [];
1395                 data.forEach(function (el) {
1396                     conversations.push(new MessageConversation(el));
1397                 });
1398                 args.conversationsChangeCallback.conversationsremoved.call(null, conversations);
1399             }
1400         }
1401     });
1402
1403     var result = bridge.sync({
1404         cmd: 'MessageStorage_addConversationsChangeListener',
1405         cid: cid,
1406         args: {
1407             filter: args.filter ? addTypeToFilter_(args.filter) : null,
1408             serviceId: self.service.id
1409         }
1410     });
1411
1412     bridge.attach(cid, 'watchId', result);
1413     return result;
1414 };
1415
1416 MessageStorage.prototype.addFoldersChangeListener = function () {
1417     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1418
1419     var args = validator_.validateArgs(arguments, [
1420         {name: 'foldersChangeCallback', type: types_.LISTENER,
1421                 values: ['foldersadded', 'foldersupdated', 'foldersremoved']},
1422         {
1423             name: 'filter',
1424             type: types_.PLATFORM_OBJECT,
1425             values: [tizen.AttributeFilter, tizen.AttributeRangeFilter, tizen.CompositeFilter],
1426             optional: true,
1427             nullable: true
1428         }
1429     ]);
1430
1431     var self = this;
1432
1433     var cid = bridge.listener({
1434         foldersadded: function (data) {
1435             if (args.foldersChangeCallback.foldersadded) {
1436                 var folders = [];
1437                 data.forEach(function (el) {
1438                     folders.push(new MessageFolder(el));
1439                 });
1440                 args.foldersChangeCallback.foldersadded.call(null, folders);
1441             }
1442         },
1443         foldersupdated: function (data) {
1444             if (args.foldersChangeCallback.foldersupdated) {
1445                 var folders = [];
1446                 data.forEach(function (el) {
1447                     folders.push(new MessageFolder(el));
1448                 });
1449                 args.foldersChangeCallback.foldersupdated.call(null, folders);
1450             }
1451         },
1452         foldersremoved: function (data) {
1453             if (args.foldersChangeCallback.foldersremoved) {
1454                 var folders = [];
1455                 data.forEach(function (el) {
1456                     folders.push(new MessageFolder(el));
1457                 });
1458                 args.foldersChangeCallback.foldersremoved.call(null, folders);
1459             }
1460         }
1461     });
1462
1463     var result = bridge.sync({
1464         cmd: 'MessageStorage_addFoldersChangeListener',
1465         cid: cid,
1466         args: {
1467             filter: args.filter ? addTypeToFilter_(args.filter) : null,
1468             serviceId: self.service.id
1469         }
1470     });
1471
1472     bridge.attach(cid, 'watchId', result);
1473     return result;
1474 };
1475
1476 MessageStorage.prototype.removeChangeListener = function () {
1477     xwalk.utils.checkPrivilegeAccess(xwalk.utils.privilege.MESSAGING_READ);
1478
1479     var args = validator_.validateArgs(arguments, [
1480         {name: 'watchId', type: types_.LONG}
1481     ]);
1482
1483     var self = this;
1484
1485     var result = bridge.sync({
1486         cmd: 'MessageStorage_removeChangeListener',
1487         args: {
1488             watchId: args.watchId,
1489             serviceId: self.service.id
1490         }
1491     });
1492
1493     bridge.find('watchId', args.watchId).forEach(function (e) {
1494         bridge.remove(e.id);
1495     });
1496     return result;
1497 };
1498
1499 function MessageConversation(data) {
1500     propertyFactory_(this, 'id'            , data.id             || null , Property.E);
1501     propertyFactory_(this, 'type'          , data.type           || ''   , Property.E);
1502     propertyFactory_(this, 'timestamp'     , data.timestamp ? new Date(data.timestamp * 1000) : null , Property.E);
1503     propertyFactory_(this, 'messageCount'  , data.messageCount   || 0    , Property.E);
1504     propertyFactory_(this, 'unreadMessages', data.unreadMessages || 0    , Property.E);
1505     propertyFactory_(this, 'preview'       , data.preview        || ''   , Property.E);
1506     propertyFactory_(this, 'subject'       , data.subject        || ''   , Property.E);
1507     propertyFactory_(this, 'isRead'        , data.isRead         || false, Property.E);
1508     propertyFactory_(this, 'from'          , data.from           || null , Property.E);
1509     propertyFactory_(this, 'to'            , data.to             || []   , Property.E);
1510     propertyFactory_(this, 'cc'            , data.cc             || []   , Property.E);
1511     propertyFactory_(this, 'bcc'           , data.bcc            || []   , Property.E);
1512     propertyFactory_(this, 'lastMessageId' , data.lastMessageId  || null , Property.E);
1513 };
1514
1515 function MessageFolder(data) {
1516     var _internal = {
1517             id: data.id || null,
1518             parentId: data.parentId || null,
1519             serviceId: data.serviceId || '',
1520             contentType: data.contentType || '',
1521             name: data.name || '',
1522             path: data.path || '',
1523             type: data.type || '',
1524             synchronizable: data.synchronizable || false
1525         };
1526
1527         Object.defineProperty(
1528             this,
1529             'id',
1530             {
1531                 get: function () {return _internal.id;},
1532                 enumerable: true
1533             }
1534         );
1535
1536         Object.defineProperty(
1537                 this,
1538                 'parentId',
1539                 {
1540                     get: function () {return _internal.parentId;},
1541                     enumerable: true
1542                 }
1543         );
1544
1545         Object.defineProperty(
1546                 this,
1547                 'serviceId',
1548                 {
1549                     get: function () {return _internal.serviceId;},
1550                     enumerable: true
1551                 }
1552         );
1553
1554         Object.defineProperty(
1555                 this,
1556                 'contentType',
1557                 {
1558                     get: function () {return _internal.contentType;},
1559                     enumerable: true
1560                 }
1561         );
1562
1563         Object.defineProperty(
1564                 this,
1565                 'name',
1566                 {
1567                     get: function () {return _internal.name;},
1568                     set: function (value) { if (value) _internal.name = value;},
1569                     enumerable: true
1570                 }
1571         );
1572
1573         Object.defineProperty(
1574                 this,
1575                 'path',
1576                 {
1577                     get: function () {return _internal.path;},
1578                     enumerable: true
1579                 }
1580         );
1581
1582         Object.defineProperty(
1583                 this,
1584                 'type',
1585                 {
1586                     get: function () {return _internal.type;},
1587                     enumerable: true
1588                 }
1589         );
1590
1591         Object.defineProperty(
1592                 this,
1593                 'synchronizable',
1594                 {
1595                     get: function () {return _internal.synchronizable;},
1596                     set: function (value) { _internal.synchronizable = Boolean(value);},
1597                     enumerable: true
1598                 }
1599         );
1600 };
1601
1602 tizen.Message = Message;
1603
1604 tizen.MessageAttachment = MessageAttachment;
1605
1606 exports = new Messaging();