|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers; |
|
5 |
|
6 let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; |
|
7 |
|
8 /** |
|
9 * Push required permissions and test if |navigator.mozMobileMessage| exists. |
|
10 * Resolve if it does, reject otherwise. |
|
11 * |
|
12 * Fulfill params: |
|
13 * manager -- an reference to navigator.mozMobileMessage. |
|
14 * |
|
15 * Reject params: (none) |
|
16 * |
|
17 * @return A deferred promise. |
|
18 */ |
|
19 let manager; |
|
20 function ensureMobileMessage() { |
|
21 let deferred = Promise.defer(); |
|
22 |
|
23 let permissions = [{ |
|
24 "type": "sms", |
|
25 "allow": 1, |
|
26 "context": document, |
|
27 }]; |
|
28 SpecialPowers.pushPermissions(permissions, function() { |
|
29 ok(true, "permissions pushed: " + JSON.stringify(permissions)); |
|
30 |
|
31 manager = window.navigator.mozMobileMessage; |
|
32 if (manager) { |
|
33 log("navigator.mozMobileMessage is instance of " + manager.constructor); |
|
34 } else { |
|
35 log("navigator.mozMobileMessage is undefined."); |
|
36 } |
|
37 |
|
38 if (manager instanceof MozMobileMessageManager) { |
|
39 deferred.resolve(manager); |
|
40 } else { |
|
41 deferred.reject(); |
|
42 } |
|
43 }); |
|
44 |
|
45 return deferred.promise; |
|
46 } |
|
47 |
|
48 /** |
|
49 * Wait for one named MobileMessageManager event. |
|
50 * |
|
51 * Resolve if that named event occurs. Never reject. |
|
52 * |
|
53 * Fulfill params: the DOMEvent passed. |
|
54 * |
|
55 * @param aEventName |
|
56 * A string event name. |
|
57 * |
|
58 * @return A deferred promise. |
|
59 */ |
|
60 function waitForManagerEvent(aEventName) { |
|
61 let deferred = Promise.defer(); |
|
62 |
|
63 manager.addEventListener(aEventName, function onevent(aEvent) { |
|
64 manager.removeEventListener(aEventName, onevent); |
|
65 |
|
66 ok(true, "MobileMessageManager event '" + aEventName + "' got."); |
|
67 deferred.resolve(aEvent); |
|
68 }); |
|
69 |
|
70 return deferred.promise; |
|
71 } |
|
72 |
|
73 /** |
|
74 * Send a SMS message to a single receiver. Resolve if it succeeds, reject |
|
75 * otherwise. |
|
76 * |
|
77 * Fulfill params: |
|
78 * message -- the sent SmsMessage. |
|
79 * |
|
80 * Reject params: |
|
81 * error -- a DOMError. |
|
82 * |
|
83 * @param aReceiver the address of the receiver. |
|
84 * @param aText the text body of the message. |
|
85 * |
|
86 * @return A deferred promise. |
|
87 */ |
|
88 function sendSmsWithSuccess(aReceiver, aText) { |
|
89 let deferred = Promise.defer(); |
|
90 |
|
91 let request = manager.send(aReceiver, aText); |
|
92 request.onsuccess = function(event) { |
|
93 deferred.resolve(event.target.result); |
|
94 }; |
|
95 request.onerror = function(event) { |
|
96 deferred.reject(event.target.error); |
|
97 }; |
|
98 |
|
99 return deferred.promise; |
|
100 } |
|
101 |
|
102 /** |
|
103 * Send a MMS message with specified parameters. Resolve if it fails, reject |
|
104 * otherwise. |
|
105 * |
|
106 * Fulfill params: |
|
107 * { |
|
108 * message, -- the failed MmsMessage |
|
109 * error, -- error of the send request |
|
110 * } |
|
111 * |
|
112 * Reject params: (none) |
|
113 * |
|
114 * @param aMmsParameters a MmsParameters instance. |
|
115 * |
|
116 * @param aSendParameters a MmsSendParameters instance. |
|
117 * |
|
118 * @return A deferred promise. |
|
119 */ |
|
120 function sendMmsWithFailure(aMmsParameters, aSendParameters) { |
|
121 let deferred = Promise.defer(); |
|
122 |
|
123 let result = { message: null, error: null }; |
|
124 function got(which, value) { |
|
125 result[which] = value; |
|
126 if (result.message != null && result.error != null) { |
|
127 deferred.resolve(result); |
|
128 } |
|
129 } |
|
130 |
|
131 manager.addEventListener("failed", function onfailed(event) { |
|
132 manager.removeEventListener("failed", onfailed); |
|
133 got("message", event.message); |
|
134 }); |
|
135 |
|
136 let request = manager.sendMMS(aMmsParameters, aSendParameters); |
|
137 request.onsuccess = function(event) { |
|
138 deferred.reject(); |
|
139 }; |
|
140 request.onerror = function(event) { |
|
141 got("error", event.target.error); |
|
142 } |
|
143 |
|
144 return deferred.promise; |
|
145 } |
|
146 |
|
147 /** |
|
148 * Retrieve messages from database. |
|
149 * |
|
150 * Fulfill params: |
|
151 * messages -- an array of {Sms,Mms}Message instances. |
|
152 * |
|
153 * Reject params: |
|
154 * event -- a DOMEvent |
|
155 * |
|
156 * @param aFilter an optional MozSmsFilter instance. |
|
157 * @param aReverse a boolean value indicating whether the order of the messages |
|
158 * should be reversed. |
|
159 * |
|
160 * @return A deferred promise. |
|
161 */ |
|
162 function getMessages(aFilter, aReverse) { |
|
163 let deferred = Promise.defer(); |
|
164 |
|
165 if (!aFilter) { |
|
166 aFilter = new MozSmsFilter; |
|
167 } |
|
168 let messages = []; |
|
169 let cursor = manager.getMessages(aFilter, aReverse || false); |
|
170 cursor.onsuccess = function(aEvent) { |
|
171 if (cursor.result) { |
|
172 messages.push(cursor.result); |
|
173 cursor.continue(); |
|
174 return; |
|
175 } |
|
176 |
|
177 deferred.resolve(messages); |
|
178 }; |
|
179 cursor.onerror = deferred.reject.bind(deferred); |
|
180 |
|
181 return deferred.promise; |
|
182 } |
|
183 |
|
184 /** |
|
185 * Retrieve all messages from database. |
|
186 * |
|
187 * Fulfill params: |
|
188 * messages -- an array of {Sms,Mms}Message instances. |
|
189 * |
|
190 * Reject params: |
|
191 * event -- a DOMEvent |
|
192 * |
|
193 * @return A deferred promise. |
|
194 */ |
|
195 function getAllMessages() { |
|
196 return getMessages(null, false); |
|
197 } |
|
198 |
|
199 /** |
|
200 * Retrieve all threads from database. |
|
201 * |
|
202 * Fulfill params: |
|
203 * threads -- an array of MozMobileMessageThread instances. |
|
204 * |
|
205 * Reject params: |
|
206 * event -- a DOMEvent |
|
207 * |
|
208 * @return A deferred promise. |
|
209 */ |
|
210 function getAllThreads() { |
|
211 let deferred = Promise.defer(); |
|
212 |
|
213 let threads = []; |
|
214 let cursor = manager.getThreads(); |
|
215 cursor.onsuccess = function(aEvent) { |
|
216 if (cursor.result) { |
|
217 threads.push(cursor.result); |
|
218 cursor.continue(); |
|
219 return; |
|
220 } |
|
221 |
|
222 deferred.resolve(threads); |
|
223 }; |
|
224 cursor.onerror = deferred.reject.bind(deferred); |
|
225 |
|
226 return deferred.promise; |
|
227 } |
|
228 |
|
229 /** |
|
230 * Retrieve a single specified thread from database. |
|
231 * |
|
232 * Fulfill params: |
|
233 * thread -- a MozMobileMessageThread instance. |
|
234 * |
|
235 * Reject params: |
|
236 * event -- a DOMEvent if an error occurs in the retrieving process, or |
|
237 * undefined if there's no such thread. |
|
238 * |
|
239 * @aThreadId a numeric value identifying the target thread. |
|
240 * |
|
241 * @return A deferred promise. |
|
242 */ |
|
243 function getThreadById(aThreadId) { |
|
244 return getAllThreads() |
|
245 .then(function(aThreads) { |
|
246 for (let thread of aThreads) { |
|
247 if (thread.id === aThreadId) { |
|
248 return thread; |
|
249 } |
|
250 } |
|
251 throw undefined; |
|
252 }); |
|
253 } |
|
254 |
|
255 /** |
|
256 * Delete messages specified from database. |
|
257 * |
|
258 * Fulfill params: |
|
259 * result -- an array of boolean values indicating whether delesion was |
|
260 * actually performed on the message record with corresponding id. |
|
261 * |
|
262 * Reject params: |
|
263 * event -- a DOMEvent. |
|
264 * |
|
265 * @aMessageId an array of numeric values identifying the target messages. |
|
266 * |
|
267 * @return An empty array if nothing to be deleted; otherwise, a deferred promise. |
|
268 */ |
|
269 function deleteMessagesById(aMessageIds) { |
|
270 if (!aMessageIds.length) { |
|
271 ok(true, "no message to be deleted"); |
|
272 return []; |
|
273 } |
|
274 |
|
275 let deferred = Promise.defer(); |
|
276 |
|
277 let request = manager.delete(aMessageIds); |
|
278 request.onsuccess = function(event) { |
|
279 deferred.resolve(event.target.result); |
|
280 }; |
|
281 request.onerror = deferred.reject.bind(deferred); |
|
282 |
|
283 return deferred.promise; |
|
284 } |
|
285 |
|
286 /** |
|
287 * Delete messages specified from database. |
|
288 * |
|
289 * Fulfill params: |
|
290 * result -- an array of boolean values indicating whether delesion was |
|
291 * actually performed on the message record with corresponding id. |
|
292 * |
|
293 * Reject params: |
|
294 * event -- a DOMEvent. |
|
295 * |
|
296 * @aMessages an array of {Sms,Mms}Message instances. |
|
297 * |
|
298 * @return A deferred promise. |
|
299 */ |
|
300 function deleteMessages(aMessages) { |
|
301 let ids = messagesToIds(aMessages); |
|
302 return deleteMessagesById(ids); |
|
303 } |
|
304 |
|
305 /** |
|
306 * Delete all messages from database. |
|
307 * |
|
308 * Fulfill params: |
|
309 * ids -- an array of numeric values identifying those deleted |
|
310 * {Sms,Mms}Messages. |
|
311 * |
|
312 * Reject params: |
|
313 * event -- a DOMEvent. |
|
314 * |
|
315 * @return A deferred promise. |
|
316 */ |
|
317 function deleteAllMessages() { |
|
318 return getAllMessages().then(deleteMessages); |
|
319 } |
|
320 |
|
321 let pendingEmulatorCmdCount = 0; |
|
322 |
|
323 /** |
|
324 * Send emulator command with safe guard. |
|
325 * |
|
326 * We should only call |finish()| after all emulator command transactions |
|
327 * end, so here comes with the pending counter. Resolve when the emulator |
|
328 * gives positive response, and reject otherwise. |
|
329 * |
|
330 * Fulfill params: |
|
331 * result -- an array of emulator response lines. |
|
332 * |
|
333 * Reject params: |
|
334 * result -- an array of emulator response lines. |
|
335 * |
|
336 * @return A deferred promise. |
|
337 */ |
|
338 function runEmulatorCmdSafe(aCommand) { |
|
339 let deferred = Promise.defer(); |
|
340 |
|
341 ++pendingEmulatorCmdCount; |
|
342 runEmulatorCmd(aCommand, function(aResult) { |
|
343 --pendingEmulatorCmdCount; |
|
344 |
|
345 ok(true, "Emulator response: " + JSON.stringify(aResult)); |
|
346 if (Array.isArray(aResult) && aResult[0] === "OK") { |
|
347 deferred.resolve(aResult); |
|
348 } else { |
|
349 deferred.reject(aResult); |
|
350 } |
|
351 }); |
|
352 |
|
353 return deferred.promise; |
|
354 } |
|
355 |
|
356 /** |
|
357 * Send simple text SMS to emulator. |
|
358 * |
|
359 * Fulfill params: |
|
360 * result -- an array of emulator response lines. |
|
361 * |
|
362 * Reject params: |
|
363 * result -- an array of emulator response lines. |
|
364 * |
|
365 * @return A deferred promise. |
|
366 */ |
|
367 function sendTextSmsToEmulator(aFrom, aText) { |
|
368 let command = "sms send " + aFrom + " " + aText; |
|
369 return runEmulatorCmdSafe(command); |
|
370 } |
|
371 |
|
372 /** |
|
373 * Send raw SMS TPDU to emulator. |
|
374 * |
|
375 * @param: aPdu |
|
376 * A hex string representing the whole SMS T-PDU. |
|
377 * |
|
378 * Fulfill params: |
|
379 * result -- an array of emulator response lines. |
|
380 * |
|
381 * Reject params: |
|
382 * result -- an array of emulator response lines. |
|
383 * |
|
384 * @return A deferred promise. |
|
385 */ |
|
386 function sendRawSmsToEmulator(aPdu) { |
|
387 let command = "sms pdu " + aPdu; |
|
388 return runEmulatorCmdSafe(command); |
|
389 } |
|
390 |
|
391 /** |
|
392 * Send multiple raw SMS TPDU to emulator and wait |
|
393 * |
|
394 * @param: aPdus |
|
395 * A array of hex strings. Each represents a SMS T-PDU. |
|
396 * |
|
397 * Fulfill params: |
|
398 * result -- array of resolved Promise, where |
|
399 * result[0].message representing the received message. |
|
400 * result[1-n] represents the response of sent emulator command. |
|
401 * |
|
402 * Reject params: |
|
403 * result -- an array of emulator response lines. |
|
404 * |
|
405 * @return A deferred promise. |
|
406 */ |
|
407 function sendMultipleRawSmsToEmulatorAndWait(aPdus) { |
|
408 let promises = []; |
|
409 |
|
410 promises.push(waitForManagerEvent("received")); |
|
411 for (let pdu of aPdus) { |
|
412 promises.push(sendRawSmsToEmulator(pdu)); |
|
413 } |
|
414 |
|
415 return Promise.all(promises); |
|
416 } |
|
417 |
|
418 /** |
|
419 * Create a new array of id attribute of input messages. |
|
420 * |
|
421 * @param aMessages an array of {Sms,Mms}Message instances. |
|
422 * |
|
423 * @return an array of numeric values. |
|
424 */ |
|
425 function messagesToIds(aMessages) { |
|
426 let ids = []; |
|
427 for (let message of aMessages) { |
|
428 ids.push(message.id); |
|
429 } |
|
430 return ids; |
|
431 } |
|
432 |
|
433 /** |
|
434 * Flush permission settings and call |finish()|. |
|
435 */ |
|
436 function cleanUp() { |
|
437 waitFor(function() { |
|
438 SpecialPowers.flushPermissions(function() { |
|
439 // Use ok here so that we have at least one test run. |
|
440 ok(true, "permissions flushed"); |
|
441 |
|
442 finish(); |
|
443 }); |
|
444 }, function() { |
|
445 return pendingEmulatorCmdCount === 0; |
|
446 }); |
|
447 } |
|
448 |
|
449 /** |
|
450 * Basic test routine helper for mobile message tests. |
|
451 * |
|
452 * This helper does nothing but clean-ups. |
|
453 * |
|
454 * @param aTestCaseMain |
|
455 * A function that takes no parameter. |
|
456 */ |
|
457 function startTestBase(aTestCaseMain) { |
|
458 Promise.resolve() |
|
459 .then(aTestCaseMain) |
|
460 .then(cleanUp, function() { |
|
461 ok(false, 'promise rejects during test.'); |
|
462 cleanUp(); |
|
463 }); |
|
464 } |
|
465 |
|
466 /** |
|
467 * Common test routine helper for mobile message tests. |
|
468 * |
|
469 * This function ensures global |manager| variable is available during the |
|
470 * process and performs clean-ups as well. |
|
471 * |
|
472 * @param aTestCaseMain |
|
473 * A function that takes no parameter. |
|
474 */ |
|
475 function startTestCommon(aTestCaseMain) { |
|
476 startTestBase(function() { |
|
477 return ensureMobileMessage() |
|
478 .then(deleteAllMessages) |
|
479 .then(aTestCaseMain) |
|
480 .then(deleteAllMessages); |
|
481 }); |
|
482 } |
|
483 |
|
484 /** |
|
485 * Helper to run the test case only needed in Multi-SIM environment. |
|
486 * |
|
487 * @param aTest |
|
488 * A function which will be invoked w/o parameter. |
|
489 * @return a Promise object. |
|
490 */ |
|
491 function runIfMultiSIM(aTest) { |
|
492 let numRIL; |
|
493 try { |
|
494 numRIL = SpecialPowers.getIntPref("ril.numRadioInterfaces"); |
|
495 } catch (ex) { |
|
496 numRIL = 1; // Pref not set. |
|
497 } |
|
498 |
|
499 if (numRIL > 1) { |
|
500 return aTest(); |
|
501 } else { |
|
502 log("Not a Multi-SIM environment. Test is skipped."); |
|
503 return Promise.resolve(); |
|
504 } |
|
505 } |