1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/embedding/android/GeckoSmsManager.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,988 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko; 1.10 + 1.11 +import java.util.ArrayList; 1.12 +import java.util.Iterator; 1.13 + 1.14 +import android.util.Log; 1.15 + 1.16 +import android.app.PendingIntent; 1.17 +import android.app.Activity; 1.18 + 1.19 +import android.database.Cursor; 1.20 + 1.21 +import android.content.Intent; 1.22 +import android.content.IntentFilter; 1.23 +import android.content.BroadcastReceiver; 1.24 +import android.content.Context; 1.25 +import android.content.ContentResolver; 1.26 +import android.content.ContentValues; 1.27 +import android.content.ContentUris; 1.28 + 1.29 +import android.net.Uri; 1.30 + 1.31 +import android.os.Bundle; 1.32 +import android.os.Handler; 1.33 +import android.os.Looper; 1.34 + 1.35 +import android.telephony.SmsManager; 1.36 +import android.telephony.SmsMessage; 1.37 + 1.38 +import static android.telephony.SmsMessage.MessageClass; 1.39 + 1.40 +/** 1.41 + * This class is returning unique ids for PendingIntent requestCode attribute. 1.42 + * There are only |Integer.MAX_VALUE - Integer.MIN_VALUE| unique IDs available, 1.43 + * and they wrap around. 1.44 + */ 1.45 +class PendingIntentUID 1.46 +{ 1.47 + static private int sUID = Integer.MIN_VALUE; 1.48 + 1.49 + static public int generate() { return sUID++; } 1.50 +} 1.51 + 1.52 +/** 1.53 + * The envelope class contains all information that are needed to keep track of 1.54 + * a sent SMS. 1.55 + */ 1.56 +class Envelope 1.57 +{ 1.58 + enum SubParts { 1.59 + SENT_PART, 1.60 + DELIVERED_PART 1.61 + } 1.62 + 1.63 + protected int mId; 1.64 + protected int mMessageId; 1.65 + protected long mMessageTimestamp; 1.66 + 1.67 + /** 1.68 + * Number of sent/delivered remaining parts. 1.69 + * @note The array has much slots as SubParts items. 1.70 + */ 1.71 + protected int[] mRemainingParts; 1.72 + 1.73 + /** 1.74 + * Whether sending/delivering is currently failing. 1.75 + * @note The array has much slots as SubParts items. 1.76 + */ 1.77 + protected boolean[] mFailing; 1.78 + 1.79 + /** 1.80 + * Error type (only for sent). 1.81 + */ 1.82 + protected int mError; 1.83 + 1.84 + public Envelope(int aId, int aParts) { 1.85 + mId = aId; 1.86 + mMessageId = -1; 1.87 + mMessageTimestamp = 0; 1.88 + mError = GeckoSmsManager.kNoError; 1.89 + 1.90 + int size = Envelope.SubParts.values().length; 1.91 + mRemainingParts = new int[size]; 1.92 + mFailing = new boolean[size]; 1.93 + 1.94 + for (int i=0; i<size; ++i) { 1.95 + mRemainingParts[i] = aParts; 1.96 + mFailing[i] = false; 1.97 + } 1.98 + } 1.99 + 1.100 + public void decreaseRemainingParts(Envelope.SubParts aType) { 1.101 + --mRemainingParts[aType.ordinal()]; 1.102 + 1.103 + if (mRemainingParts[SubParts.SENT_PART.ordinal()] > 1.104 + mRemainingParts[SubParts.DELIVERED_PART.ordinal()]) { 1.105 + Log.e("GeckoSmsManager", "Delivered more parts than we sent!?"); 1.106 + } 1.107 + } 1.108 + 1.109 + public boolean arePartsRemaining(Envelope.SubParts aType) { 1.110 + return mRemainingParts[aType.ordinal()] != 0; 1.111 + } 1.112 + 1.113 + public void markAsFailed(Envelope.SubParts aType) { 1.114 + mFailing[aType.ordinal()] = true; 1.115 + } 1.116 + 1.117 + public boolean isFailing(Envelope.SubParts aType) { 1.118 + return mFailing[aType.ordinal()]; 1.119 + } 1.120 + 1.121 + public int getMessageId() { 1.122 + return mMessageId; 1.123 + } 1.124 + 1.125 + public void setMessageId(int aMessageId) { 1.126 + mMessageId = aMessageId; 1.127 + } 1.128 + 1.129 + public long getMessageTimestamp() { 1.130 + return mMessageTimestamp; 1.131 + } 1.132 + 1.133 + public void setMessageTimestamp(long aMessageTimestamp) { 1.134 + mMessageTimestamp = aMessageTimestamp; 1.135 + } 1.136 + 1.137 + public int getError() { 1.138 + return mError; 1.139 + } 1.140 + 1.141 + public void setError(int aError) { 1.142 + mError = aError; 1.143 + } 1.144 +} 1.145 + 1.146 +/** 1.147 + * Postman class is a singleton that manages Envelope instances. 1.148 + */ 1.149 +class Postman 1.150 +{ 1.151 + public static final int kUnknownEnvelopeId = -1; 1.152 + 1.153 + private static final Postman sInstance = new Postman(); 1.154 + 1.155 + private ArrayList<Envelope> mEnvelopes = new ArrayList<Envelope>(1); 1.156 + 1.157 + private Postman() {} 1.158 + 1.159 + public static Postman getInstance() { 1.160 + return sInstance; 1.161 + } 1.162 + 1.163 + public int createEnvelope(int aParts) { 1.164 + /* 1.165 + * We are going to create the envelope in the first empty slot in the array 1.166 + * list. If there is no empty slot, we create a new one. 1.167 + */ 1.168 + int size = mEnvelopes.size(); 1.169 + 1.170 + for (int i=0; i<size; ++i) { 1.171 + if (mEnvelopes.get(i) == null) { 1.172 + mEnvelopes.set(i, new Envelope(i, aParts)); 1.173 + return i; 1.174 + } 1.175 + } 1.176 + 1.177 + mEnvelopes.add(new Envelope(size, aParts)); 1.178 + return size; 1.179 + } 1.180 + 1.181 + public Envelope getEnvelope(int aId) { 1.182 + if (aId < 0 || mEnvelopes.size() <= aId) { 1.183 + Log.e("GeckoSmsManager", "Trying to get an unknown Envelope!"); 1.184 + return null; 1.185 + } 1.186 + 1.187 + Envelope envelope = mEnvelopes.get(aId); 1.188 + if (envelope == null) { 1.189 + Log.e("GeckoSmsManager", "Trying to get an empty Envelope!"); 1.190 + } 1.191 + 1.192 + return envelope; 1.193 + } 1.194 + 1.195 + public void destroyEnvelope(int aId) { 1.196 + if (aId < 0 || mEnvelopes.size() <= aId) { 1.197 + Log.e("GeckoSmsManager", "Trying to destroy an unknown Envelope!"); 1.198 + return; 1.199 + } 1.200 + 1.201 + if (mEnvelopes.set(aId, null) == null) { 1.202 + Log.e("GeckoSmsManager", "Trying to destroy an empty Envelope!"); 1.203 + } 1.204 + } 1.205 +} 1.206 + 1.207 +class SmsIOThread extends Thread { 1.208 + private final static SmsIOThread sInstance = new SmsIOThread(); 1.209 + 1.210 + private Handler mHandler; 1.211 + 1.212 + public static SmsIOThread getInstance() { 1.213 + return sInstance; 1.214 + } 1.215 + 1.216 + public boolean execute(Runnable r) { 1.217 + return mHandler.post(r); 1.218 + } 1.219 + 1.220 + public void run() { 1.221 + Looper.prepare(); 1.222 + 1.223 + mHandler = new Handler(); 1.224 + 1.225 + Looper.loop(); 1.226 + } 1.227 +} 1.228 + 1.229 +class MessagesListManager 1.230 +{ 1.231 + private static final MessagesListManager sInstance = new MessagesListManager(); 1.232 + 1.233 + public static MessagesListManager getInstance() { 1.234 + return sInstance; 1.235 + } 1.236 + 1.237 + private ArrayList<Cursor> mCursors = new ArrayList<Cursor>(0); 1.238 + 1.239 + public int add(Cursor aCursor) { 1.240 + int size = mCursors.size(); 1.241 + 1.242 + for (int i=0; i<size; ++i) { 1.243 + if (mCursors.get(i) == null) { 1.244 + mCursors.set(i, aCursor); 1.245 + return i; 1.246 + } 1.247 + } 1.248 + 1.249 + mCursors.add(aCursor); 1.250 + return size; 1.251 + } 1.252 + 1.253 + public Cursor get(int aId) { 1.254 + if (aId < 0 || mCursors.size() <= aId) { 1.255 + Log.e("GeckoSmsManager", "Trying to get an unknown list!"); 1.256 + return null; 1.257 + } 1.258 + 1.259 + Cursor cursor = mCursors.get(aId); 1.260 + if (cursor == null) { 1.261 + Log.e("GeckoSmsManager", "Trying to get an empty list!"); 1.262 + } 1.263 + 1.264 + return cursor; 1.265 + } 1.266 + 1.267 + public void remove(int aId) { 1.268 + if (aId < 0 || mCursors.size() <= aId) { 1.269 + Log.e("GeckoSmsManager", "Trying to destroy an unknown list!"); 1.270 + return; 1.271 + } 1.272 + 1.273 + Cursor cursor = mCursors.set(aId, null); 1.274 + if (cursor == null) { 1.275 + Log.e("GeckoSmsManager", "Trying to destroy an empty list!"); 1.276 + return; 1.277 + } 1.278 + 1.279 + cursor.close(); 1.280 + } 1.281 + 1.282 + public void clear() { 1.283 + for (int i=0; i<mCursors.size(); ++i) { 1.284 + Cursor c = mCursors.get(i); 1.285 + if (c != null) { 1.286 + c.close(); 1.287 + } 1.288 + } 1.289 + 1.290 + mCursors.clear(); 1.291 + } 1.292 +} 1.293 + 1.294 +public class GeckoSmsManager 1.295 + extends BroadcastReceiver 1.296 + implements ISmsManager 1.297 +{ 1.298 + public final static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"; 1.299 + public final static String ACTION_SMS_SENT = "org.mozilla.gecko.SMS_SENT"; 1.300 + public final static String ACTION_SMS_DELIVERED = "org.mozilla.gecko.SMS_DELIVERED"; 1.301 + 1.302 + /* 1.303 + * Make sure that the following error codes are in sync with the ones 1.304 + * defined in dom/mobilemessage/interfaces/nsIMobileMessageCallback.idl. They are owned 1.305 + * owned by the interface. 1.306 + */ 1.307 + public final static int kNoError = 0; 1.308 + public final static int kNoSignalError = 1; 1.309 + public final static int kNotFoundError = 2; 1.310 + public final static int kUnknownError = 3; 1.311 + public final static int kInternalError = 4; 1.312 + public final static int kNoSimCardError = 5; 1.313 + public final static int kRadioDisabledError = 6; 1.314 + public final static int kInvalidAddressError = 7; 1.315 + public final static int kFdnCheckError = 8; 1.316 + public final static int kNonActiveSimCardError = 9; 1.317 + public final static int kStorageFullError = 10; 1.318 + public final static int kSimNotMatchedError = 11; 1.319 + 1.320 + private final static int kMaxMessageSize = 160; 1.321 + 1.322 + private final static Uri kSmsContentUri = Uri.parse("content://sms"); 1.323 + private final static Uri kSmsSentContentUri = Uri.parse("content://sms/sent"); 1.324 + 1.325 + private final static int kSmsTypeInbox = 1; 1.326 + private final static int kSmsTypeSentbox = 2; 1.327 + 1.328 + /* 1.329 + * Keep the following state codes in syng with |DeliveryState| in: 1.330 + * dom/mobilemessage/src/Types.h 1.331 + */ 1.332 + private final static int kDeliveryStateSent = 0; 1.333 + private final static int kDeliveryStateReceived = 1; 1.334 + private final static int kDeliveryStateSending = 2; 1.335 + private final static int kDeliveryStateError = 3; 1.336 + private final static int kDeliveryStateUnknown = 4; 1.337 + private final static int kDeliveryStateNotDownloaded = 5; 1.338 + private final static int kDeliveryStateEndGuard = 6; 1.339 + 1.340 + /* 1.341 + * Keep the following status codes in sync with |DeliveryStatus| in: 1.342 + * dom/mobilemessage/src/Types.h 1.343 + */ 1.344 + private final static int kDeliveryStatusNotApplicable = 0; 1.345 + private final static int kDeliveryStatusSuccess = 1; 1.346 + private final static int kDeliveryStatusPending = 2; 1.347 + private final static int kDeliveryStatusError = 3; 1.348 + 1.349 + /* 1.350 + * android.provider.Telephony.Sms.STATUS_*. Duplicated because they're not 1.351 + * part of Android public API. 1.352 + */ 1.353 + private final static int kInternalDeliveryStatusNone = -1; 1.354 + private final static int kInternalDeliveryStatusComplete = 0; 1.355 + private final static int kInternalDeliveryStatusPending = 32; 1.356 + private final static int kInternalDeliveryStatusFailed = 64; 1.357 + 1.358 + /* 1.359 + * Keep the following values in sync with |MessageClass| in: 1.360 + * dom/mobilemessage/src/Types.h 1.361 + */ 1.362 + private final static int kMessageClassNormal = 0; 1.363 + private final static int kMessageClassClass0 = 1; 1.364 + private final static int kMessageClassClass1 = 2; 1.365 + private final static int kMessageClassClass2 = 3; 1.366 + private final static int kMessageClassClass3 = 4; 1.367 + 1.368 + private final static String[] kRequiredMessageRows = new String[] { "_id", "address", "body", "date", "type", "status" }; 1.369 + 1.370 + public GeckoSmsManager() { 1.371 + SmsIOThread.getInstance().start(); 1.372 + } 1.373 + 1.374 + public void start() { 1.375 + IntentFilter smsFilter = new IntentFilter(); 1.376 + smsFilter.addAction(GeckoSmsManager.ACTION_SMS_RECEIVED); 1.377 + smsFilter.addAction(GeckoSmsManager.ACTION_SMS_SENT); 1.378 + smsFilter.addAction(GeckoSmsManager.ACTION_SMS_DELIVERED); 1.379 + 1.380 + GeckoApp.mAppContext.registerReceiver(this, smsFilter); 1.381 + } 1.382 + 1.383 + @Override 1.384 + public void onReceive(Context context, Intent intent) { 1.385 + if (intent.getAction().equals(ACTION_SMS_RECEIVED)) { 1.386 + // TODO: Try to find the receiver number to be able to populate 1.387 + // SmsMessage.receiver. 1.388 + // TODO: Get the id and the date from the stock app saved message. 1.389 + // Using the stock app saved message require us to wait for it to 1.390 + // be saved which can lead to race conditions. 1.391 + 1.392 + Bundle bundle = intent.getExtras(); 1.393 + 1.394 + if (bundle == null) { 1.395 + return; 1.396 + } 1.397 + 1.398 + Object[] pdus = (Object[]) bundle.get("pdus"); 1.399 + 1.400 + for (int i=0; i<pdus.length; ++i) { 1.401 + SmsMessage msg = SmsMessage.createFromPdu((byte[])pdus[i]); 1.402 + 1.403 + GeckoAppShell.notifySmsReceived(msg.getDisplayOriginatingAddress(), 1.404 + msg.getDisplayMessageBody(), 1.405 + getGeckoMessageClass(msg.getMessageClass()), 1.406 + System.currentTimeMillis()); 1.407 + } 1.408 + 1.409 + return; 1.410 + } 1.411 + 1.412 + if (intent.getAction().equals(ACTION_SMS_SENT) || 1.413 + intent.getAction().equals(ACTION_SMS_DELIVERED)) { 1.414 + Bundle bundle = intent.getExtras(); 1.415 + 1.416 + if (bundle == null || !bundle.containsKey("envelopeId") || 1.417 + !bundle.containsKey("number") || !bundle.containsKey("message") || 1.418 + !bundle.containsKey("requestId")) { 1.419 + Log.e("GeckoSmsManager", "Got an invalid ACTION_SMS_SENT/ACTION_SMS_DELIVERED!"); 1.420 + return; 1.421 + } 1.422 + 1.423 + int envelopeId = bundle.getInt("envelopeId"); 1.424 + Postman postman = Postman.getInstance(); 1.425 + 1.426 + Envelope envelope = postman.getEnvelope(envelopeId); 1.427 + if (envelope == null) { 1.428 + Log.e("GeckoSmsManager", "Got an invalid envelope id (or Envelope has been destroyed)!"); 1.429 + return; 1.430 + } 1.431 + 1.432 + Envelope.SubParts part = intent.getAction().equals(ACTION_SMS_SENT) 1.433 + ? Envelope.SubParts.SENT_PART 1.434 + : Envelope.SubParts.DELIVERED_PART; 1.435 + envelope.decreaseRemainingParts(part); 1.436 + 1.437 + 1.438 + if (getResultCode() != Activity.RESULT_OK) { 1.439 + switch (getResultCode()) { 1.440 + case SmsManager.RESULT_ERROR_NULL_PDU: 1.441 + envelope.setError(kInternalError); 1.442 + break; 1.443 + case SmsManager.RESULT_ERROR_NO_SERVICE: 1.444 + case SmsManager.RESULT_ERROR_RADIO_OFF: 1.445 + envelope.setError(kNoSignalError); 1.446 + break; 1.447 + case SmsManager.RESULT_ERROR_GENERIC_FAILURE: 1.448 + default: 1.449 + envelope.setError(kUnknownError); 1.450 + break; 1.451 + } 1.452 + envelope.markAsFailed(part); 1.453 + Log.i("GeckoSmsManager", "SMS part sending failed!"); 1.454 + } 1.455 + 1.456 + if (envelope.arePartsRemaining(part)) { 1.457 + return; 1.458 + } 1.459 + 1.460 + if (envelope.isFailing(part)) { 1.461 + if (part == Envelope.SubParts.SENT_PART) { 1.462 + GeckoAppShell.notifySmsSendFailed(envelope.getError(), 1.463 + bundle.getInt("requestId")); 1.464 + Log.i("GeckoSmsManager", "SMS sending failed!"); 1.465 + } else { 1.466 + GeckoAppShell.notifySmsDelivery(envelope.getMessageId(), 1.467 + kDeliveryStatusError, 1.468 + bundle.getString("number"), 1.469 + bundle.getString("message"), 1.470 + envelope.getMessageTimestamp()); 1.471 + Log.i("GeckoSmsManager", "SMS delivery failed!"); 1.472 + } 1.473 + } else { 1.474 + if (part == Envelope.SubParts.SENT_PART) { 1.475 + String number = bundle.getString("number"); 1.476 + String message = bundle.getString("message"); 1.477 + long timestamp = System.currentTimeMillis(); 1.478 + 1.479 + int id = saveSentMessage(number, message, timestamp); 1.480 + 1.481 + GeckoAppShell.notifySmsSent(id, number, message, timestamp, 1.482 + bundle.getInt("requestId")); 1.483 + 1.484 + envelope.setMessageId(id); 1.485 + envelope.setMessageTimestamp(timestamp); 1.486 + 1.487 + Log.i("GeckoSmsManager", "SMS sending was successfull!"); 1.488 + } else { 1.489 + GeckoAppShell.notifySmsDelivery(envelope.getMessageId(), 1.490 + kDeliveryStatusSuccess, 1.491 + bundle.getString("number"), 1.492 + bundle.getString("message"), 1.493 + envelope.getMessageTimestamp()); 1.494 + Log.i("GeckoSmsManager", "SMS succesfully delivered!"); 1.495 + } 1.496 + } 1.497 + 1.498 + // Destroy the envelope object only if the SMS has been sent and delivered. 1.499 + if (!envelope.arePartsRemaining(Envelope.SubParts.SENT_PART) && 1.500 + !envelope.arePartsRemaining(Envelope.SubParts.DELIVERED_PART)) { 1.501 + postman.destroyEnvelope(envelopeId); 1.502 + } 1.503 + 1.504 + return; 1.505 + } 1.506 + } 1.507 + 1.508 + public void send(String aNumber, String aMessage, int aRequestId) { 1.509 + int envelopeId = Postman.kUnknownEnvelopeId; 1.510 + 1.511 + try { 1.512 + SmsManager sm = SmsManager.getDefault(); 1.513 + 1.514 + Intent sentIntent = new Intent(ACTION_SMS_SENT); 1.515 + Intent deliveredIntent = new Intent(ACTION_SMS_DELIVERED); 1.516 + 1.517 + Bundle bundle = new Bundle(); 1.518 + bundle.putString("number", aNumber); 1.519 + bundle.putString("message", aMessage); 1.520 + bundle.putInt("requestId", aRequestId); 1.521 + 1.522 + if (aMessage.length() <= kMaxMessageSize) { 1.523 + envelopeId = Postman.getInstance().createEnvelope(1); 1.524 + bundle.putInt("envelopeId", envelopeId); 1.525 + 1.526 + sentIntent.putExtras(bundle); 1.527 + deliveredIntent.putExtras(bundle); 1.528 + 1.529 + /* 1.530 + * There are a few things to know about getBroadcast and pending intents: 1.531 + * - the pending intents are in a shared pool maintained by the system; 1.532 + * - each pending intent is identified by a token; 1.533 + * - when a new pending intent is created, if it has the same token as 1.534 + * another intent in the pool, one of them has to be removed. 1.535 + * 1.536 + * To prevent having a hard time because of this situation, we give a 1.537 + * unique id to all pending intents we are creating. This unique id is 1.538 + * generated by GetPendingIntentUID(). 1.539 + */ 1.540 + PendingIntent sentPendingIntent = 1.541 + PendingIntent.getBroadcast(GeckoApp.mAppContext, 1.542 + PendingIntentUID.generate(), sentIntent, 1.543 + PendingIntent.FLAG_CANCEL_CURRENT); 1.544 + 1.545 + PendingIntent deliveredPendingIntent = 1.546 + PendingIntent.getBroadcast(GeckoApp.mAppContext, 1.547 + PendingIntentUID.generate(), deliveredIntent, 1.548 + PendingIntent.FLAG_CANCEL_CURRENT); 1.549 + 1.550 + sm.sendTextMessage(aNumber, "", aMessage, 1.551 + sentPendingIntent, deliveredPendingIntent); 1.552 + } else { 1.553 + ArrayList<String> parts = sm.divideMessage(aMessage); 1.554 + envelopeId = Postman.getInstance().createEnvelope(parts.size()); 1.555 + bundle.putInt("envelopeId", envelopeId); 1.556 + 1.557 + sentIntent.putExtras(bundle); 1.558 + deliveredIntent.putExtras(bundle); 1.559 + 1.560 + ArrayList<PendingIntent> sentPendingIntents = 1.561 + new ArrayList<PendingIntent>(parts.size()); 1.562 + ArrayList<PendingIntent> deliveredPendingIntents = 1.563 + new ArrayList<PendingIntent>(parts.size()); 1.564 + 1.565 + for (int i=0; i<parts.size(); ++i) { 1.566 + sentPendingIntents.add( 1.567 + PendingIntent.getBroadcast(GeckoApp.mAppContext, 1.568 + PendingIntentUID.generate(), sentIntent, 1.569 + PendingIntent.FLAG_CANCEL_CURRENT) 1.570 + ); 1.571 + 1.572 + deliveredPendingIntents.add( 1.573 + PendingIntent.getBroadcast(GeckoApp.mAppContext, 1.574 + PendingIntentUID.generate(), deliveredIntent, 1.575 + PendingIntent.FLAG_CANCEL_CURRENT) 1.576 + ); 1.577 + } 1.578 + 1.579 + sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents, 1.580 + deliveredPendingIntents); 1.581 + } 1.582 + } catch (Exception e) { 1.583 + Log.e("GeckoSmsManager", "Failed to send an SMS: ", e); 1.584 + 1.585 + if (envelopeId != Postman.kUnknownEnvelopeId) { 1.586 + Postman.getInstance().destroyEnvelope(envelopeId); 1.587 + } 1.588 + 1.589 + GeckoAppShell.notifySmsSendFailed(kUnknownError, aRequestId); 1.590 + } 1.591 + } 1.592 + 1.593 + public int saveSentMessage(String aRecipient, String aBody, long aDate) { 1.594 + try { 1.595 + ContentValues values = new ContentValues(); 1.596 + values.put("address", aRecipient); 1.597 + values.put("body", aBody); 1.598 + values.put("date", aDate); 1.599 + // Always 'PENDING' because we always request status report. 1.600 + values.put("status", kInternalDeliveryStatusPending); 1.601 + 1.602 + ContentResolver cr = GeckoApp.mAppContext.getContentResolver(); 1.603 + Uri uri = cr.insert(kSmsSentContentUri, values); 1.604 + 1.605 + long id = ContentUris.parseId(uri); 1.606 + 1.607 + // The DOM API takes a 32bits unsigned int for the id. It's unlikely that 1.608 + // we happen to need more than that but it doesn't cost to check. 1.609 + if (id > Integer.MAX_VALUE) { 1.610 + throw new IdTooHighException(); 1.611 + } 1.612 + 1.613 + return (int)id; 1.614 + } catch (IdTooHighException e) { 1.615 + Log.e("GeckoSmsManager", "The id we received is higher than the higher allowed value."); 1.616 + return -1; 1.617 + } catch (Exception e) { 1.618 + Log.e("GeckoSmsManager", "Something went wrong when trying to write a sent message: " + e); 1.619 + return -1; 1.620 + } 1.621 + } 1.622 + 1.623 + public void getMessage(int aMessageId, int aRequestId) { 1.624 + class GetMessageRunnable implements Runnable { 1.625 + private int mMessageId; 1.626 + private int mRequestId; 1.627 + 1.628 + GetMessageRunnable(int aMessageId, int aRequestId) { 1.629 + mMessageId = aMessageId; 1.630 + mRequestId = aRequestId; 1.631 + } 1.632 + 1.633 + @Override 1.634 + public void run() { 1.635 + Cursor cursor = null; 1.636 + 1.637 + try { 1.638 + ContentResolver cr = GeckoApp.mAppContext.getContentResolver(); 1.639 + Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId); 1.640 + 1.641 + cursor = cr.query(message, kRequiredMessageRows, null, null, null); 1.642 + if (cursor == null || cursor.getCount() == 0) { 1.643 + throw new NotFoundException(); 1.644 + } 1.645 + 1.646 + if (cursor.getCount() != 1) { 1.647 + throw new TooManyResultsException(); 1.648 + } 1.649 + 1.650 + cursor.moveToFirst(); 1.651 + 1.652 + if (cursor.getInt(cursor.getColumnIndex("_id")) != mMessageId) { 1.653 + throw new UnmatchingIdException(); 1.654 + } 1.655 + 1.656 + int type = cursor.getInt(cursor.getColumnIndex("type")); 1.657 + int deliveryStatus; 1.658 + String sender = ""; 1.659 + String receiver = ""; 1.660 + 1.661 + if (type == kSmsTypeInbox) { 1.662 + deliveryStatus = kDeliveryStatusSuccess; 1.663 + sender = cursor.getString(cursor.getColumnIndex("address")); 1.664 + } else if (type == kSmsTypeSentbox) { 1.665 + deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status"))); 1.666 + receiver = cursor.getString(cursor.getColumnIndex("address")); 1.667 + } else { 1.668 + throw new InvalidTypeException(); 1.669 + } 1.670 + 1.671 + GeckoAppShell.notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")), 1.672 + deliveryStatus, 1.673 + receiver, sender, 1.674 + cursor.getString(cursor.getColumnIndex("body")), 1.675 + cursor.getLong(cursor.getColumnIndex("date")), 1.676 + mRequestId); 1.677 + } catch (NotFoundException e) { 1.678 + Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found"); 1.679 + GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId); 1.680 + } catch (UnmatchingIdException e) { 1.681 + Log.e("GeckoSmsManager", "Requested message id (" + mMessageId + 1.682 + ") is different from the one we got."); 1.683 + GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId); 1.684 + } catch (TooManyResultsException e) { 1.685 + Log.e("GeckoSmsManager", "Get too many results for id " + mMessageId); 1.686 + GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId); 1.687 + } catch (InvalidTypeException e) { 1.688 + Log.i("GeckoSmsManager", "Message has an invalid type, we ignore it."); 1.689 + GeckoAppShell.notifyGetSmsFailed(kNotFoundError, mRequestId); 1.690 + } catch (Exception e) { 1.691 + Log.e("GeckoSmsManager", "Error while trying to get message: " + e); 1.692 + GeckoAppShell.notifyGetSmsFailed(kUnknownError, mRequestId); 1.693 + } finally { 1.694 + if (cursor != null) { 1.695 + cursor.close(); 1.696 + } 1.697 + } 1.698 + } 1.699 + } 1.700 + 1.701 + if (!SmsIOThread.getInstance().execute(new GetMessageRunnable(aMessageId, aRequestId))) { 1.702 + Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread"); 1.703 + GeckoAppShell.notifyGetSmsFailed(kUnknownError, aRequestId); 1.704 + } 1.705 + } 1.706 + 1.707 + public void deleteMessage(int aMessageId, int aRequestId) { 1.708 + class DeleteMessageRunnable implements Runnable { 1.709 + private int mMessageId; 1.710 + private int mRequestId; 1.711 + 1.712 + DeleteMessageRunnable(int aMessageId, int aRequestId) { 1.713 + mMessageId = aMessageId; 1.714 + mRequestId = aRequestId; 1.715 + } 1.716 + 1.717 + @Override 1.718 + public void run() { 1.719 + try { 1.720 + ContentResolver cr = GeckoApp.mAppContext.getContentResolver(); 1.721 + Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId); 1.722 + 1.723 + int count = cr.delete(message, null, null); 1.724 + 1.725 + if (count > 1) { 1.726 + throw new TooManyResultsException(); 1.727 + } 1.728 + 1.729 + GeckoAppShell.notifySmsDeleted(count == 1, mRequestId); 1.730 + } catch (TooManyResultsException e) { 1.731 + Log.e("GeckoSmsManager", "Delete more than one message? " + e); 1.732 + GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId); 1.733 + } catch (Exception e) { 1.734 + Log.e("GeckoSmsManager", "Error while trying to delete a message: " + e); 1.735 + GeckoAppShell.notifySmsDeleteFailed(kUnknownError, mRequestId); 1.736 + } 1.737 + } 1.738 + } 1.739 + 1.740 + if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId))) { 1.741 + Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread"); 1.742 + GeckoAppShell.notifySmsDeleteFailed(kUnknownError, aRequestId); 1.743 + } 1.744 + } 1.745 + 1.746 + public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) { 1.747 + class CreateMessageListRunnable implements Runnable { 1.748 + private long mStartDate; 1.749 + private long mEndDate; 1.750 + private String[] mNumbers; 1.751 + private int mNumbersCount; 1.752 + private int mDeliveryState; 1.753 + private boolean mReverse; 1.754 + private int mRequestId; 1.755 + 1.756 + CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) { 1.757 + mStartDate = aStartDate; 1.758 + mEndDate = aEndDate; 1.759 + mNumbers = aNumbers; 1.760 + mNumbersCount = aNumbersCount; 1.761 + mDeliveryState = aDeliveryState; 1.762 + mReverse = aReverse; 1.763 + mRequestId = aRequestId; 1.764 + } 1.765 + 1.766 + @Override 1.767 + public void run() { 1.768 + Cursor cursor = null; 1.769 + boolean closeCursor = true; 1.770 + 1.771 + try { 1.772 + // TODO: should use the |selectionArgs| argument in |ContentResolver.query()|. 1.773 + ArrayList<String> restrictions = new ArrayList<String>(); 1.774 + 1.775 + if (mStartDate != 0) { 1.776 + restrictions.add("date >= " + mStartDate); 1.777 + } 1.778 + 1.779 + if (mEndDate != 0) { 1.780 + restrictions.add("date <= " + mEndDate); 1.781 + } 1.782 + 1.783 + if (mNumbersCount > 0) { 1.784 + String numberRestriction = "address IN ('" + mNumbers[0] + "'"; 1.785 + 1.786 + for (int i=1; i<mNumbersCount; ++i) { 1.787 + numberRestriction += ", '" + mNumbers[i] + "'"; 1.788 + } 1.789 + numberRestriction += ")"; 1.790 + 1.791 + restrictions.add(numberRestriction); 1.792 + } 1.793 + 1.794 + if (mDeliveryState == kDeliveryStateUnknown) { 1.795 + restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')"); 1.796 + } else if (mDeliveryState == kDeliveryStateSent) { 1.797 + restrictions.add("type = " + kSmsTypeSentbox); 1.798 + } else if (mDeliveryState == kDeliveryStateReceived) { 1.799 + restrictions.add("type = " + kSmsTypeInbox); 1.800 + } else { 1.801 + throw new UnexpectedDeliveryStateException(); 1.802 + } 1.803 + 1.804 + String restrictionText = restrictions.size() > 0 ? restrictions.get(0) : ""; 1.805 + 1.806 + for (int i=1; i<restrictions.size(); ++i) { 1.807 + restrictionText += " AND " + restrictions.get(i); 1.808 + } 1.809 + 1.810 + ContentResolver cr = GeckoApp.mAppContext.getContentResolver(); 1.811 + cursor = cr.query(kSmsContentUri, kRequiredMessageRows, restrictionText, null, 1.812 + mReverse ? "date DESC" : "date ASC"); 1.813 + 1.814 + if (cursor.getCount() == 0) { 1.815 + GeckoAppShell.notifyNoMessageInList(mRequestId); 1.816 + return; 1.817 + } 1.818 + 1.819 + cursor.moveToFirst(); 1.820 + 1.821 + int type = cursor.getInt(cursor.getColumnIndex("type")); 1.822 + int deliveryStatus; 1.823 + String sender = ""; 1.824 + String receiver = ""; 1.825 + 1.826 + if (type == kSmsTypeInbox) { 1.827 + deliveryStatus = kDeliveryStatusSuccess; 1.828 + sender = cursor.getString(cursor.getColumnIndex("address")); 1.829 + } else if (type == kSmsTypeSentbox) { 1.830 + deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status"))); 1.831 + receiver = cursor.getString(cursor.getColumnIndex("address")); 1.832 + } else { 1.833 + throw new UnexpectedDeliveryStateException(); 1.834 + } 1.835 + 1.836 + int listId = MessagesListManager.getInstance().add(cursor); 1.837 + closeCursor = false; 1.838 + GeckoAppShell.notifyListCreated(listId, 1.839 + cursor.getInt(cursor.getColumnIndex("_id")), 1.840 + deliveryStatus, 1.841 + receiver, sender, 1.842 + cursor.getString(cursor.getColumnIndex("body")), 1.843 + cursor.getLong(cursor.getColumnIndex("date")), 1.844 + mRequestId); 1.845 + } catch (UnexpectedDeliveryStateException e) { 1.846 + Log.e("GeckoSmsManager", "Unexcepted delivery state type: " + e); 1.847 + GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId); 1.848 + } catch (Exception e) { 1.849 + Log.e("GeckoSmsManager", "Error while trying to create a message list cursor: " + e); 1.850 + GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId); 1.851 + } finally { 1.852 + // Close the cursor if MessagesListManager isn't taking care of it. 1.853 + // We could also just check if it is in the MessagesListManager list but 1.854 + // that would be less efficient. 1.855 + if (cursor != null && closeCursor) { 1.856 + cursor.close(); 1.857 + } 1.858 + } 1.859 + } 1.860 + } 1.861 + 1.862 + if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId))) { 1.863 + Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread"); 1.864 + GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, aRequestId); 1.865 + } 1.866 + } 1.867 + 1.868 + public void getNextMessageInList(int aListId, int aRequestId) { 1.869 + class GetNextMessageInListRunnable implements Runnable { 1.870 + private int mListId; 1.871 + private int mRequestId; 1.872 + 1.873 + GetNextMessageInListRunnable(int aListId, int aRequestId) { 1.874 + mListId = aListId; 1.875 + mRequestId = aRequestId; 1.876 + } 1.877 + 1.878 + @Override 1.879 + public void run() { 1.880 + try { 1.881 + Cursor cursor = MessagesListManager.getInstance().get(mListId); 1.882 + 1.883 + if (!cursor.moveToNext()) { 1.884 + MessagesListManager.getInstance().remove(mListId); 1.885 + GeckoAppShell.notifyNoMessageInList(mRequestId); 1.886 + return; 1.887 + } 1.888 + 1.889 + int type = cursor.getInt(cursor.getColumnIndex("type")); 1.890 + int deliveryStatus; 1.891 + String sender = ""; 1.892 + String receiver = ""; 1.893 + 1.894 + if (type == kSmsTypeInbox) { 1.895 + deliveryStatus = kDeliveryStatusSuccess; 1.896 + sender = cursor.getString(cursor.getColumnIndex("address")); 1.897 + } else if (type == kSmsTypeSentbox) { 1.898 + deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status"))); 1.899 + receiver = cursor.getString(cursor.getColumnIndex("address")); 1.900 + } else { 1.901 + throw new UnexpectedDeliveryStateException(); 1.902 + } 1.903 + 1.904 + int listId = MessagesListManager.getInstance().add(cursor); 1.905 + GeckoAppShell.notifyGotNextMessage(cursor.getInt(cursor.getColumnIndex("_id")), 1.906 + deliveryStatus, 1.907 + receiver, sender, 1.908 + cursor.getString(cursor.getColumnIndex("body")), 1.909 + cursor.getLong(cursor.getColumnIndex("date")), 1.910 + mRequestId); 1.911 + } catch (UnexpectedDeliveryStateException e) { 1.912 + Log.e("GeckoSmsManager", "Unexcepted delivery state type: " + e); 1.913 + GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId); 1.914 + } catch (Exception e) { 1.915 + Log.e("GeckoSmsManager", "Error while trying to get the next message of a list: " + e); 1.916 + GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, mRequestId); 1.917 + } 1.918 + } 1.919 + } 1.920 + 1.921 + if (!SmsIOThread.getInstance().execute(new GetNextMessageInListRunnable(aListId, aRequestId))) { 1.922 + Log.e("GeckoSmsManager", "Failed to add GetNextMessageInListRunnable to the SmsIOThread"); 1.923 + GeckoAppShell.notifyReadingMessageListFailed(kUnknownError, aRequestId); 1.924 + } 1.925 + } 1.926 + 1.927 + public void clearMessageList(int aListId) { 1.928 + MessagesListManager.getInstance().remove(aListId); 1.929 + } 1.930 + 1.931 + public void stop() { 1.932 + GeckoApp.mAppContext.unregisterReceiver(this); 1.933 + } 1.934 + 1.935 + public void shutdown() { 1.936 + SmsIOThread.getInstance().interrupt(); 1.937 + MessagesListManager.getInstance().clear(); 1.938 + } 1.939 + 1.940 + private int getGeckoDeliveryStatus(int aDeliveryStatus) { 1.941 + if (aDeliveryStatus == kInternalDeliveryStatusNone) { 1.942 + return kDeliveryStatusNotApplicable; 1.943 + } 1.944 + if (aDeliveryStatus >= kInternalDeliveryStatusFailed) { 1.945 + return kDeliveryStatusError; 1.946 + } 1.947 + if (aDeliveryStatus >= kInternalDeliveryStatusPending) { 1.948 + return kDeliveryStatusPending; 1.949 + } 1.950 + return kDeliveryStatusSuccess; 1.951 + } 1.952 + 1.953 + private int getGeckoMessageClass(MessageClass aMessageClass) { 1.954 + switch (aMessageClass) { 1.955 + case UNKNOWN: 1.956 + return kMessageClassNormal; 1.957 + case CLASS_0: 1.958 + return kMessageClassClass0; 1.959 + case CLASS_1: 1.960 + return kMessageClassClass1; 1.961 + case CLASS_2: 1.962 + return kMessageClassClass2; 1.963 + case CLASS_3: 1.964 + return kMessageClassClass3; 1.965 + } 1.966 + } 1.967 + 1.968 + class IdTooHighException extends Exception { 1.969 + private static final long serialVersionUID = 395697882128640L; 1.970 + } 1.971 + 1.972 + class InvalidTypeException extends Exception { 1.973 + private static final long serialVersionUID = 23359904803795434L; 1.974 + } 1.975 + 1.976 + class NotFoundException extends Exception { 1.977 + private static final long serialVersionUID = 266226999371957426L; 1.978 + } 1.979 + 1.980 + class TooManyResultsException extends Exception { 1.981 + private static final long serialVersionUID = 48899777673841920L; 1.982 + } 1.983 + 1.984 + class UnexpectedDeliveryStateException extends Exception { 1.985 + private static final long serialVersionUID = 5044567998961920L; 1.986 + } 1.987 + 1.988 + class UnmatchingIdException extends Exception { 1.989 + private static final long serialVersionUID = 1935649715512128L; 1.990 + } 1.991 +}