Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 package org.mozilla.gecko;
8 import android.app.Activity;
9 import android.app.PendingIntent;
10 import android.content.BroadcastReceiver;
11 import android.content.ContentResolver;
12 import android.content.ContentUris;
13 import android.content.ContentValues;
14 import android.content.Context;
15 import android.content.Intent;
16 import android.content.IntentFilter;
17 import android.database.Cursor;
18 import android.net.Uri;
19 import android.os.Bundle;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.telephony.SmsManager;
23 import android.telephony.SmsMessage;
24 import android.util.Log;
26 import static android.telephony.SmsMessage.MessageClass;
28 import java.util.ArrayList;
30 /**
31 * This class is returning unique ids for PendingIntent requestCode attribute.
32 * There are only |Integer.MAX_VALUE - Integer.MIN_VALUE| unique IDs available,
33 * and they wrap around.
34 */
35 class PendingIntentUID
36 {
37 static private int sUID = Integer.MIN_VALUE;
39 static public int generate() { return sUID++; }
40 }
42 /**
43 * The envelope class contains all information that are needed to keep track of
44 * a sent SMS.
45 */
46 class Envelope
47 {
48 enum SubParts {
49 SENT_PART,
50 DELIVERED_PART
51 }
53 protected int mId;
54 protected int mMessageId;
55 protected long mMessageTimestamp;
57 /**
58 * Number of sent/delivered remaining parts.
59 * @note The array has much slots as SubParts items.
60 */
61 protected int[] mRemainingParts;
63 /**
64 * Whether sending/delivering is currently failing.
65 * @note The array has much slots as SubParts items.
66 */
67 protected boolean[] mFailing;
69 /**
70 * Error type (only for sent).
71 */
72 protected int mError;
74 public Envelope(int aId, int aParts) {
75 mId = aId;
76 mMessageId = -1;
77 mMessageTimestamp = 0;
78 mError = GeckoSmsManager.kNoError;
80 int size = Envelope.SubParts.values().length;
81 mRemainingParts = new int[size];
82 mFailing = new boolean[size];
84 for (int i=0; i<size; ++i) {
85 mRemainingParts[i] = aParts;
86 mFailing[i] = false;
87 }
88 }
90 public void decreaseRemainingParts(Envelope.SubParts aType) {
91 --mRemainingParts[aType.ordinal()];
93 if (mRemainingParts[SubParts.SENT_PART.ordinal()] >
94 mRemainingParts[SubParts.DELIVERED_PART.ordinal()]) {
95 Log.e("GeckoSmsManager", "Delivered more parts than we sent!?");
96 }
97 }
99 public boolean arePartsRemaining(Envelope.SubParts aType) {
100 return mRemainingParts[aType.ordinal()] != 0;
101 }
103 public void markAsFailed(Envelope.SubParts aType) {
104 mFailing[aType.ordinal()] = true;
105 }
107 public boolean isFailing(Envelope.SubParts aType) {
108 return mFailing[aType.ordinal()];
109 }
111 public int getMessageId() {
112 return mMessageId;
113 }
115 public void setMessageId(int aMessageId) {
116 mMessageId = aMessageId;
117 }
119 public long getMessageTimestamp() {
120 return mMessageTimestamp;
121 }
123 public void setMessageTimestamp(long aMessageTimestamp) {
124 mMessageTimestamp = aMessageTimestamp;
125 }
127 public int getError() {
128 return mError;
129 }
131 public void setError(int aError) {
132 mError = aError;
133 }
134 }
136 /**
137 * Postman class is a singleton that manages Envelope instances.
138 */
139 class Postman
140 {
141 public static final int kUnknownEnvelopeId = -1;
143 private static final Postman sInstance = new Postman();
145 private ArrayList<Envelope> mEnvelopes = new ArrayList<Envelope>(1);
147 private Postman() {}
149 public static Postman getInstance() {
150 return sInstance;
151 }
153 public int createEnvelope(int aParts) {
154 /*
155 * We are going to create the envelope in the first empty slot in the array
156 * list. If there is no empty slot, we create a new one.
157 */
158 int size = mEnvelopes.size();
160 for (int i=0; i<size; ++i) {
161 if (mEnvelopes.get(i) == null) {
162 mEnvelopes.set(i, new Envelope(i, aParts));
163 return i;
164 }
165 }
167 mEnvelopes.add(new Envelope(size, aParts));
168 return size;
169 }
171 public Envelope getEnvelope(int aId) {
172 if (aId < 0 || mEnvelopes.size() <= aId) {
173 Log.e("GeckoSmsManager", "Trying to get an unknown Envelope!");
174 return null;
175 }
177 Envelope envelope = mEnvelopes.get(aId);
178 if (envelope == null) {
179 Log.e("GeckoSmsManager", "Trying to get an empty Envelope!");
180 }
182 return envelope;
183 }
185 public void destroyEnvelope(int aId) {
186 if (aId < 0 || mEnvelopes.size() <= aId) {
187 Log.e("GeckoSmsManager", "Trying to destroy an unknown Envelope!");
188 return;
189 }
191 if (mEnvelopes.set(aId, null) == null) {
192 Log.e("GeckoSmsManager", "Trying to destroy an empty Envelope!");
193 }
194 }
195 }
197 class SmsIOThread extends Thread {
198 private final static SmsIOThread sInstance = new SmsIOThread();
200 private Handler mHandler;
202 public static SmsIOThread getInstance() {
203 return sInstance;
204 }
206 public boolean execute(Runnable r) {
207 return mHandler.post(r);
208 }
210 @Override
211 public void run() {
212 Looper.prepare();
214 mHandler = new Handler();
216 Looper.loop();
217 }
218 }
220 class MessagesListManager
221 {
222 private static final MessagesListManager sInstance = new MessagesListManager();
224 public static MessagesListManager getInstance() {
225 return sInstance;
226 }
228 private ArrayList<Cursor> mCursors = new ArrayList<Cursor>(0);
230 public int add(Cursor aCursor) {
231 int size = mCursors.size();
233 for (int i=0; i<size; ++i) {
234 if (mCursors.get(i) == null) {
235 mCursors.set(i, aCursor);
236 return i;
237 }
238 }
240 mCursors.add(aCursor);
241 return size;
242 }
244 public Cursor get(int aId) {
245 if (aId < 0 || mCursors.size() <= aId) {
246 Log.e("GeckoSmsManager", "Trying to get an unknown list!");
247 return null;
248 }
250 Cursor cursor = mCursors.get(aId);
251 if (cursor == null) {
252 Log.e("GeckoSmsManager", "Trying to get an empty list!");
253 }
255 return cursor;
256 }
258 public void remove(int aId) {
259 if (aId < 0 || mCursors.size() <= aId) {
260 Log.e("GeckoSmsManager", "Trying to destroy an unknown list!");
261 return;
262 }
264 Cursor cursor = mCursors.set(aId, null);
265 if (cursor == null) {
266 Log.e("GeckoSmsManager", "Trying to destroy an empty list!");
267 return;
268 }
270 cursor.close();
271 }
273 public void clear() {
274 for (int i=0; i<mCursors.size(); ++i) {
275 Cursor c = mCursors.get(i);
276 if (c != null) {
277 c.close();
278 }
279 }
281 mCursors.clear();
282 }
283 }
285 public class GeckoSmsManager
286 extends BroadcastReceiver
287 implements ISmsManager
288 {
289 public final static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
290 public final static String ACTION_SMS_SENT = "org.mozilla.gecko.SMS_SENT";
291 public final static String ACTION_SMS_DELIVERED = "org.mozilla.gecko.SMS_DELIVERED";
293 /*
294 * Make sure that the following error codes are in sync with |ErrorType| in:
295 * dom/mobilemessage/src/Types.h
296 * The error code are owned by the DOM.
297 */
298 public final static int kNoError = 0;
299 public final static int kNoSignalError = 1;
300 public final static int kNotFoundError = 2;
301 public final static int kUnknownError = 3;
302 public final static int kInternalError = 4;
303 public final static int kNoSimCardError = 5;
304 public final static int kRadioDisabledError = 6;
305 public final static int kInvalidAddressError = 7;
306 public final static int kFdnCheckError = 8;
307 public final static int kNonActiveSimCardError = 9;
308 public final static int kStorageFullError = 10;
309 public final static int kSimNotMatchedError = 11;
311 private final static int kMaxMessageSize = 160;
313 private final static Uri kSmsContentUri = Uri.parse("content://sms");
314 private final static Uri kSmsSentContentUri = Uri.parse("content://sms/sent");
316 private final static int kSmsTypeInbox = 1;
317 private final static int kSmsTypeSentbox = 2;
319 /*
320 * Keep the following state codes in syng with |DeliveryState| in:
321 * dom/mobilemessage/src/Types.h
322 */
323 private final static int kDeliveryStateSent = 0;
324 private final static int kDeliveryStateReceived = 1;
325 private final static int kDeliveryStateSending = 2;
326 private final static int kDeliveryStateError = 3;
327 private final static int kDeliveryStateUnknown = 4;
328 private final static int kDeliveryStateNotDownloaded = 5;
329 private final static int kDeliveryStateEndGuard = 6;
331 /*
332 * Keep the following status codes in sync with |DeliveryStatus| in:
333 * dom/mobilemessage/src/Types.h
334 */
335 private final static int kDeliveryStatusNotApplicable = 0;
336 private final static int kDeliveryStatusSuccess = 1;
337 private final static int kDeliveryStatusPending = 2;
338 private final static int kDeliveryStatusError = 3;
340 /*
341 * android.provider.Telephony.Sms.STATUS_*. Duplicated because they're not
342 * part of Android public API.
343 */
344 private final static int kInternalDeliveryStatusNone = -1;
345 private final static int kInternalDeliveryStatusComplete = 0;
346 private final static int kInternalDeliveryStatusPending = 32;
347 private final static int kInternalDeliveryStatusFailed = 64;
349 /*
350 * Keep the following values in sync with |MessageClass| in:
351 * dom/mobilemessage/src/Types.h
352 */
353 private final static int kMessageClassNormal = 0;
354 private final static int kMessageClassClass0 = 1;
355 private final static int kMessageClassClass1 = 2;
356 private final static int kMessageClassClass2 = 3;
357 private final static int kMessageClassClass3 = 4;
359 private final static String[] kRequiredMessageRows = new String[] { "_id", "address", "body", "date", "type", "status" };
361 public GeckoSmsManager() {
362 SmsIOThread.getInstance().start();
363 }
365 @Override
366 public void start() {
367 IntentFilter smsFilter = new IntentFilter();
368 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_RECEIVED);
369 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_SENT);
370 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_DELIVERED);
372 GeckoAppShell.getContext().registerReceiver(this, smsFilter);
373 }
375 @Override
376 public void onReceive(Context context, Intent intent) {
377 if (intent.getAction().equals(ACTION_SMS_RECEIVED)) {
378 // TODO: Try to find the receiver number to be able to populate
379 // SmsMessage.receiver.
380 // TODO: Get the id and the date from the stock app saved message.
381 // Using the stock app saved message require us to wait for it to
382 // be saved which can lead to race conditions.
384 Bundle bundle = intent.getExtras();
386 if (bundle == null) {
387 return;
388 }
390 Object[] pdus = (Object[]) bundle.get("pdus");
392 for (int i=0; i<pdus.length; ++i) {
393 SmsMessage msg = SmsMessage.createFromPdu((byte[])pdus[i]);
395 notifySmsReceived(msg.getDisplayOriginatingAddress(),
396 msg.getDisplayMessageBody(),
397 getGeckoMessageClass(msg.getMessageClass()),
398 System.currentTimeMillis());
399 }
401 return;
402 }
404 if (intent.getAction().equals(ACTION_SMS_SENT) ||
405 intent.getAction().equals(ACTION_SMS_DELIVERED)) {
406 Bundle bundle = intent.getExtras();
408 if (bundle == null || !bundle.containsKey("envelopeId") ||
409 !bundle.containsKey("number") || !bundle.containsKey("message") ||
410 !bundle.containsKey("requestId")) {
411 Log.e("GeckoSmsManager", "Got an invalid ACTION_SMS_SENT/ACTION_SMS_DELIVERED!");
412 return;
413 }
415 int envelopeId = bundle.getInt("envelopeId");
416 Postman postman = Postman.getInstance();
418 Envelope envelope = postman.getEnvelope(envelopeId);
419 if (envelope == null) {
420 Log.e("GeckoSmsManager", "Got an invalid envelope id (or Envelope has been destroyed)!");
421 return;
422 }
424 Envelope.SubParts part = intent.getAction().equals(ACTION_SMS_SENT)
425 ? Envelope.SubParts.SENT_PART
426 : Envelope.SubParts.DELIVERED_PART;
427 envelope.decreaseRemainingParts(part);
430 if (getResultCode() != Activity.RESULT_OK) {
431 switch (getResultCode()) {
432 case SmsManager.RESULT_ERROR_NULL_PDU:
433 envelope.setError(kInternalError);
434 break;
435 case SmsManager.RESULT_ERROR_NO_SERVICE:
436 case SmsManager.RESULT_ERROR_RADIO_OFF:
437 envelope.setError(kNoSignalError);
438 break;
439 case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
440 default:
441 envelope.setError(kUnknownError);
442 break;
443 }
444 envelope.markAsFailed(part);
445 Log.i("GeckoSmsManager", "SMS part sending failed!");
446 }
448 if (envelope.arePartsRemaining(part)) {
449 return;
450 }
452 if (envelope.isFailing(part)) {
453 if (part == Envelope.SubParts.SENT_PART) {
454 notifySmsSendFailed(envelope.getError(), bundle.getInt("requestId"));
455 Log.i("GeckoSmsManager", "SMS sending failed!");
456 } else {
457 notifySmsDelivery(envelope.getMessageId(),
458 kDeliveryStatusError,
459 bundle.getString("number"),
460 bundle.getString("message"),
461 envelope.getMessageTimestamp());
462 Log.i("GeckoSmsManager", "SMS delivery failed!");
463 }
464 } else {
465 if (part == Envelope.SubParts.SENT_PART) {
466 String number = bundle.getString("number");
467 String message = bundle.getString("message");
468 long timestamp = System.currentTimeMillis();
470 int id = saveSentMessage(number, message, timestamp);
472 notifySmsSent(id, number, message, timestamp,
473 bundle.getInt("requestId"));
475 envelope.setMessageId(id);
476 envelope.setMessageTimestamp(timestamp);
478 Log.i("GeckoSmsManager", "SMS sending was successfull!");
479 } else {
480 notifySmsDelivery(envelope.getMessageId(),
481 kDeliveryStatusSuccess,
482 bundle.getString("number"),
483 bundle.getString("message"),
484 envelope.getMessageTimestamp());
485 Log.i("GeckoSmsManager", "SMS successfully delivered!");
486 }
487 }
489 // Destroy the envelope object only if the SMS has been sent and delivered.
490 if (!envelope.arePartsRemaining(Envelope.SubParts.SENT_PART) &&
491 !envelope.arePartsRemaining(Envelope.SubParts.DELIVERED_PART)) {
492 postman.destroyEnvelope(envelopeId);
493 }
495 return;
496 }
497 }
499 @Override
500 public void send(String aNumber, String aMessage, int aRequestId) {
501 int envelopeId = Postman.kUnknownEnvelopeId;
503 try {
504 SmsManager sm = SmsManager.getDefault();
506 Intent sentIntent = new Intent(ACTION_SMS_SENT);
507 Intent deliveredIntent = new Intent(ACTION_SMS_DELIVERED);
509 Bundle bundle = new Bundle();
510 bundle.putString("number", aNumber);
511 bundle.putString("message", aMessage);
512 bundle.putInt("requestId", aRequestId);
514 if (aMessage.length() <= kMaxMessageSize) {
515 envelopeId = Postman.getInstance().createEnvelope(1);
516 bundle.putInt("envelopeId", envelopeId);
518 sentIntent.putExtras(bundle);
519 deliveredIntent.putExtras(bundle);
521 /*
522 * There are a few things to know about getBroadcast and pending intents:
523 * - the pending intents are in a shared pool maintained by the system;
524 * - each pending intent is identified by a token;
525 * - when a new pending intent is created, if it has the same token as
526 * another intent in the pool, one of them has to be removed.
527 *
528 * To prevent having a hard time because of this situation, we give a
529 * unique id to all pending intents we are creating. This unique id is
530 * generated by GetPendingIntentUID().
531 */
532 PendingIntent sentPendingIntent =
533 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
534 PendingIntentUID.generate(), sentIntent,
535 PendingIntent.FLAG_CANCEL_CURRENT);
537 PendingIntent deliveredPendingIntent =
538 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
539 PendingIntentUID.generate(), deliveredIntent,
540 PendingIntent.FLAG_CANCEL_CURRENT);
542 sm.sendTextMessage(aNumber, "", aMessage,
543 sentPendingIntent, deliveredPendingIntent);
544 } else {
545 ArrayList<String> parts = sm.divideMessage(aMessage);
546 envelopeId = Postman.getInstance().createEnvelope(parts.size());
547 bundle.putInt("envelopeId", envelopeId);
549 sentIntent.putExtras(bundle);
550 deliveredIntent.putExtras(bundle);
552 ArrayList<PendingIntent> sentPendingIntents =
553 new ArrayList<PendingIntent>(parts.size());
554 ArrayList<PendingIntent> deliveredPendingIntents =
555 new ArrayList<PendingIntent>(parts.size());
557 for (int i=0; i<parts.size(); ++i) {
558 sentPendingIntents.add(
559 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
560 PendingIntentUID.generate(), sentIntent,
561 PendingIntent.FLAG_CANCEL_CURRENT)
562 );
564 deliveredPendingIntents.add(
565 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
566 PendingIntentUID.generate(), deliveredIntent,
567 PendingIntent.FLAG_CANCEL_CURRENT)
568 );
569 }
571 sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents,
572 deliveredPendingIntents);
573 }
574 } catch (Exception e) {
575 Log.e("GeckoSmsManager", "Failed to send an SMS: ", e);
577 if (envelopeId != Postman.kUnknownEnvelopeId) {
578 Postman.getInstance().destroyEnvelope(envelopeId);
579 }
581 notifySmsSendFailed(kUnknownError, aRequestId);
582 }
583 }
585 public int saveSentMessage(String aRecipient, String aBody, long aDate) {
586 try {
587 ContentValues values = new ContentValues();
588 values.put("address", aRecipient);
589 values.put("body", aBody);
590 values.put("date", aDate);
591 // Always 'PENDING' because we always request status report.
592 values.put("status", kInternalDeliveryStatusPending);
594 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
595 Uri uri = cr.insert(kSmsSentContentUri, values);
597 long id = ContentUris.parseId(uri);
599 // The DOM API takes a 32bits unsigned int for the id. It's unlikely that
600 // we happen to need more than that but it doesn't cost to check.
601 if (id > Integer.MAX_VALUE) {
602 throw new IdTooHighException();
603 }
605 return (int)id;
606 } catch (IdTooHighException e) {
607 Log.e("GeckoSmsManager", "The id we received is higher than the higher allowed value.");
608 return -1;
609 } catch (Exception e) {
610 Log.e("GeckoSmsManager", "Something went wrong when trying to write a sent message", e);
611 return -1;
612 }
613 }
615 @Override
616 public void getMessage(int aMessageId, int aRequestId) {
617 class GetMessageRunnable implements Runnable {
618 private int mMessageId;
619 private int mRequestId;
621 GetMessageRunnable(int aMessageId, int aRequestId) {
622 mMessageId = aMessageId;
623 mRequestId = aRequestId;
624 }
626 @Override
627 public void run() {
628 Cursor cursor = null;
630 try {
631 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
632 Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId);
634 cursor = cr.query(message, kRequiredMessageRows, null, null, null);
635 if (cursor == null || cursor.getCount() == 0) {
636 throw new NotFoundException();
637 }
639 if (cursor.getCount() != 1) {
640 throw new TooManyResultsException();
641 }
643 cursor.moveToFirst();
645 if (cursor.getInt(cursor.getColumnIndex("_id")) != mMessageId) {
646 throw new UnmatchingIdException();
647 }
649 int type = cursor.getInt(cursor.getColumnIndex("type"));
650 int deliveryStatus;
651 String sender = "";
652 String receiver = "";
654 if (type == kSmsTypeInbox) {
655 deliveryStatus = kDeliveryStatusSuccess;
656 sender = cursor.getString(cursor.getColumnIndex("address"));
657 } else if (type == kSmsTypeSentbox) {
658 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
659 receiver = cursor.getString(cursor.getColumnIndex("address"));
660 } else {
661 throw new InvalidTypeException();
662 }
664 notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")),
665 deliveryStatus,
666 receiver, sender,
667 cursor.getString(cursor.getColumnIndex("body")),
668 cursor.getLong(cursor.getColumnIndex("date")),
669 mRequestId);
670 } catch (NotFoundException e) {
671 Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found");
672 notifyGetSmsFailed(kNotFoundError, mRequestId);
673 } catch (UnmatchingIdException e) {
674 Log.e("GeckoSmsManager", "Requested message id (" + mMessageId +
675 ") is different from the one we got.");
676 notifyGetSmsFailed(kUnknownError, mRequestId);
677 } catch (TooManyResultsException e) {
678 Log.e("GeckoSmsManager", "Get too many results for id " + mMessageId);
679 notifyGetSmsFailed(kUnknownError, mRequestId);
680 } catch (InvalidTypeException e) {
681 Log.i("GeckoSmsManager", "Message has an invalid type, we ignore it.");
682 notifyGetSmsFailed(kNotFoundError, mRequestId);
683 } catch (Exception e) {
684 Log.e("GeckoSmsManager", "Error while trying to get message", e);
685 notifyGetSmsFailed(kUnknownError, mRequestId);
686 } finally {
687 if (cursor != null) {
688 cursor.close();
689 }
690 }
691 }
692 }
694 if (!SmsIOThread.getInstance().execute(new GetMessageRunnable(aMessageId, aRequestId))) {
695 Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
696 notifyGetSmsFailed(kUnknownError, aRequestId);
697 }
698 }
700 @Override
701 public void deleteMessage(int aMessageId, int aRequestId) {
702 class DeleteMessageRunnable implements Runnable {
703 private int mMessageId;
704 private int mRequestId;
706 DeleteMessageRunnable(int aMessageId, int aRequestId) {
707 mMessageId = aMessageId;
708 mRequestId = aRequestId;
709 }
711 @Override
712 public void run() {
713 try {
714 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
715 Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId);
717 int count = cr.delete(message, null, null);
719 if (count > 1) {
720 throw new TooManyResultsException();
721 }
723 notifySmsDeleted(count == 1, mRequestId);
724 } catch (TooManyResultsException e) {
725 Log.e("GeckoSmsManager", "Delete more than one message?", e);
726 notifySmsDeleteFailed(kUnknownError, mRequestId);
727 } catch (Exception e) {
728 Log.e("GeckoSmsManager", "Error while trying to delete a message", e);
729 notifySmsDeleteFailed(kUnknownError, mRequestId);
730 }
731 }
732 }
734 if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId))) {
735 Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
736 notifySmsDeleteFailed(kUnknownError, aRequestId);
737 }
738 }
740 @Override
741 public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
742 class CreateMessageListRunnable implements Runnable {
743 private long mStartDate;
744 private long mEndDate;
745 private String[] mNumbers;
746 private int mNumbersCount;
747 private int mDeliveryState;
748 private boolean mReverse;
749 private int mRequestId;
751 CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
752 mStartDate = aStartDate;
753 mEndDate = aEndDate;
754 mNumbers = aNumbers;
755 mNumbersCount = aNumbersCount;
756 mDeliveryState = aDeliveryState;
757 mReverse = aReverse;
758 mRequestId = aRequestId;
759 }
761 @Override
762 public void run() {
763 Cursor cursor = null;
764 boolean closeCursor = true;
766 try {
767 // TODO: should use the |selectionArgs| argument in |ContentResolver.query()|.
768 ArrayList<String> restrictions = new ArrayList<String>();
770 if (mStartDate != 0) {
771 restrictions.add("date >= " + mStartDate);
772 }
774 if (mEndDate != 0) {
775 restrictions.add("date <= " + mEndDate);
776 }
778 if (mNumbersCount > 0) {
779 String numberRestriction = "address IN ('" + mNumbers[0] + "'";
781 for (int i=1; i<mNumbersCount; ++i) {
782 numberRestriction += ", '" + mNumbers[i] + "'";
783 }
784 numberRestriction += ")";
786 restrictions.add(numberRestriction);
787 }
789 if (mDeliveryState == kDeliveryStateUnknown) {
790 restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')");
791 } else if (mDeliveryState == kDeliveryStateSent) {
792 restrictions.add("type = " + kSmsTypeSentbox);
793 } else if (mDeliveryState == kDeliveryStateReceived) {
794 restrictions.add("type = " + kSmsTypeInbox);
795 } else {
796 throw new UnexpectedDeliveryStateException();
797 }
799 String restrictionText = restrictions.size() > 0 ? restrictions.get(0) : "";
801 for (int i=1; i<restrictions.size(); ++i) {
802 restrictionText += " AND " + restrictions.get(i);
803 }
805 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
806 cursor = cr.query(kSmsContentUri, kRequiredMessageRows, restrictionText, null,
807 mReverse ? "date DESC" : "date ASC");
809 if (cursor.getCount() == 0) {
810 notifyNoMessageInList(mRequestId);
811 return;
812 }
814 cursor.moveToFirst();
816 int type = cursor.getInt(cursor.getColumnIndex("type"));
817 int deliveryStatus;
818 String sender = "";
819 String receiver = "";
821 if (type == kSmsTypeInbox) {
822 deliveryStatus = kDeliveryStatusSuccess;
823 sender = cursor.getString(cursor.getColumnIndex("address"));
824 } else if (type == kSmsTypeSentbox) {
825 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
826 receiver = cursor.getString(cursor.getColumnIndex("address"));
827 } else {
828 throw new UnexpectedDeliveryStateException();
829 }
831 int listId = MessagesListManager.getInstance().add(cursor);
832 closeCursor = false;
833 notifyListCreated(listId,
834 cursor.getInt(cursor.getColumnIndex("_id")),
835 deliveryStatus,
836 receiver, sender,
837 cursor.getString(cursor.getColumnIndex("body")),
838 cursor.getLong(cursor.getColumnIndex("date")),
839 mRequestId);
840 } catch (UnexpectedDeliveryStateException e) {
841 Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
842 notifyReadingMessageListFailed(kUnknownError, mRequestId);
843 } catch (Exception e) {
844 Log.e("GeckoSmsManager", "Error while trying to create a message list cursor", e);
845 notifyReadingMessageListFailed(kUnknownError, mRequestId);
846 } finally {
847 // Close the cursor if MessagesListManager isn't taking care of it.
848 // We could also just check if it is in the MessagesListManager list but
849 // that would be less efficient.
850 if (cursor != null && closeCursor) {
851 cursor.close();
852 }
853 }
854 }
855 }
857 if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId))) {
858 Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread");
859 notifyReadingMessageListFailed(kUnknownError, aRequestId);
860 }
861 }
863 @Override
864 public void getNextMessageInList(int aListId, int aRequestId) {
865 class GetNextMessageInListRunnable implements Runnable {
866 private int mListId;
867 private int mRequestId;
869 GetNextMessageInListRunnable(int aListId, int aRequestId) {
870 mListId = aListId;
871 mRequestId = aRequestId;
872 }
874 @Override
875 public void run() {
876 try {
877 Cursor cursor = MessagesListManager.getInstance().get(mListId);
879 if (!cursor.moveToNext()) {
880 MessagesListManager.getInstance().remove(mListId);
881 notifyNoMessageInList(mRequestId);
882 return;
883 }
885 int type = cursor.getInt(cursor.getColumnIndex("type"));
886 int deliveryStatus;
887 String sender = "";
888 String receiver = "";
890 if (type == kSmsTypeInbox) {
891 deliveryStatus = kDeliveryStatusSuccess;
892 sender = cursor.getString(cursor.getColumnIndex("address"));
893 } else if (type == kSmsTypeSentbox) {
894 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
895 receiver = cursor.getString(cursor.getColumnIndex("address"));
896 } else {
897 throw new UnexpectedDeliveryStateException();
898 }
900 int listId = MessagesListManager.getInstance().add(cursor);
901 notifyGotNextMessage(cursor.getInt(cursor.getColumnIndex("_id")),
902 deliveryStatus,
903 receiver, sender,
904 cursor.getString(cursor.getColumnIndex("body")),
905 cursor.getLong(cursor.getColumnIndex("date")),
906 mRequestId);
907 } catch (UnexpectedDeliveryStateException e) {
908 Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
909 notifyReadingMessageListFailed(kUnknownError, mRequestId);
910 } catch (Exception e) {
911 Log.e("GeckoSmsManager", "Error while trying to get the next message of a list", e);
912 notifyReadingMessageListFailed(kUnknownError, mRequestId);
913 }
914 }
915 }
917 if (!SmsIOThread.getInstance().execute(new GetNextMessageInListRunnable(aListId, aRequestId))) {
918 Log.e("GeckoSmsManager", "Failed to add GetNextMessageInListRunnable to the SmsIOThread");
919 notifyReadingMessageListFailed(kUnknownError, aRequestId);
920 }
921 }
923 @Override
924 public void clearMessageList(int aListId) {
925 MessagesListManager.getInstance().remove(aListId);
926 }
928 @Override
929 public void stop() {
930 GeckoAppShell.getContext().unregisterReceiver(this);
931 }
933 @Override
934 public void shutdown() {
935 SmsIOThread.getInstance().interrupt();
936 MessagesListManager.getInstance().clear();
937 }
939 private int getGeckoDeliveryStatus(int aDeliveryStatus) {
940 if (aDeliveryStatus == kInternalDeliveryStatusNone) {
941 return kDeliveryStatusNotApplicable;
942 }
943 if (aDeliveryStatus >= kInternalDeliveryStatusFailed) {
944 return kDeliveryStatusError;
945 }
946 if (aDeliveryStatus >= kInternalDeliveryStatusPending) {
947 return kDeliveryStatusPending;
948 }
949 return kDeliveryStatusSuccess;
950 }
952 private int getGeckoMessageClass(MessageClass aMessageClass) {
953 switch (aMessageClass) {
954 case CLASS_0:
955 return kMessageClassClass0;
956 case CLASS_1:
957 return kMessageClassClass1;
958 case CLASS_2:
959 return kMessageClassClass2;
960 case CLASS_3:
961 return kMessageClassClass3;
962 default:
963 return kMessageClassNormal;
964 }
965 }
967 class IdTooHighException extends Exception {
968 private static final long serialVersionUID = 29935575131092050L;
969 }
971 class InvalidTypeException extends Exception {
972 private static final long serialVersionUID = 47436856832535912L;
973 }
975 class NotFoundException extends Exception {
976 private static final long serialVersionUID = 1940676816633984L;
977 }
979 class TooManyResultsException extends Exception {
980 private static final long serialVersionUID = 51883196784325305L;
981 }
983 class UnexpectedDeliveryStateException extends Exception {
984 private static final long serialVersionUID = 494122763684005716L;
985 }
987 class UnmatchingIdException extends Exception {
988 private static final long serialVersionUID = 158467542575633280L;
989 }
991 private static native void notifySmsReceived(String aSender, String aBody, int aMessageClass, long aTimestamp);
992 private static native void notifySmsSent(int aId, String aReceiver, String aBody, long aTimestamp, int aRequestId);
993 private static native void notifySmsDelivery(int aId, int aDeliveryStatus, String aReceiver, String aBody, long aTimestamp);
994 private static native void notifySmsSendFailed(int aError, int aRequestId);
995 private static native void notifyGetSms(int aId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
996 private static native void notifyGetSmsFailed(int aError, int aRequestId);
997 private static native void notifySmsDeleted(boolean aDeleted, int aRequestId);
998 private static native void notifySmsDeleteFailed(int aError, int aRequestId);
999 private static native void notifyNoMessageInList(int aRequestId);
1000 private static native void notifyListCreated(int aListId, int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
1001 private static native void notifyGotNextMessage(int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
1002 private static native void notifyReadingMessageListFailed(int aError, int aRequestId);
1003 }