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
;
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
++; }
50 * The envelope class contains all information that are needed to keep track of
61 protected int mMessageId
;
62 protected long mMessageTimestamp
;
65 * Number of sent/delivered remaining parts.
66 * @note The array has much slots as SubParts items.
68 protected int[] mRemainingParts
;
71 * Whether sending/delivering is currently failing.
72 * @note The array has much slots as SubParts items.
74 protected boolean[] mFailing
;
77 * Error type (only for sent).
81 public Envelope(int aId
, int aParts
) {
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
;
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() {
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() {
138 public void setError(int aError
) {
144 * Postman class is a singleton that manages Envelope instances.
148 public static final int kUnknownEnvelopeId
= -1;
150 private static final Postman sInstance
= new Postman();
152 private ArrayList
<Envelope
> mEnvelopes
= new ArrayList
<Envelope
>(1);
156 public static Postman
getInstance() {
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
));
174 mEnvelopes
.add(new Envelope(size
, aParts
));
178 public Envelope
getEnvelope(int aId
) {
179 if (aId
< 0 || mEnvelopes
.size() <= aId
) {
180 Log
.e("GeckoSmsManager", "Trying to get an unknown Envelope!");
184 Envelope envelope
= mEnvelopes
.get(aId
);
185 if (envelope
== null) {
186 Log
.e("GeckoSmsManager", "Trying to get an empty Envelope!");
192 public void destroyEnvelope(int aId
) {
193 if (aId
< 0 || mEnvelopes
.size() <= aId
) {
194 Log
.e("GeckoSmsManager", "Trying to destroy an unknown Envelope!");
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() {
213 public boolean execute(Runnable r
) {
214 return mHandler
.post(r
);
220 mHandler
= new Handler();
226 class MessagesListManager
228 private static final MessagesListManager sInstance
= new MessagesListManager();
230 public static MessagesListManager
getInstance() {
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
);
246 mCursors
.add(aCursor
);
250 public Cursor
get(int aId
) {
251 if (aId
< 0 || mCursors
.size() <= aId
) {
252 Log
.e("GeckoSmsManager", "Trying to get an unknown list!");
256 Cursor cursor
= mCursors
.get(aId
);
257 if (cursor
== null) {
258 Log
.e("GeckoSmsManager", "Trying to get an empty list!");
264 public void remove(int aId
) {
265 if (aId
< 0 || mCursors
.size() <= aId
) {
266 Log
.e("GeckoSmsManager", "Trying to destroy an unknown list!");
270 Cursor cursor
= mCursors
.set(aId
, null);
271 if (cursor
== null) {
272 Log
.e("GeckoSmsManager", "Trying to destroy an empty list!");
279 public void clear() {
280 for (int i
=0; i
<mCursors
.size(); ++i
) {
281 Cursor c
= mCursors
.get(i
);
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
);
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) {
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());
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!");
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)!");
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
);
440 case SmsManager
.RESULT_ERROR_NO_SERVICE
:
441 case SmsManager
.RESULT_ERROR_RADIO_OFF
:
442 envelope
.setError(kNoSignalError
);
444 case SmsManager
.RESULT_ERROR_GENERIC_FAILURE
:
446 envelope
.setError(kUnknownError
);
449 envelope
.markAsFailed(part
);
450 Log
.i("GeckoSmsManager", "SMS part sending failed!");
453 if (envelope
.arePartsRemaining(part
)) {
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!");
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!");
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!");
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
);
505 public void send(String aNumber
, String aMessage
, int aRequestId
) {
506 int envelopeId
= Postman
.kUnknownEnvelopeId
;
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
);
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
) {
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();
611 } catch (IdTooHighException e
) {
612 Log
.e("GeckoSmsManager", "The id we received is higher than the higher allowed value.");
614 } catch (Exception e
) {
615 Log
.e("GeckoSmsManager", "Something went wrong when trying to write a sent message: " + e
);
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
;
632 Cursor cursor
= null;
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"));
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"));
665 throw new InvalidTypeException();
668 GeckoAppShell
.notifyGetSms(cursor
.getInt(cursor
.getColumnIndex("_id")),
671 cursor
.getString(cursor
.getColumnIndex("body")),
672 cursor
.getLong(cursor
.getColumnIndex("date")),
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
);
691 if (cursor
!= null) {
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
;
717 ContentResolver cr
= GeckoApp
.mAppContext
.getContentResolver();
718 Uri message
= ContentUris
.withAppendedId(kSmsContentUri
, mMessageId
);
720 int count
= cr
.delete(message
, null, null);
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
;
757 mNumbersCount
= aNumbersCount
;
758 mDeliveryState
= aDeliveryState
;
760 mRequestId
= aRequestId
;
765 Cursor cursor
= null;
766 boolean closeCursor
= true;
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
);
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
);
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
);
816 cursor
.moveToFirst();
818 int type
= cursor
.getInt(cursor
.getColumnIndex("type"));
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"));
830 throw new UnexpectedDeliveryStateException();
833 int listId
= MessagesListManager
.getInstance().add(cursor
);
835 GeckoAppShell
.notifyListCreated(listId
,
836 cursor
.getInt(cursor
.getColumnIndex("_id")),
839 cursor
.getString(cursor
.getColumnIndex("body")),
840 cursor
.getLong(cursor
.getColumnIndex("date")),
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
);
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
) {
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
{
868 private int mRequestId
;
870 GetNextMessageInListRunnable(int aListId
, int aRequestId
) {
872 mRequestId
= aRequestId
;
878 Cursor cursor
= MessagesListManager
.getInstance().get(mListId
);
880 if (!cursor
.moveToNext()) {
881 MessagesListManager
.getInstance().remove(mListId
);
882 GeckoAppShell
.notifyNoMessageInList(mRequestId
);
886 int type
= cursor
.getInt(cursor
.getColumnIndex("type"));
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"));
898 throw new UnexpectedDeliveryStateException();
901 int listId
= MessagesListManager
.getInstance().add(cursor
);
902 GeckoAppShell
.notifyGotNextMessage(cursor
.getInt(cursor
.getColumnIndex("_id")),
905 cursor
.getString(cursor
.getColumnIndex("body")),
906 cursor
.getLong(cursor
.getColumnIndex("date")),
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
);
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
) {
953 return kMessageClassNormal
;
955 return kMessageClassClass0
;
957 return kMessageClassClass1
;
959 return kMessageClassClass2
;
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;