|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const DEBUG = false; |
|
8 function debug(s) { dump("-*- Fallback ContactService component: " + s + "\n"); } |
|
9 |
|
10 const Cu = Components.utils; |
|
11 const Cc = Components.classes; |
|
12 const Ci = Components.interfaces; |
|
13 |
|
14 this.EXPORTED_SYMBOLS = ["ContactService"]; |
|
15 |
|
16 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
17 Cu.import("resource://gre/modules/Services.jsm"); |
|
18 Cu.import("resource://gre/modules/ContactDB.jsm"); |
|
19 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); |
|
20 |
|
21 XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
|
22 "@mozilla.org/parentprocessmessagemanager;1", |
|
23 "nsIMessageListenerManager"); |
|
24 |
|
25 |
|
26 /* all exported symbols need to be bound to this on B2G - Bug 961777 */ |
|
27 let ContactService = this.ContactService = { |
|
28 init: function() { |
|
29 if (DEBUG) debug("Init"); |
|
30 this._messages = ["Contacts:Find", "Contacts:GetAll", "Contacts:GetAll:SendNow", |
|
31 "Contacts:Clear", "Contact:Save", |
|
32 "Contact:Remove", "Contacts:RegisterForMessages", |
|
33 "child-process-shutdown", "Contacts:GetRevision", |
|
34 "Contacts:GetCount"]; |
|
35 this._children = []; |
|
36 this._cursors = new Map(); |
|
37 this._messages.forEach(function(msgName) { |
|
38 ppmm.addMessageListener(msgName, this); |
|
39 }.bind(this)); |
|
40 |
|
41 this._db = new ContactDB(); |
|
42 this._db.init(); |
|
43 |
|
44 this.configureSubstringMatching(); |
|
45 |
|
46 Services.obs.addObserver(this, "profile-before-change", false); |
|
47 Services.prefs.addObserver("ril.lastKnownSimMcc", this, false); |
|
48 }, |
|
49 |
|
50 observe: function(aSubject, aTopic, aData) { |
|
51 if (aTopic === 'profile-before-change') { |
|
52 this._messages.forEach(function(msgName) { |
|
53 ppmm.removeMessageListener(msgName, this); |
|
54 }.bind(this)); |
|
55 Services.obs.removeObserver(this, "profile-before-change"); |
|
56 Services.prefs.removeObserver("dom.phonenumber.substringmatching", this); |
|
57 ppmm = null; |
|
58 this._messages = null; |
|
59 if (this._db) |
|
60 this._db.close(); |
|
61 this._db = null; |
|
62 this._children = null; |
|
63 this._cursors = null; |
|
64 } else if (aTopic === 'nsPref:changed' && aData === "ril.lastKnownSimMcc") { |
|
65 this.configureSubstringMatching(); |
|
66 } |
|
67 }, |
|
68 |
|
69 configureSubstringMatching: function() { |
|
70 let countryName = PhoneNumberUtils.getCountryName(); |
|
71 if (Services.prefs.getPrefType("dom.phonenumber.substringmatching." + countryName) == Ci.nsIPrefBranch.PREF_INT) { |
|
72 let val = Services.prefs.getIntPref("dom.phonenumber.substringmatching." + countryName); |
|
73 if (val) { |
|
74 this._db.enableSubstringMatching(val); |
|
75 return; |
|
76 } |
|
77 } |
|
78 // if we got here, we dont have a substring setting |
|
79 // for this country, so disable substring matching |
|
80 this._db.disableSubstringMatching(); |
|
81 }, |
|
82 |
|
83 assertPermission: function(aMessage, aPerm) { |
|
84 if (!aMessage.target.assertPermission(aPerm)) { |
|
85 Cu.reportError("Contacts message " + aMessage.name + |
|
86 " from a content process with no" + aPerm + " privileges."); |
|
87 return false; |
|
88 } |
|
89 return true; |
|
90 }, |
|
91 |
|
92 broadcastMessage: function broadcastMessage(aMsgName, aContent) { |
|
93 this._children.forEach(function(msgMgr) { |
|
94 msgMgr.sendAsyncMessage(aMsgName, aContent); |
|
95 }); |
|
96 }, |
|
97 |
|
98 receiveMessage: function(aMessage) { |
|
99 if (DEBUG) debug("receiveMessage " + aMessage.name); |
|
100 let mm = aMessage.target; |
|
101 let msg = aMessage.data; |
|
102 |
|
103 switch (aMessage.name) { |
|
104 case "Contacts:Find": |
|
105 if (!this.assertPermission(aMessage, "contacts-read")) { |
|
106 return null; |
|
107 } |
|
108 let result = []; |
|
109 this._db.find( |
|
110 function(contacts) { |
|
111 for (let i in contacts) { |
|
112 result.push(contacts[i]); |
|
113 } |
|
114 |
|
115 if (DEBUG) debug("result:" + JSON.stringify(result)); |
|
116 mm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result}); |
|
117 }.bind(this), |
|
118 function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this), |
|
119 msg.options.findOptions); |
|
120 break; |
|
121 case "Contacts:GetAll": |
|
122 if (!this.assertPermission(aMessage, "contacts-read")) { |
|
123 return null; |
|
124 } |
|
125 let cursorList = this._cursors.get(mm); |
|
126 if (!cursorList) { |
|
127 cursorList = []; |
|
128 this._cursors.set(mm, cursorList); |
|
129 } |
|
130 cursorList.push(msg.cursorId); |
|
131 |
|
132 this._db.getAll( |
|
133 function(aContacts) { |
|
134 try { |
|
135 mm.sendAsyncMessage("Contacts:GetAll:Next", {cursorId: msg.cursorId, contacts: aContacts}); |
|
136 if (aContacts === null) { |
|
137 let cursorList = this._cursors.get(mm); |
|
138 let index = cursorList.indexOf(msg.cursorId); |
|
139 cursorList.splice(index, 1); |
|
140 } |
|
141 } catch (e) { |
|
142 if (DEBUG) debug("Child is dead, DB should stop sending contacts"); |
|
143 throw e; |
|
144 } |
|
145 }.bind(this), |
|
146 function(aErrorMsg) { mm.sendAsyncMessage("Contacts:GetAll:Return:KO", { requestID: msg.cursorId, errorMsg: aErrorMsg }); }, |
|
147 msg.findOptions, msg.cursorId); |
|
148 break; |
|
149 case "Contacts:GetAll:SendNow": |
|
150 // sendNow is a no op if there isn't an existing cursor in the DB, so we |
|
151 // don't need to assert the permission again. |
|
152 this._db.sendNow(msg.cursorId); |
|
153 break; |
|
154 case "Contact:Save": |
|
155 if (msg.options.reason === "create") { |
|
156 if (!this.assertPermission(aMessage, "contacts-create")) { |
|
157 return null; |
|
158 } |
|
159 } else { |
|
160 if (!this.assertPermission(aMessage, "contacts-write")) { |
|
161 return null; |
|
162 } |
|
163 } |
|
164 this._db.saveContact( |
|
165 msg.options.contact, |
|
166 function() { |
|
167 mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.options.contact.id }); |
|
168 this.broadcastMessage("Contact:Changed", { contactID: msg.options.contact.id, reason: msg.options.reason }); |
|
169 }.bind(this), |
|
170 function(aErrorMsg) { mm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this) |
|
171 ); |
|
172 break; |
|
173 case "Contact:Remove": |
|
174 if (!this.assertPermission(aMessage, "contacts-write")) { |
|
175 return null; |
|
176 } |
|
177 this._db.removeContact( |
|
178 msg.options.id, |
|
179 function() { |
|
180 mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.options.id }); |
|
181 this.broadcastMessage("Contact:Changed", { contactID: msg.options.id, reason: "remove" }); |
|
182 }.bind(this), |
|
183 function(aErrorMsg) { mm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this) |
|
184 ); |
|
185 break; |
|
186 case "Contacts:Clear": |
|
187 if (!this.assertPermission(aMessage, "contacts-write")) { |
|
188 return null; |
|
189 } |
|
190 this._db.clear( |
|
191 function() { |
|
192 mm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); |
|
193 this.broadcastMessage("Contact:Changed", { reason: "remove" }); |
|
194 }.bind(this), |
|
195 function(aErrorMsg) { |
|
196 mm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); |
|
197 }.bind(this) |
|
198 ); |
|
199 break; |
|
200 case "Contacts:GetRevision": |
|
201 if (!this.assertPermission(aMessage, "contacts-read")) { |
|
202 return null; |
|
203 } |
|
204 this._db.getRevision( |
|
205 function(revision) { |
|
206 mm.sendAsyncMessage("Contacts:Revision", { |
|
207 requestID: msg.requestID, |
|
208 revision: revision |
|
209 }); |
|
210 }, |
|
211 function(aErrorMsg) { |
|
212 mm.sendAsyncMessage("Contacts:GetRevision:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); |
|
213 }.bind(this) |
|
214 ); |
|
215 break; |
|
216 case "Contacts:GetCount": |
|
217 if (!this.assertPermission(aMessage, "contacts-read")) { |
|
218 return null; |
|
219 } |
|
220 this._db.getCount( |
|
221 function(count) { |
|
222 mm.sendAsyncMessage("Contacts:Count", { |
|
223 requestID: msg.requestID, |
|
224 count: count |
|
225 }); |
|
226 }, |
|
227 function(aErrorMsg) { |
|
228 mm.sendAsyncMessage("Contacts:Count:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); |
|
229 }.bind(this) |
|
230 ); |
|
231 break; |
|
232 case "Contacts:RegisterForMessages": |
|
233 if (!aMessage.target.assertPermission("contacts-read")) { |
|
234 return null; |
|
235 } |
|
236 if (DEBUG) debug("Register!"); |
|
237 if (this._children.indexOf(mm) == -1) { |
|
238 this._children.push(mm); |
|
239 } |
|
240 break; |
|
241 case "child-process-shutdown": |
|
242 if (DEBUG) debug("Unregister"); |
|
243 let index = this._children.indexOf(mm); |
|
244 if (index != -1) { |
|
245 if (DEBUG) debug("Unregister index: " + index); |
|
246 this._children.splice(index, 1); |
|
247 } |
|
248 cursorList = this._cursors.get(mm); |
|
249 if (cursorList) { |
|
250 for (let id of cursorList) { |
|
251 this._db.clearDispatcher(id); |
|
252 } |
|
253 this._cursors.delete(mm); |
|
254 } |
|
255 break; |
|
256 default: |
|
257 if (DEBUG) debug("WRONG MESSAGE NAME: " + aMessage.name); |
|
258 } |
|
259 } |
|
260 } |
|
261 |
|
262 ContactService.init(); |