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