Bumping manifests a=b2g-bump
[gecko.git] / embedding / android / GeckoSmsManager.java
blob562d1bcb1bad42de4e650bff5b56979cf9b7fbba
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 java.util.ArrayList;
9 import java.util.Iterator;
11 import android.util.Log;
13 import android.app.PendingIntent;
14 import android.app.Activity;
16 import android.database.Cursor;
18 import android.content.Intent;
19 import android.content.IntentFilter;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.ContentResolver;
23 import android.content.ContentValues;
24 import android.content.ContentUris;
26 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Looper;
32 import android.telephony.SmsManager;
33 import android.telephony.SmsMessage;
35 import static android.telephony.SmsMessage.MessageClass;
37 /**
38 * This class is returning unique ids for PendingIntent requestCode attribute.
39 * There are only |Integer.MAX_VALUE - Integer.MIN_VALUE| unique IDs available,
40 * and they wrap around.
42 class PendingIntentUID
44 static private int sUID = Integer.MIN_VALUE;
46 static public int generate() { return sUID++; }
49 /**
50 * The envelope class contains all information that are needed to keep track of
51 * a sent SMS.
53 class Envelope
55 enum SubParts {
56 SENT_PART,
57 DELIVERED_PART
60 protected int mId;
61 protected int mMessageId;
62 protected long mMessageTimestamp;
64 /**
65 * Number of sent/delivered remaining parts.
66 * @note The array has much slots as SubParts items.
68 protected int[] mRemainingParts;
70 /**
71 * Whether sending/delivering is currently failing.
72 * @note The array has much slots as SubParts items.
74 protected boolean[] mFailing;
76 /**
77 * Error type (only for sent).
79 protected int mError;
81 public Envelope(int aId, int aParts) {
82 mId = aId;
83 mMessageId = -1;
84 mMessageTimestamp = 0;
85 mError = GeckoSmsManager.kNoError;
87 int size = Envelope.SubParts.values().length;
88 mRemainingParts = new int[size];
89 mFailing = new boolean[size];
91 for (int i=0; i<size; ++i) {
92 mRemainingParts[i] = aParts;
93 mFailing[i] = false;
97 public void decreaseRemainingParts(Envelope.SubParts aType) {
98 --mRemainingParts[aType.ordinal()];
100 if (mRemainingParts[SubParts.SENT_PART.ordinal()] >
101 mRemainingParts[SubParts.DELIVERED_PART.ordinal()]) {
102 Log.e("GeckoSmsManager", "Delivered more parts than we sent!?");
106 public boolean arePartsRemaining(Envelope.SubParts aType) {
107 return mRemainingParts[aType.ordinal()] != 0;
110 public void markAsFailed(Envelope.SubParts aType) {
111 mFailing[aType.ordinal()] = true;
114 public boolean isFailing(Envelope.SubParts aType) {
115 return mFailing[aType.ordinal()];
118 public int getMessageId() {
119 return mMessageId;
122 public void setMessageId(int aMessageId) {
123 mMessageId = aMessageId;
126 public long getMessageTimestamp() {
127 return mMessageTimestamp;
130 public void setMessageTimestamp(long aMessageTimestamp) {
131 mMessageTimestamp = aMessageTimestamp;
134 public int getError() {
135 return mError;
138 public void setError(int aError) {
139 mError = aError;
144 * Postman class is a singleton that manages Envelope instances.
146 class Postman
148 public static final int kUnknownEnvelopeId = -1;
150 private static final Postman sInstance = new Postman();
152 private ArrayList<Envelope> mEnvelopes = new ArrayList<Envelope>(1);
154 private Postman() {}
156 public static Postman getInstance() {
157 return sInstance;
160 public int createEnvelope(int aParts) {
162 * We are going to create the envelope in the first empty slot in the array
163 * list. If there is no empty slot, we create a new one.
165 int size = mEnvelopes.size();
167 for (int i=0; i<size; ++i) {
168 if (mEnvelopes.get(i) == null) {
169 mEnvelopes.set(i, new Envelope(i, aParts));
170 return i;
174 mEnvelopes.add(new Envelope(size, aParts));
175 return size;
178 public Envelope getEnvelope(int aId) {
179 if (aId < 0 || mEnvelopes.size() <= aId) {
180 Log.e("GeckoSmsManager", "Trying to get an unknown Envelope!");
181 return null;
184 Envelope envelope = mEnvelopes.get(aId);
185 if (envelope == null) {
186 Log.e("GeckoSmsManager", "Trying to get an empty Envelope!");
189 return envelope;
192 public void destroyEnvelope(int aId) {
193 if (aId < 0 || mEnvelopes.size() <= aId) {
194 Log.e("GeckoSmsManager", "Trying to destroy an unknown Envelope!");
195 return;
198 if (mEnvelopes.set(aId, null) == null) {
199 Log.e("GeckoSmsManager", "Trying to destroy an empty Envelope!");
204 class SmsIOThread extends Thread {
205 private final static SmsIOThread sInstance = new SmsIOThread();
207 private Handler mHandler;
209 public static SmsIOThread getInstance() {
210 return sInstance;
213 public boolean execute(Runnable r) {
214 return mHandler.post(r);
217 public void run() {
218 Looper.prepare();
220 mHandler = new Handler();
222 Looper.loop();
226 class MessagesListManager
228 private static final MessagesListManager sInstance = new MessagesListManager();
230 public static MessagesListManager getInstance() {
231 return sInstance;
234 private ArrayList<Cursor> mCursors = new ArrayList<Cursor>(0);
236 public int add(Cursor aCursor) {
237 int size = mCursors.size();
239 for (int i=0; i<size; ++i) {
240 if (mCursors.get(i) == null) {
241 mCursors.set(i, aCursor);
242 return i;
246 mCursors.add(aCursor);
247 return size;
250 public Cursor get(int aId) {
251 if (aId < 0 || mCursors.size() <= aId) {
252 Log.e("GeckoSmsManager", "Trying to get an unknown list!");
253 return null;
256 Cursor cursor = mCursors.get(aId);
257 if (cursor == null) {
258 Log.e("GeckoSmsManager", "Trying to get an empty list!");
261 return cursor;
264 public void remove(int aId) {
265 if (aId < 0 || mCursors.size() <= aId) {
266 Log.e("GeckoSmsManager", "Trying to destroy an unknown list!");
267 return;
270 Cursor cursor = mCursors.set(aId, null);
271 if (cursor == null) {
272 Log.e("GeckoSmsManager", "Trying to destroy an empty list!");
273 return;
276 cursor.close();
279 public void clear() {
280 for (int i=0; i<mCursors.size(); ++i) {
281 Cursor c = mCursors.get(i);
282 if (c != null) {
283 c.close();
287 mCursors.clear();
291 public class GeckoSmsManager
292 extends BroadcastReceiver
293 implements ISmsManager
295 public final static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
296 public final static String ACTION_SMS_SENT = "org.mozilla.gecko.SMS_SENT";
297 public final static String ACTION_SMS_DELIVERED = "org.mozilla.gecko.SMS_DELIVERED";
300 * Make sure that the following error codes are in sync with the ones
301 * defined in dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl. They are owned
302 * owned by the interface.
304 public final static int kNoError = 0;
305 public final static int kNoSignalError = 1;
306 public final static int kNotFoundError = 2;
307 public final static int kUnknownError = 3;
308 public final static int kInternalError = 4;
309 public final static int kNoSimCardError = 5;
310 public final static int kRadioDisabledError = 6;
311 public final static int kInvalidAddressError = 7;
312 public final static int kFdnCheckError = 8;
313 public final static int kNonActiveSimCardError = 9;
314 public final static int kStorageFullError = 10;
315 public final static int kSimNotMatchedError = 11;
317 private final static int kMaxMessageSize = 160;
319 private final static Uri kSmsContentUri = Uri.parse("content://sms");
320 private final static Uri kSmsSentContentUri = Uri.parse("content://sms/sent");
322 private final static int kSmsTypeInbox = 1;
323 private final static int kSmsTypeSentbox = 2;
326 * Keep the following state codes in syng with |DeliveryState| in:
327 * dom/mobilemessage/Types.h
329 private final static int kDeliveryStateSent = 0;
330 private final static int kDeliveryStateReceived = 1;
331 private final static int kDeliveryStateSending = 2;
332 private final static int kDeliveryStateError = 3;
333 private final static int kDeliveryStateUnknown = 4;
334 private final static int kDeliveryStateNotDownloaded = 5;
335 private final static int kDeliveryStateEndGuard = 6;
338 * Keep the following status codes in sync with |DeliveryStatus| in:
339 * dom/mobilemessage/Types.h
341 private final static int kDeliveryStatusNotApplicable = 0;
342 private final static int kDeliveryStatusSuccess = 1;
343 private final static int kDeliveryStatusPending = 2;
344 private final static int kDeliveryStatusError = 3;
347 * android.provider.Telephony.Sms.STATUS_*. Duplicated because they're not
348 * part of Android public API.
350 private final static int kInternalDeliveryStatusNone = -1;
351 private final static int kInternalDeliveryStatusComplete = 0;
352 private final static int kInternalDeliveryStatusPending = 32;
353 private final static int kInternalDeliveryStatusFailed = 64;
356 * Keep the following values in sync with |MessageClass| in:
357 * dom/mobilemessage/Types.h
359 private final static int kMessageClassNormal = 0;
360 private final static int kMessageClassClass0 = 1;
361 private final static int kMessageClassClass1 = 2;
362 private final static int kMessageClassClass2 = 3;
363 private final static int kMessageClassClass3 = 4;
365 private final static String[] kRequiredMessageRows = new String[] { "_id", "address", "body", "date", "type", "status" };
367 public GeckoSmsManager() {
368 SmsIOThread.getInstance().start();
371 public void start() {
372 IntentFilter smsFilter = new IntentFilter();
373 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_RECEIVED);
374 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_SENT);
375 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_DELIVERED);
377 GeckoApp.mAppContext.registerReceiver(this, smsFilter);
380 @Override
381 public void onReceive(Context context, Intent intent) {
382 if (intent.getAction().equals(ACTION_SMS_RECEIVED)) {
383 // TODO: Try to find the receiver number to be able to populate
384 // SmsMessage.receiver.
385 // TODO: Get the id and the date from the stock app saved message.
386 // Using the stock app saved message require us to wait for it to
387 // be saved which can lead to race conditions.
389 Bundle bundle = intent.getExtras();
391 if (bundle == null) {
392 return;
395 Object[] pdus = (Object[]) bundle.get("pdus");
397 for (int i=0; i<pdus.length; ++i) {
398 SmsMessage msg = SmsMessage.createFromPdu((byte[])pdus[i]);
400 GeckoAppShell.notifySmsReceived(msg.getDisplayOriginatingAddress(),
401 msg.getDisplayMessageBody(),
402 getGeckoMessageClass(msg.getMessageClass()),
403 System.currentTimeMillis());
406 return;
409 if (intent.getAction().equals(ACTION_SMS_SENT) ||
410 intent.getAction().equals(ACTION_SMS_DELIVERED)) {
411 Bundle bundle = intent.getExtras();
413 if (bundle == null || !bundle.containsKey("envelopeId") ||
414 !bundle.containsKey("number") || !bundle.containsKey("message") ||
415 !bundle.containsKey("requestId")) {
416 Log.e("GeckoSmsManager", "Got an invalid ACTION_SMS_SENT/ACTION_SMS_DELIVERED!");
417 return;
420 int envelopeId = bundle.getInt("envelopeId");
421 Postman postman = Postman.getInstance();
423 Envelope envelope = postman.getEnvelope(envelopeId);
424 if (envelope == null) {
425 Log.e("GeckoSmsManager", "Got an invalid envelope id (or Envelope has been destroyed)!");
426 return;
429 Envelope.SubParts part = intent.getAction().equals(ACTION_SMS_SENT)
430 ? Envelope.SubParts.SENT_PART
431 : Envelope.SubParts.DELIVERED_PART;
432 envelope.decreaseRemainingParts(part);
435 if (getResultCode() != Activity.RESULT_OK) {
436 switch (getResultCode()) {
437 case SmsManager.RESULT_ERROR_NULL_PDU:
438 envelope.setError(kInternalError);
439 break;
440 case SmsManager.RESULT_ERROR_NO_SERVICE:
441 case SmsManager.RESULT_ERROR_RADIO_OFF:
442 envelope.setError(kNoSignalError);
443 break;
444 case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
445 default:
446 envelope.setError(kUnknownError);
447 break;
449 envelope.markAsFailed(part);
450 Log.i("GeckoSmsManager", "SMS part sending failed!");
453 if (envelope.arePartsRemaining(part)) {
454 return;
457 if (envelope.isFailing(part)) {
458 if (part == Envelope.SubParts.SENT_PART) {
459 GeckoAppShell.notifySmsSendFailed(envelope.getError(),
460 bundle.getInt("requestId"));
461 Log.i("GeckoSmsManager", "SMS sending failed!");
462 } else {
463 GeckoAppShell.notifySmsDelivery(envelope.getMessageId(),
464 kDeliveryStatusError,
465 bundle.getString("number"),
466 bundle.getString("message"),
467 envelope.getMessageTimestamp());
468 Log.i("GeckoSmsManager", "SMS delivery failed!");
470 } else {
471 if (part == Envelope.SubParts.SENT_PART) {
472 String number = bundle.getString("number");
473 String message = bundle.getString("message");
474 long timestamp = System.currentTimeMillis();
476 int id = saveSentMessage(number, message, timestamp);
478 GeckoAppShell.notifySmsSent(id, number, message, timestamp,
479 bundle.getInt("requestId"));
481 envelope.setMessageId(id);
482 envelope.setMessageTimestamp(timestamp);
484 Log.i("GeckoSmsManager", "SMS sending was successfull!");
485 } else {
486 GeckoAppShell.notifySmsDelivery(envelope.getMessageId(),
487 kDeliveryStatusSuccess,
488 bundle.getString("number"),
489 bundle.getString("message"),
490 envelope.getMessageTimestamp());
491 Log.i("GeckoSmsManager", "SMS succesfully delivered!");
495 // Destroy the envelope object only if the SMS has been sent and delivered.
496 if (!envelope.arePartsRemaining(Envelope.SubParts.SENT_PART) &&
497 !envelope.arePartsRemaining(Envelope.SubParts.DELIVERED_PART)) {
498 postman.destroyEnvelope(envelopeId);
501 return;
505 public void send(String aNumber, String aMessage, int aRequestId) {
506 int envelopeId = Postman.kUnknownEnvelopeId;
508 try {
509 SmsManager sm = SmsManager.getDefault();
511 Intent sentIntent = new Intent(ACTION_SMS_SENT);
512 Intent deliveredIntent = new Intent(ACTION_SMS_DELIVERED);
514 Bundle bundle = new Bundle();
515 bundle.putString("number", aNumber);
516 bundle.putString("message", aMessage);
517 bundle.putInt("requestId", aRequestId);
519 if (aMessage.length() <= kMaxMessageSize) {
520 envelopeId = Postman.getInstance().createEnvelope(1);
521 bundle.putInt("envelopeId", envelopeId);
523 sentIntent.putExtras(bundle);
524 deliveredIntent.putExtras(bundle);
527 * There are a few things to know about getBroadcast and pending intents:
528 * - the pending intents are in a shared pool maintained by the system;
529 * - each pending intent is identified by a token;
530 * - when a new pending intent is created, if it has the same token as
531 * another intent in the pool, one of them has to be removed.
533 * To prevent having a hard time because of this situation, we give a
534 * unique id to all pending intents we are creating. This unique id is
535 * generated by GetPendingIntentUID().
537 PendingIntent sentPendingIntent =
538 PendingIntent.getBroadcast(GeckoApp.mAppContext,
539 PendingIntentUID.generate(), sentIntent,
540 PendingIntent.FLAG_CANCEL_CURRENT);
542 PendingIntent deliveredPendingIntent =
543 PendingIntent.getBroadcast(GeckoApp.mAppContext,
544 PendingIntentUID.generate(), deliveredIntent,
545 PendingIntent.FLAG_CANCEL_CURRENT);
547 sm.sendTextMessage(aNumber, "", aMessage,
548 sentPendingIntent, deliveredPendingIntent);
549 } else {
550 ArrayList<String> parts = sm.divideMessage(aMessage);
551 envelopeId = Postman.getInstance().createEnvelope(parts.size());
552 bundle.putInt("envelopeId", envelopeId);
554 sentIntent.putExtras(bundle);
555 deliveredIntent.putExtras(bundle);
557 ArrayList<PendingIntent> sentPendingIntents =
558 new ArrayList<PendingIntent>(parts.size());
559 ArrayList<PendingIntent> deliveredPendingIntents =
560 new ArrayList<PendingIntent>(parts.size());
562 for (int i=0; i<parts.size(); ++i) {
563 sentPendingIntents.add(
564 PendingIntent.getBroadcast(GeckoApp.mAppContext,
565 PendingIntentUID.generate(), sentIntent,
566 PendingIntent.FLAG_CANCEL_CURRENT)
569 deliveredPendingIntents.add(
570 PendingIntent.getBroadcast(GeckoApp.mAppContext,
571 PendingIntentUID.generate(), deliveredIntent,
572 PendingIntent.FLAG_CANCEL_CURRENT)
576 sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents,
577 deliveredPendingIntents);
579 } catch (Exception e) {
580 Log.e("GeckoSmsManager", "Failed to send an SMS: ", e);
582 if (envelopeId != Postman.kUnknownEnvelopeId) {
583 Postman.getInstance().destroyEnvelope(envelopeId);
586 GeckoAppShell.notifySmsSendFailed(kUnknownError, aRequestId);
590 public int saveSentMessage(String aRecipient, String aBody, long aDate) {
591 try {
592 ContentValues values = new ContentValues();
593 values.put("address", aRecipient);
594 values.put("body", aBody);
595 values.put("date", aDate);
596 // Always 'PENDING' because we always request status report.
597 values.put("status", kInternalDeliveryStatusPending);
599 ContentResolver cr = GeckoApp.mAppContext.getContentResolver();
600 Uri uri = cr.insert(kSmsSentContentUri, values);
602 long id = ContentUris.parseId(uri);
604 // The DOM API takes a 32bits unsigned int for the id. It's unlikely that
605 // we happen to need more than that but it doesn't cost to check.
606 if (id > Integer.MAX_VALUE) {
607 throw new IdTooHighException();
610 return (int)id;
611 } catch (IdTooHighException e) {
612 Log.e("GeckoSmsManager", "The id we received is higher than the higher allowed value.");
613 return -1;
614 } catch (Exception e) {
615 Log.e("GeckoSmsManager", "Something went wrong when trying to write a sent message: " + e);
616 return -1;
620 public void getMessage(int aMessageId, int aRequestId) {
621 class GetMessageRunnable implements Runnable {
622 private int mMessageId;
623 private int mRequestId;
625 GetMessageRunnable(int aMessageId, int aRequestId) {
626 mMessageId = aMessageId;
627 mRequestId = aRequestId;
630 @Override
631 public void run() {
632 Cursor cursor = null;
634 try {
635 ContentResolver cr = GeckoApp.mAppContext.getContentResolver();
636 Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId);
638 cursor = cr.query(message, kRequiredMessageRows, null, null, null);
639 if (cursor == null || cursor.getCount() == 0) {
640 throw new NotFoundException();
643 if (cursor.getCount() != 1) {
644 throw new TooManyResultsException();
647 cursor.moveToFirst();
649 if (cursor.getInt(cursor.getColumnIndex("_id")) != mMessageId) {
650 throw new UnmatchingIdException();
653 int type = cursor.getInt(cursor.getColumnIndex("type"));
654 int deliveryStatus;
655 String sender = "";
656 String receiver = "";
658 if (type == kSmsTypeInbox) {
659 deliveryStatus = kDeliveryStatusSuccess;
660 sender = cursor.getString(cursor.getColumnIndex("address"));
661 } else if (type == kSmsTypeSentbox) {
662 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
663 receiver = cursor.getString(cursor.getColumnIndex("address"));
664 } else {
665 throw new InvalidTypeException();
668 GeckoAppShell.notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")),
669 deliveryStatus,
670 receiver, sender,
671 cursor.getString(cursor.getColumnIndex("body")),
672 cursor.getLong(cursor.getColumnIndex("date")),
673 mRequestId);
674 } catch (NotFoundException e) {
675 Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found");
676 GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId);
677 } catch (UnmatchingIdException e) {
678 Log.e("GeckoSmsManager", "Requested message id (" + mMessageId +
679 ") is different from the one we got.");
680 GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId);
681 } catch (TooManyResultsException e) {
682 Log.e("GeckoSmsManager", "Get too many results for id " + mMessageId);
683 GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId);
684 } catch (InvalidTypeException e) {
685 Log.i("GeckoSmsManager", "Message has an invalid type, we ignore it.");
686 GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId);
687 } catch (Exception e) {
688 Log.e("GeckoSmsManager", "Error while trying to get message: " + e);
689 GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId);
690 } finally {
691 if (cursor != null) {
692 cursor.close();
698 if (!SmsIOThread.getInstance().execute(new GetMessageRunnable(aMessageId, aRequestId))) {
699 Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
700 GeckoAppShell.notifyGetSmsFailed(kUnknownError, aRequestId);
704 public void deleteMessage(int aMessageId, int aRequestId) {
705 class DeleteMessageRunnable implements Runnable {
706 private int mMessageId;
707 private int mRequestId;
709 DeleteMessageRunnable(int aMessageId, int aRequestId) {
710 mMessageId = aMessageId;
711 mRequestId = aRequestId;
714 @Override
715 public void run() {
716 try {
717 ContentResolver cr = GeckoApp.mAppContext.getContentResolver();
718 Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId);
720 int count = cr.delete(message, null, null);
722 if (count > 1) {
723 throw new TooManyResultsException();
726 GeckoAppShell.notifySmsDeleted(count == 1, mRequestId);
727 } catch (TooManyResultsException e) {
728 Log.e("GeckoSmsManager", "Delete more than one message? " + e);
729 GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId);
730 } catch (Exception e) {
731 Log.e("GeckoSmsManager", "Error while trying to delete a message: " + e);
732 GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId);
737 if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId))) {
738 Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
739 GeckoAppShell.notifySmsDeleteFailed(kUnknownError, aRequestId);
743 public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
744 class CreateMessageListRunnable implements Runnable {
745 private long mStartDate;
746 private long mEndDate;
747 private String[] mNumbers;
748 private int mNumbersCount;
749 private int mDeliveryState;
750 private boolean mReverse;
751 private int mRequestId;
753 CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
754 mStartDate = aStartDate;
755 mEndDate = aEndDate;
756 mNumbers = aNumbers;
757 mNumbersCount = aNumbersCount;
758 mDeliveryState = aDeliveryState;
759 mReverse = aReverse;
760 mRequestId = aRequestId;
763 @Override
764 public void run() {
765 Cursor cursor = null;
766 boolean closeCursor = true;
768 try {
769 // TODO: should use the |selectionArgs| argument in |ContentResolver.query()|.
770 ArrayList<String> restrictions = new ArrayList<String>();
772 if (mStartDate != 0) {
773 restrictions.add("date >= " + mStartDate);
776 if (mEndDate != 0) {
777 restrictions.add("date <= " + mEndDate);
780 if (mNumbersCount > 0) {
781 String numberRestriction = "address IN ('" + mNumbers[0] + "'";
783 for (int i=1; i<mNumbersCount; ++i) {
784 numberRestriction += ", '" + mNumbers[i] + "'";
786 numberRestriction += ")";
788 restrictions.add(numberRestriction);
791 if (mDeliveryState == kDeliveryStateUnknown) {
792 restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')");
793 } else if (mDeliveryState == kDeliveryStateSent) {
794 restrictions.add("type = " + kSmsTypeSentbox);
795 } else if (mDeliveryState == kDeliveryStateReceived) {
796 restrictions.add("type = " + kSmsTypeInbox);
797 } else {
798 throw new UnexpectedDeliveryStateException();
801 String restrictionText = restrictions.size() > 0 ? restrictions.get(0) : "";
803 for (int i=1; i<restrictions.size(); ++i) {
804 restrictionText += " AND " + restrictions.get(i);
807 ContentResolver cr = GeckoApp.mAppContext.getContentResolver();
808 cursor = cr.query(kSmsContentUri, kRequiredMessageRows, restrictionText, null,
809 mReverse ? "date DESC" : "date ASC");
811 if (cursor.getCount() == 0) {
812 GeckoAppShell.notifyNoMessageInList(mRequestId);
813 return;
816 cursor.moveToFirst();
818 int type = cursor.getInt(cursor.getColumnIndex("type"));
819 int deliveryStatus;
820 String sender = "";
821 String receiver = "";
823 if (type == kSmsTypeInbox) {
824 deliveryStatus = kDeliveryStatusSuccess;
825 sender = cursor.getString(cursor.getColumnIndex("address"));
826 } else if (type == kSmsTypeSentbox) {
827 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
828 receiver = cursor.getString(cursor.getColumnIndex("address"));
829 } else {
830 throw new UnexpectedDeliveryStateException();
833 int listId = MessagesListManager.getInstance().add(cursor);
834 closeCursor = false;
835 GeckoAppShell.notifyListCreated(listId,
836 cursor.getInt(cursor.getColumnIndex("_id")),
837 deliveryStatus,
838 receiver, sender,
839 cursor.getString(cursor.getColumnIndex("body")),
840 cursor.getLong(cursor.getColumnIndex("date")),
841 mRequestId);
842 } catch (UnexpectedDeliveryStateException e) {
843 Log.e("GeckoSmsManager", "Unexcepted delivery state type: " + e);
844 GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId);
845 } catch (Exception e) {
846 Log.e("GeckoSmsManager", "Error while trying to create a message list cursor: " + e);
847 GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId);
848 } finally {
849 // Close the cursor if MessagesListManager isn't taking care of it.
850 // We could also just check if it is in the MessagesListManager list but
851 // that would be less efficient.
852 if (cursor != null && closeCursor) {
853 cursor.close();
859 if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId))) {
860 Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread");
861 GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, aRequestId);
865 public void getNextMessageInList(int aListId, int aRequestId) {
866 class GetNextMessageInListRunnable implements Runnable {
867 private int mListId;
868 private int mRequestId;
870 GetNextMessageInListRunnable(int aListId, int aRequestId) {
871 mListId = aListId;
872 mRequestId = aRequestId;
875 @Override
876 public void run() {
877 try {
878 Cursor cursor = MessagesListManager.getInstance().get(mListId);
880 if (!cursor.moveToNext()) {
881 MessagesListManager.getInstance().remove(mListId);
882 GeckoAppShell.notifyNoMessageInList(mRequestId);
883 return;
886 int type = cursor.getInt(cursor.getColumnIndex("type"));
887 int deliveryStatus;
888 String sender = "";
889 String receiver = "";
891 if (type == kSmsTypeInbox) {
892 deliveryStatus = kDeliveryStatusSuccess;
893 sender = cursor.getString(cursor.getColumnIndex("address"));
894 } else if (type == kSmsTypeSentbox) {
895 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
896 receiver = cursor.getString(cursor.getColumnIndex("address"));
897 } else {
898 throw new UnexpectedDeliveryStateException();
901 int listId = MessagesListManager.getInstance().add(cursor);
902 GeckoAppShell.notifyGotNextMessage(cursor.getInt(cursor.getColumnIndex("_id")),
903 deliveryStatus,
904 receiver, sender,
905 cursor.getString(cursor.getColumnIndex("body")),
906 cursor.getLong(cursor.getColumnIndex("date")),
907 mRequestId);
908 } catch (UnexpectedDeliveryStateException e) {
909 Log.e("GeckoSmsManager", "Unexcepted delivery state type: " + e);
910 GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId);
911 } catch (Exception e) {
912 Log.e("GeckoSmsManager", "Error while trying to get the next message of a list: " + e);
913 GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId);
918 if (!SmsIOThread.getInstance().execute(new GetNextMessageInListRunnable(aListId, aRequestId))) {
919 Log.e("GeckoSmsManager", "Failed to add GetNextMessageInListRunnable to the SmsIOThread");
920 GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, aRequestId);
924 public void clearMessageList(int aListId) {
925 MessagesListManager.getInstance().remove(aListId);
928 public void stop() {
929 GeckoApp.mAppContext.unregisterReceiver(this);
932 public void shutdown() {
933 SmsIOThread.getInstance().interrupt();
934 MessagesListManager.getInstance().clear();
937 private int getGeckoDeliveryStatus(int aDeliveryStatus) {
938 if (aDeliveryStatus == kInternalDeliveryStatusNone) {
939 return kDeliveryStatusNotApplicable;
941 if (aDeliveryStatus >= kInternalDeliveryStatusFailed) {
942 return kDeliveryStatusError;
944 if (aDeliveryStatus >= kInternalDeliveryStatusPending) {
945 return kDeliveryStatusPending;
947 return kDeliveryStatusSuccess;
950 private int getGeckoMessageClass(MessageClass aMessageClass) {
951 switch (aMessageClass) {
952 case UNKNOWN:
953 return kMessageClassNormal;
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;
965 class IdTooHighException extends Exception {
966 private static final long serialVersionUID = 395697882128640L;
969 class InvalidTypeException extends Exception {
970 private static final long serialVersionUID = 23359904803795434L;
973 class NotFoundException extends Exception {
974 private static final long serialVersionUID = 266226999371957426L;
977 class TooManyResultsException extends Exception {
978 private static final long serialVersionUID = 48899777673841920L;
981 class UnexpectedDeliveryStateException extends Exception {
982 private static final long serialVersionUID = 5044567998961920L;
985 class UnmatchingIdException extends Exception {
986 private static final long serialVersionUID = 1935649715512128L;