mobile/android/base/GeckoSmsManager.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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);

mercurial