embedding/android/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 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.
    41  */
    42 class PendingIntentUID
    43 {
    44   static private int sUID = Integer.MIN_VALUE;
    46   static public int generate() { return sUID++; }
    47 }
    49 /**
    50  * The envelope class contains all information that are needed to keep track of
    51  * a sent SMS.
    52  */
    53 class Envelope
    54 {
    55   enum SubParts {
    56     SENT_PART,
    57     DELIVERED_PART
    58   }
    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.
    67    */
    68   protected int[]     mRemainingParts;
    70   /**
    71    * Whether sending/delivering is currently failing.
    72    * @note The array has much slots as SubParts items.
    73    */
    74   protected boolean[] mFailing;
    76   /**
    77    * Error type (only for sent).
    78    */
    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;
    94     }
    95   }
    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!?");
   103     }
   104   }
   106   public boolean arePartsRemaining(Envelope.SubParts aType) {
   107     return mRemainingParts[aType.ordinal()] != 0;
   108   }
   110   public void markAsFailed(Envelope.SubParts aType) {
   111     mFailing[aType.ordinal()] = true;
   112   }
   114   public boolean isFailing(Envelope.SubParts aType) {
   115     return mFailing[aType.ordinal()];
   116   }
   118   public int getMessageId() {
   119     return mMessageId;
   120   }
   122   public void setMessageId(int aMessageId) {
   123     mMessageId = aMessageId;
   124   }
   126   public long getMessageTimestamp() {
   127     return mMessageTimestamp;
   128   }
   130   public void setMessageTimestamp(long aMessageTimestamp) {
   131     mMessageTimestamp = aMessageTimestamp;
   132   }
   134   public int getError() {
   135     return mError;
   136   }
   138   public void setError(int aError) {
   139     mError = aError;
   140   }
   141 }
   143 /**
   144  * Postman class is a singleton that manages Envelope instances.
   145  */
   146 class Postman
   147 {
   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;
   158   }
   160   public int createEnvelope(int aParts) {
   161     /*
   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.
   164      */
   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;
   171       }
   172     }
   174     mEnvelopes.add(new Envelope(size, aParts));
   175     return size;
   176   }
   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;
   182     }
   184     Envelope envelope = mEnvelopes.get(aId);
   185     if (envelope == null) {
   186       Log.e("GeckoSmsManager", "Trying to get an empty Envelope!");
   187     }
   189     return envelope;
   190   }
   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;
   196     }
   198     if (mEnvelopes.set(aId, null) == null) {
   199       Log.e("GeckoSmsManager", "Trying to destroy an empty Envelope!");
   200     }
   201   }
   202 }
   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;
   211   }
   213   public boolean execute(Runnable r) {
   214     return mHandler.post(r);
   215   }
   217   public void run() {
   218     Looper.prepare();
   220     mHandler = new Handler();
   222     Looper.loop();
   223   }
   224 }
   226 class MessagesListManager
   227 {
   228   private static final MessagesListManager sInstance = new MessagesListManager();
   230   public static MessagesListManager getInstance() {
   231     return sInstance;
   232   }
   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;
   243       }
   244     }
   246     mCursors.add(aCursor);
   247     return size;
   248   }
   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;
   254     }
   256     Cursor cursor = mCursors.get(aId);
   257     if (cursor == null) {
   258       Log.e("GeckoSmsManager", "Trying to get an empty list!");
   259     }
   261     return cursor;
   262   }
   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;
   268     }
   270     Cursor cursor = mCursors.set(aId, null);
   271     if (cursor == null) {
   272       Log.e("GeckoSmsManager", "Trying to destroy an empty list!");
   273       return;
   274     }
   276     cursor.close();
   277   }
   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();
   284       }
   285     }
   287     mCursors.clear();
   288   }
   289 }
   291 public class GeckoSmsManager
   292   extends BroadcastReceiver
   293   implements ISmsManager
   294 {
   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";
   299   /*
   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.
   303    */
   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;
   325   /*
   326    * Keep the following state codes in syng with |DeliveryState| in:
   327    * dom/mobilemessage/src/Types.h
   328    */
   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;
   337   /*
   338    * Keep the following status codes in sync with |DeliveryStatus| in:
   339    * dom/mobilemessage/src/Types.h
   340    */
   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;
   346   /*
   347    * android.provider.Telephony.Sms.STATUS_*. Duplicated because they're not
   348    * part of Android public API.
   349    */
   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;
   355   /*
   356    * Keep the following values in sync with |MessageClass| in:
   357    * dom/mobilemessage/src/Types.h
   358    */
   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();
   369   }
   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);
   378   }
   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;
   393       }
   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());
   404       }
   406       return;
   407     }
   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;
   418       }
   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;
   427       }
   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;
   448         }
   449         envelope.markAsFailed(part);
   450         Log.i("GeckoSmsManager", "SMS part sending failed!");
   451       }
   453       if (envelope.arePartsRemaining(part)) {
   454         return;
   455       }
   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!");
   469         }
   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!");
   492         }
   493       }
   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);
   499       }
   501       return;
   502     }
   503   }
   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);
   526         /*
   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.
   532          *
   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().
   536          */
   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)
   567           );
   569           deliveredPendingIntents.add(
   570             PendingIntent.getBroadcast(GeckoApp.mAppContext,
   571                                        PendingIntentUID.generate(), deliveredIntent,
   572                                        PendingIntent.FLAG_CANCEL_CURRENT)
   573           );
   574         }
   576         sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents,
   577                                     deliveredPendingIntents);
   578       }
   579     } catch (Exception e) {
   580       Log.e("GeckoSmsManager", "Failed to send an SMS: ", e);
   582       if (envelopeId != Postman.kUnknownEnvelopeId) {
   583         Postman.getInstance().destroyEnvelope(envelopeId);
   584       }
   586       GeckoAppShell.notifySmsSendFailed(kUnknownError, aRequestId);
   587     }
   588   }
   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();
   608       }
   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;
   617     }
   618   }
   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;
   628       }
   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();
   641           }
   643           if (cursor.getCount() != 1) {
   644             throw new TooManyResultsException();
   645           }
   647           cursor.moveToFirst();
   649           if (cursor.getInt(cursor.getColumnIndex("_id")) != mMessageId) {
   650             throw new UnmatchingIdException();
   651           }
   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();
   666           }
   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();
   693           }
   694         }
   695       }
   696     }
   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);
   701     }
   702   }
   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;
   712       }
   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();
   724           }
   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);
   733         }
   734       }
   735     }
   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);
   740     }
   741   }
   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;
   761       }
   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);
   774           }
   776           if (mEndDate != 0) {
   777             restrictions.add("date <= " + mEndDate);
   778           }
   780           if (mNumbersCount > 0) {
   781             String numberRestriction = "address IN ('" + mNumbers[0] + "'";
   783             for (int i=1; i<mNumbersCount; ++i) {
   784               numberRestriction += ", '" + mNumbers[i] + "'";
   785             }
   786             numberRestriction += ")";
   788             restrictions.add(numberRestriction);
   789           }
   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();
   799           }
   801           String restrictionText = restrictions.size() > 0 ? restrictions.get(0) : "";
   803           for (int i=1; i<restrictions.size(); ++i) {
   804             restrictionText += " AND " + restrictions.get(i);
   805           }
   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;
   814           }
   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();
   831           }
   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();
   854           }
   855         }
   856       }
   857     }
   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);
   862     }
   863   }
   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;
   873       }
   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;
   884           }
   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();
   899           }
   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);
   914         }
   915       }
   916     }
   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);
   921     }
   922   }
   924   public void clearMessageList(int aListId) {
   925     MessagesListManager.getInstance().remove(aListId);
   926   }
   928   public void stop() {
   929     GeckoApp.mAppContext.unregisterReceiver(this);
   930   }
   932   public void shutdown() {
   933     SmsIOThread.getInstance().interrupt();
   934     MessagesListManager.getInstance().clear();
   935   }
   937   private int getGeckoDeliveryStatus(int aDeliveryStatus) {
   938     if (aDeliveryStatus == kInternalDeliveryStatusNone) {
   939       return kDeliveryStatusNotApplicable;
   940     }
   941     if (aDeliveryStatus >= kInternalDeliveryStatusFailed) {
   942       return kDeliveryStatusError;
   943     }
   944     if (aDeliveryStatus >= kInternalDeliveryStatusPending) {
   945       return kDeliveryStatusPending;
   946     }
   947     return kDeliveryStatusSuccess;
   948   }
   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;
   962     }
   963   }
   965   class IdTooHighException extends Exception {
   966     private static final long serialVersionUID = 395697882128640L;
   967   }
   969   class InvalidTypeException extends Exception {
   970     private static final long serialVersionUID = 23359904803795434L;
   971   }
   973   class NotFoundException extends Exception {
   974     private static final long serialVersionUID = 266226999371957426L;
   975   }
   977   class TooManyResultsException extends Exception {
   978     private static final long serialVersionUID = 48899777673841920L;
   979   }
   981   class UnexpectedDeliveryStateException extends Exception {
   982     private static final long serialVersionUID = 5044567998961920L;
   983   }
   985   class UnmatchingIdException extends Exception {
   986     private static final long serialVersionUID = 1935649715512128L;
   987   }
   988 }

mercurial