mobile/android/base/GeckoSmsManager.java

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

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

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

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko;
michael@0 7
michael@0 8 import android.app.Activity;
michael@0 9 import android.app.PendingIntent;
michael@0 10 import android.content.BroadcastReceiver;
michael@0 11 import android.content.ContentResolver;
michael@0 12 import android.content.ContentUris;
michael@0 13 import android.content.ContentValues;
michael@0 14 import android.content.Context;
michael@0 15 import android.content.Intent;
michael@0 16 import android.content.IntentFilter;
michael@0 17 import android.database.Cursor;
michael@0 18 import android.net.Uri;
michael@0 19 import android.os.Bundle;
michael@0 20 import android.os.Handler;
michael@0 21 import android.os.Looper;
michael@0 22 import android.telephony.SmsManager;
michael@0 23 import android.telephony.SmsMessage;
michael@0 24 import android.util.Log;
michael@0 25
michael@0 26 import static android.telephony.SmsMessage.MessageClass;
michael@0 27
michael@0 28 import java.util.ArrayList;
michael@0 29
michael@0 30 /**
michael@0 31 * This class is returning unique ids for PendingIntent requestCode attribute.
michael@0 32 * There are only |Integer.MAX_VALUE - Integer.MIN_VALUE| unique IDs available,
michael@0 33 * and they wrap around.
michael@0 34 */
michael@0 35 class PendingIntentUID
michael@0 36 {
michael@0 37 static private int sUID = Integer.MIN_VALUE;
michael@0 38
michael@0 39 static public int generate() { return sUID++; }
michael@0 40 }
michael@0 41
michael@0 42 /**
michael@0 43 * The envelope class contains all information that are needed to keep track of
michael@0 44 * a sent SMS.
michael@0 45 */
michael@0 46 class Envelope
michael@0 47 {
michael@0 48 enum SubParts {
michael@0 49 SENT_PART,
michael@0 50 DELIVERED_PART
michael@0 51 }
michael@0 52
michael@0 53 protected int mId;
michael@0 54 protected int mMessageId;
michael@0 55 protected long mMessageTimestamp;
michael@0 56
michael@0 57 /**
michael@0 58 * Number of sent/delivered remaining parts.
michael@0 59 * @note The array has much slots as SubParts items.
michael@0 60 */
michael@0 61 protected int[] mRemainingParts;
michael@0 62
michael@0 63 /**
michael@0 64 * Whether sending/delivering is currently failing.
michael@0 65 * @note The array has much slots as SubParts items.
michael@0 66 */
michael@0 67 protected boolean[] mFailing;
michael@0 68
michael@0 69 /**
michael@0 70 * Error type (only for sent).
michael@0 71 */
michael@0 72 protected int mError;
michael@0 73
michael@0 74 public Envelope(int aId, int aParts) {
michael@0 75 mId = aId;
michael@0 76 mMessageId = -1;
michael@0 77 mMessageTimestamp = 0;
michael@0 78 mError = GeckoSmsManager.kNoError;
michael@0 79
michael@0 80 int size = Envelope.SubParts.values().length;
michael@0 81 mRemainingParts = new int[size];
michael@0 82 mFailing = new boolean[size];
michael@0 83
michael@0 84 for (int i=0; i<size; ++i) {
michael@0 85 mRemainingParts[i] = aParts;
michael@0 86 mFailing[i] = false;
michael@0 87 }
michael@0 88 }
michael@0 89
michael@0 90 public void decreaseRemainingParts(Envelope.SubParts aType) {
michael@0 91 --mRemainingParts[aType.ordinal()];
michael@0 92
michael@0 93 if (mRemainingParts[SubParts.SENT_PART.ordinal()] >
michael@0 94 mRemainingParts[SubParts.DELIVERED_PART.ordinal()]) {
michael@0 95 Log.e("GeckoSmsManager", "Delivered more parts than we sent!?");
michael@0 96 }
michael@0 97 }
michael@0 98
michael@0 99 public boolean arePartsRemaining(Envelope.SubParts aType) {
michael@0 100 return mRemainingParts[aType.ordinal()] != 0;
michael@0 101 }
michael@0 102
michael@0 103 public void markAsFailed(Envelope.SubParts aType) {
michael@0 104 mFailing[aType.ordinal()] = true;
michael@0 105 }
michael@0 106
michael@0 107 public boolean isFailing(Envelope.SubParts aType) {
michael@0 108 return mFailing[aType.ordinal()];
michael@0 109 }
michael@0 110
michael@0 111 public int getMessageId() {
michael@0 112 return mMessageId;
michael@0 113 }
michael@0 114
michael@0 115 public void setMessageId(int aMessageId) {
michael@0 116 mMessageId = aMessageId;
michael@0 117 }
michael@0 118
michael@0 119 public long getMessageTimestamp() {
michael@0 120 return mMessageTimestamp;
michael@0 121 }
michael@0 122
michael@0 123 public void setMessageTimestamp(long aMessageTimestamp) {
michael@0 124 mMessageTimestamp = aMessageTimestamp;
michael@0 125 }
michael@0 126
michael@0 127 public int getError() {
michael@0 128 return mError;
michael@0 129 }
michael@0 130
michael@0 131 public void setError(int aError) {
michael@0 132 mError = aError;
michael@0 133 }
michael@0 134 }
michael@0 135
michael@0 136 /**
michael@0 137 * Postman class is a singleton that manages Envelope instances.
michael@0 138 */
michael@0 139 class Postman
michael@0 140 {
michael@0 141 public static final int kUnknownEnvelopeId = -1;
michael@0 142
michael@0 143 private static final Postman sInstance = new Postman();
michael@0 144
michael@0 145 private ArrayList<Envelope> mEnvelopes = new ArrayList<Envelope>(1);
michael@0 146
michael@0 147 private Postman() {}
michael@0 148
michael@0 149 public static Postman getInstance() {
michael@0 150 return sInstance;
michael@0 151 }
michael@0 152
michael@0 153 public int createEnvelope(int aParts) {
michael@0 154 /*
michael@0 155 * We are going to create the envelope in the first empty slot in the array
michael@0 156 * list. If there is no empty slot, we create a new one.
michael@0 157 */
michael@0 158 int size = mEnvelopes.size();
michael@0 159
michael@0 160 for (int i=0; i<size; ++i) {
michael@0 161 if (mEnvelopes.get(i) == null) {
michael@0 162 mEnvelopes.set(i, new Envelope(i, aParts));
michael@0 163 return i;
michael@0 164 }
michael@0 165 }
michael@0 166
michael@0 167 mEnvelopes.add(new Envelope(size, aParts));
michael@0 168 return size;
michael@0 169 }
michael@0 170
michael@0 171 public Envelope getEnvelope(int aId) {
michael@0 172 if (aId < 0 || mEnvelopes.size() <= aId) {
michael@0 173 Log.e("GeckoSmsManager", "Trying to get an unknown Envelope!");
michael@0 174 return null;
michael@0 175 }
michael@0 176
michael@0 177 Envelope envelope = mEnvelopes.get(aId);
michael@0 178 if (envelope == null) {
michael@0 179 Log.e("GeckoSmsManager", "Trying to get an empty Envelope!");
michael@0 180 }
michael@0 181
michael@0 182 return envelope;
michael@0 183 }
michael@0 184
michael@0 185 public void destroyEnvelope(int aId) {
michael@0 186 if (aId < 0 || mEnvelopes.size() <= aId) {
michael@0 187 Log.e("GeckoSmsManager", "Trying to destroy an unknown Envelope!");
michael@0 188 return;
michael@0 189 }
michael@0 190
michael@0 191 if (mEnvelopes.set(aId, null) == null) {
michael@0 192 Log.e("GeckoSmsManager", "Trying to destroy an empty Envelope!");
michael@0 193 }
michael@0 194 }
michael@0 195 }
michael@0 196
michael@0 197 class SmsIOThread extends Thread {
michael@0 198 private final static SmsIOThread sInstance = new SmsIOThread();
michael@0 199
michael@0 200 private Handler mHandler;
michael@0 201
michael@0 202 public static SmsIOThread getInstance() {
michael@0 203 return sInstance;
michael@0 204 }
michael@0 205
michael@0 206 public boolean execute(Runnable r) {
michael@0 207 return mHandler.post(r);
michael@0 208 }
michael@0 209
michael@0 210 @Override
michael@0 211 public void run() {
michael@0 212 Looper.prepare();
michael@0 213
michael@0 214 mHandler = new Handler();
michael@0 215
michael@0 216 Looper.loop();
michael@0 217 }
michael@0 218 }
michael@0 219
michael@0 220 class MessagesListManager
michael@0 221 {
michael@0 222 private static final MessagesListManager sInstance = new MessagesListManager();
michael@0 223
michael@0 224 public static MessagesListManager getInstance() {
michael@0 225 return sInstance;
michael@0 226 }
michael@0 227
michael@0 228 private ArrayList<Cursor> mCursors = new ArrayList<Cursor>(0);
michael@0 229
michael@0 230 public int add(Cursor aCursor) {
michael@0 231 int size = mCursors.size();
michael@0 232
michael@0 233 for (int i=0; i<size; ++i) {
michael@0 234 if (mCursors.get(i) == null) {
michael@0 235 mCursors.set(i, aCursor);
michael@0 236 return i;
michael@0 237 }
michael@0 238 }
michael@0 239
michael@0 240 mCursors.add(aCursor);
michael@0 241 return size;
michael@0 242 }
michael@0 243
michael@0 244 public Cursor get(int aId) {
michael@0 245 if (aId < 0 || mCursors.size() <= aId) {
michael@0 246 Log.e("GeckoSmsManager", "Trying to get an unknown list!");
michael@0 247 return null;
michael@0 248 }
michael@0 249
michael@0 250 Cursor cursor = mCursors.get(aId);
michael@0 251 if (cursor == null) {
michael@0 252 Log.e("GeckoSmsManager", "Trying to get an empty list!");
michael@0 253 }
michael@0 254
michael@0 255 return cursor;
michael@0 256 }
michael@0 257
michael@0 258 public void remove(int aId) {
michael@0 259 if (aId < 0 || mCursors.size() <= aId) {
michael@0 260 Log.e("GeckoSmsManager", "Trying to destroy an unknown list!");
michael@0 261 return;
michael@0 262 }
michael@0 263
michael@0 264 Cursor cursor = mCursors.set(aId, null);
michael@0 265 if (cursor == null) {
michael@0 266 Log.e("GeckoSmsManager", "Trying to destroy an empty list!");
michael@0 267 return;
michael@0 268 }
michael@0 269
michael@0 270 cursor.close();
michael@0 271 }
michael@0 272
michael@0 273 public void clear() {
michael@0 274 for (int i=0; i<mCursors.size(); ++i) {
michael@0 275 Cursor c = mCursors.get(i);
michael@0 276 if (c != null) {
michael@0 277 c.close();
michael@0 278 }
michael@0 279 }
michael@0 280
michael@0 281 mCursors.clear();
michael@0 282 }
michael@0 283 }
michael@0 284
michael@0 285 public class GeckoSmsManager
michael@0 286 extends BroadcastReceiver
michael@0 287 implements ISmsManager
michael@0 288 {
michael@0 289 public final static String ACTION_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
michael@0 290 public final static String ACTION_SMS_SENT = "org.mozilla.gecko.SMS_SENT";
michael@0 291 public final static String ACTION_SMS_DELIVERED = "org.mozilla.gecko.SMS_DELIVERED";
michael@0 292
michael@0 293 /*
michael@0 294 * Make sure that the following error codes are in sync with |ErrorType| in:
michael@0 295 * dom/mobilemessage/src/Types.h
michael@0 296 * The error code are owned by the DOM.
michael@0 297 */
michael@0 298 public final static int kNoError = 0;
michael@0 299 public final static int kNoSignalError = 1;
michael@0 300 public final static int kNotFoundError = 2;
michael@0 301 public final static int kUnknownError = 3;
michael@0 302 public final static int kInternalError = 4;
michael@0 303 public final static int kNoSimCardError = 5;
michael@0 304 public final static int kRadioDisabledError = 6;
michael@0 305 public final static int kInvalidAddressError = 7;
michael@0 306 public final static int kFdnCheckError = 8;
michael@0 307 public final static int kNonActiveSimCardError = 9;
michael@0 308 public final static int kStorageFullError = 10;
michael@0 309 public final static int kSimNotMatchedError = 11;
michael@0 310
michael@0 311 private final static int kMaxMessageSize = 160;
michael@0 312
michael@0 313 private final static Uri kSmsContentUri = Uri.parse("content://sms");
michael@0 314 private final static Uri kSmsSentContentUri = Uri.parse("content://sms/sent");
michael@0 315
michael@0 316 private final static int kSmsTypeInbox = 1;
michael@0 317 private final static int kSmsTypeSentbox = 2;
michael@0 318
michael@0 319 /*
michael@0 320 * Keep the following state codes in syng with |DeliveryState| in:
michael@0 321 * dom/mobilemessage/src/Types.h
michael@0 322 */
michael@0 323 private final static int kDeliveryStateSent = 0;
michael@0 324 private final static int kDeliveryStateReceived = 1;
michael@0 325 private final static int kDeliveryStateSending = 2;
michael@0 326 private final static int kDeliveryStateError = 3;
michael@0 327 private final static int kDeliveryStateUnknown = 4;
michael@0 328 private final static int kDeliveryStateNotDownloaded = 5;
michael@0 329 private final static int kDeliveryStateEndGuard = 6;
michael@0 330
michael@0 331 /*
michael@0 332 * Keep the following status codes in sync with |DeliveryStatus| in:
michael@0 333 * dom/mobilemessage/src/Types.h
michael@0 334 */
michael@0 335 private final static int kDeliveryStatusNotApplicable = 0;
michael@0 336 private final static int kDeliveryStatusSuccess = 1;
michael@0 337 private final static int kDeliveryStatusPending = 2;
michael@0 338 private final static int kDeliveryStatusError = 3;
michael@0 339
michael@0 340 /*
michael@0 341 * android.provider.Telephony.Sms.STATUS_*. Duplicated because they're not
michael@0 342 * part of Android public API.
michael@0 343 */
michael@0 344 private final static int kInternalDeliveryStatusNone = -1;
michael@0 345 private final static int kInternalDeliveryStatusComplete = 0;
michael@0 346 private final static int kInternalDeliveryStatusPending = 32;
michael@0 347 private final static int kInternalDeliveryStatusFailed = 64;
michael@0 348
michael@0 349 /*
michael@0 350 * Keep the following values in sync with |MessageClass| in:
michael@0 351 * dom/mobilemessage/src/Types.h
michael@0 352 */
michael@0 353 private final static int kMessageClassNormal = 0;
michael@0 354 private final static int kMessageClassClass0 = 1;
michael@0 355 private final static int kMessageClassClass1 = 2;
michael@0 356 private final static int kMessageClassClass2 = 3;
michael@0 357 private final static int kMessageClassClass3 = 4;
michael@0 358
michael@0 359 private final static String[] kRequiredMessageRows = new String[] { "_id", "address", "body", "date", "type", "status" };
michael@0 360
michael@0 361 public GeckoSmsManager() {
michael@0 362 SmsIOThread.getInstance().start();
michael@0 363 }
michael@0 364
michael@0 365 @Override
michael@0 366 public void start() {
michael@0 367 IntentFilter smsFilter = new IntentFilter();
michael@0 368 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_RECEIVED);
michael@0 369 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_SENT);
michael@0 370 smsFilter.addAction(GeckoSmsManager.ACTION_SMS_DELIVERED);
michael@0 371
michael@0 372 GeckoAppShell.getContext().registerReceiver(this, smsFilter);
michael@0 373 }
michael@0 374
michael@0 375 @Override
michael@0 376 public void onReceive(Context context, Intent intent) {
michael@0 377 if (intent.getAction().equals(ACTION_SMS_RECEIVED)) {
michael@0 378 // TODO: Try to find the receiver number to be able to populate
michael@0 379 // SmsMessage.receiver.
michael@0 380 // TODO: Get the id and the date from the stock app saved message.
michael@0 381 // Using the stock app saved message require us to wait for it to
michael@0 382 // be saved which can lead to race conditions.
michael@0 383
michael@0 384 Bundle bundle = intent.getExtras();
michael@0 385
michael@0 386 if (bundle == null) {
michael@0 387 return;
michael@0 388 }
michael@0 389
michael@0 390 Object[] pdus = (Object[]) bundle.get("pdus");
michael@0 391
michael@0 392 for (int i=0; i<pdus.length; ++i) {
michael@0 393 SmsMessage msg = SmsMessage.createFromPdu((byte[])pdus[i]);
michael@0 394
michael@0 395 notifySmsReceived(msg.getDisplayOriginatingAddress(),
michael@0 396 msg.getDisplayMessageBody(),
michael@0 397 getGeckoMessageClass(msg.getMessageClass()),
michael@0 398 System.currentTimeMillis());
michael@0 399 }
michael@0 400
michael@0 401 return;
michael@0 402 }
michael@0 403
michael@0 404 if (intent.getAction().equals(ACTION_SMS_SENT) ||
michael@0 405 intent.getAction().equals(ACTION_SMS_DELIVERED)) {
michael@0 406 Bundle bundle = intent.getExtras();
michael@0 407
michael@0 408 if (bundle == null || !bundle.containsKey("envelopeId") ||
michael@0 409 !bundle.containsKey("number") || !bundle.containsKey("message") ||
michael@0 410 !bundle.containsKey("requestId")) {
michael@0 411 Log.e("GeckoSmsManager", "Got an invalid ACTION_SMS_SENT/ACTION_SMS_DELIVERED!");
michael@0 412 return;
michael@0 413 }
michael@0 414
michael@0 415 int envelopeId = bundle.getInt("envelopeId");
michael@0 416 Postman postman = Postman.getInstance();
michael@0 417
michael@0 418 Envelope envelope = postman.getEnvelope(envelopeId);
michael@0 419 if (envelope == null) {
michael@0 420 Log.e("GeckoSmsManager", "Got an invalid envelope id (or Envelope has been destroyed)!");
michael@0 421 return;
michael@0 422 }
michael@0 423
michael@0 424 Envelope.SubParts part = intent.getAction().equals(ACTION_SMS_SENT)
michael@0 425 ? Envelope.SubParts.SENT_PART
michael@0 426 : Envelope.SubParts.DELIVERED_PART;
michael@0 427 envelope.decreaseRemainingParts(part);
michael@0 428
michael@0 429
michael@0 430 if (getResultCode() != Activity.RESULT_OK) {
michael@0 431 switch (getResultCode()) {
michael@0 432 case SmsManager.RESULT_ERROR_NULL_PDU:
michael@0 433 envelope.setError(kInternalError);
michael@0 434 break;
michael@0 435 case SmsManager.RESULT_ERROR_NO_SERVICE:
michael@0 436 case SmsManager.RESULT_ERROR_RADIO_OFF:
michael@0 437 envelope.setError(kNoSignalError);
michael@0 438 break;
michael@0 439 case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
michael@0 440 default:
michael@0 441 envelope.setError(kUnknownError);
michael@0 442 break;
michael@0 443 }
michael@0 444 envelope.markAsFailed(part);
michael@0 445 Log.i("GeckoSmsManager", "SMS part sending failed!");
michael@0 446 }
michael@0 447
michael@0 448 if (envelope.arePartsRemaining(part)) {
michael@0 449 return;
michael@0 450 }
michael@0 451
michael@0 452 if (envelope.isFailing(part)) {
michael@0 453 if (part == Envelope.SubParts.SENT_PART) {
michael@0 454 notifySmsSendFailed(envelope.getError(), bundle.getInt("requestId"));
michael@0 455 Log.i("GeckoSmsManager", "SMS sending failed!");
michael@0 456 } else {
michael@0 457 notifySmsDelivery(envelope.getMessageId(),
michael@0 458 kDeliveryStatusError,
michael@0 459 bundle.getString("number"),
michael@0 460 bundle.getString("message"),
michael@0 461 envelope.getMessageTimestamp());
michael@0 462 Log.i("GeckoSmsManager", "SMS delivery failed!");
michael@0 463 }
michael@0 464 } else {
michael@0 465 if (part == Envelope.SubParts.SENT_PART) {
michael@0 466 String number = bundle.getString("number");
michael@0 467 String message = bundle.getString("message");
michael@0 468 long timestamp = System.currentTimeMillis();
michael@0 469
michael@0 470 int id = saveSentMessage(number, message, timestamp);
michael@0 471
michael@0 472 notifySmsSent(id, number, message, timestamp,
michael@0 473 bundle.getInt("requestId"));
michael@0 474
michael@0 475 envelope.setMessageId(id);
michael@0 476 envelope.setMessageTimestamp(timestamp);
michael@0 477
michael@0 478 Log.i("GeckoSmsManager", "SMS sending was successfull!");
michael@0 479 } else {
michael@0 480 notifySmsDelivery(envelope.getMessageId(),
michael@0 481 kDeliveryStatusSuccess,
michael@0 482 bundle.getString("number"),
michael@0 483 bundle.getString("message"),
michael@0 484 envelope.getMessageTimestamp());
michael@0 485 Log.i("GeckoSmsManager", "SMS successfully delivered!");
michael@0 486 }
michael@0 487 }
michael@0 488
michael@0 489 // Destroy the envelope object only if the SMS has been sent and delivered.
michael@0 490 if (!envelope.arePartsRemaining(Envelope.SubParts.SENT_PART) &&
michael@0 491 !envelope.arePartsRemaining(Envelope.SubParts.DELIVERED_PART)) {
michael@0 492 postman.destroyEnvelope(envelopeId);
michael@0 493 }
michael@0 494
michael@0 495 return;
michael@0 496 }
michael@0 497 }
michael@0 498
michael@0 499 @Override
michael@0 500 public void send(String aNumber, String aMessage, int aRequestId) {
michael@0 501 int envelopeId = Postman.kUnknownEnvelopeId;
michael@0 502
michael@0 503 try {
michael@0 504 SmsManager sm = SmsManager.getDefault();
michael@0 505
michael@0 506 Intent sentIntent = new Intent(ACTION_SMS_SENT);
michael@0 507 Intent deliveredIntent = new Intent(ACTION_SMS_DELIVERED);
michael@0 508
michael@0 509 Bundle bundle = new Bundle();
michael@0 510 bundle.putString("number", aNumber);
michael@0 511 bundle.putString("message", aMessage);
michael@0 512 bundle.putInt("requestId", aRequestId);
michael@0 513
michael@0 514 if (aMessage.length() <= kMaxMessageSize) {
michael@0 515 envelopeId = Postman.getInstance().createEnvelope(1);
michael@0 516 bundle.putInt("envelopeId", envelopeId);
michael@0 517
michael@0 518 sentIntent.putExtras(bundle);
michael@0 519 deliveredIntent.putExtras(bundle);
michael@0 520
michael@0 521 /*
michael@0 522 * There are a few things to know about getBroadcast and pending intents:
michael@0 523 * - the pending intents are in a shared pool maintained by the system;
michael@0 524 * - each pending intent is identified by a token;
michael@0 525 * - when a new pending intent is created, if it has the same token as
michael@0 526 * another intent in the pool, one of them has to be removed.
michael@0 527 *
michael@0 528 * To prevent having a hard time because of this situation, we give a
michael@0 529 * unique id to all pending intents we are creating. This unique id is
michael@0 530 * generated by GetPendingIntentUID().
michael@0 531 */
michael@0 532 PendingIntent sentPendingIntent =
michael@0 533 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
michael@0 534 PendingIntentUID.generate(), sentIntent,
michael@0 535 PendingIntent.FLAG_CANCEL_CURRENT);
michael@0 536
michael@0 537 PendingIntent deliveredPendingIntent =
michael@0 538 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
michael@0 539 PendingIntentUID.generate(), deliveredIntent,
michael@0 540 PendingIntent.FLAG_CANCEL_CURRENT);
michael@0 541
michael@0 542 sm.sendTextMessage(aNumber, "", aMessage,
michael@0 543 sentPendingIntent, deliveredPendingIntent);
michael@0 544 } else {
michael@0 545 ArrayList<String> parts = sm.divideMessage(aMessage);
michael@0 546 envelopeId = Postman.getInstance().createEnvelope(parts.size());
michael@0 547 bundle.putInt("envelopeId", envelopeId);
michael@0 548
michael@0 549 sentIntent.putExtras(bundle);
michael@0 550 deliveredIntent.putExtras(bundle);
michael@0 551
michael@0 552 ArrayList<PendingIntent> sentPendingIntents =
michael@0 553 new ArrayList<PendingIntent>(parts.size());
michael@0 554 ArrayList<PendingIntent> deliveredPendingIntents =
michael@0 555 new ArrayList<PendingIntent>(parts.size());
michael@0 556
michael@0 557 for (int i=0; i<parts.size(); ++i) {
michael@0 558 sentPendingIntents.add(
michael@0 559 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
michael@0 560 PendingIntentUID.generate(), sentIntent,
michael@0 561 PendingIntent.FLAG_CANCEL_CURRENT)
michael@0 562 );
michael@0 563
michael@0 564 deliveredPendingIntents.add(
michael@0 565 PendingIntent.getBroadcast(GeckoAppShell.getContext(),
michael@0 566 PendingIntentUID.generate(), deliveredIntent,
michael@0 567 PendingIntent.FLAG_CANCEL_CURRENT)
michael@0 568 );
michael@0 569 }
michael@0 570
michael@0 571 sm.sendMultipartTextMessage(aNumber, "", parts, sentPendingIntents,
michael@0 572 deliveredPendingIntents);
michael@0 573 }
michael@0 574 } catch (Exception e) {
michael@0 575 Log.e("GeckoSmsManager", "Failed to send an SMS: ", e);
michael@0 576
michael@0 577 if (envelopeId != Postman.kUnknownEnvelopeId) {
michael@0 578 Postman.getInstance().destroyEnvelope(envelopeId);
michael@0 579 }
michael@0 580
michael@0 581 notifySmsSendFailed(kUnknownError, aRequestId);
michael@0 582 }
michael@0 583 }
michael@0 584
michael@0 585 public int saveSentMessage(String aRecipient, String aBody, long aDate) {
michael@0 586 try {
michael@0 587 ContentValues values = new ContentValues();
michael@0 588 values.put("address", aRecipient);
michael@0 589 values.put("body", aBody);
michael@0 590 values.put("date", aDate);
michael@0 591 // Always 'PENDING' because we always request status report.
michael@0 592 values.put("status", kInternalDeliveryStatusPending);
michael@0 593
michael@0 594 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
michael@0 595 Uri uri = cr.insert(kSmsSentContentUri, values);
michael@0 596
michael@0 597 long id = ContentUris.parseId(uri);
michael@0 598
michael@0 599 // The DOM API takes a 32bits unsigned int for the id. It's unlikely that
michael@0 600 // we happen to need more than that but it doesn't cost to check.
michael@0 601 if (id > Integer.MAX_VALUE) {
michael@0 602 throw new IdTooHighException();
michael@0 603 }
michael@0 604
michael@0 605 return (int)id;
michael@0 606 } catch (IdTooHighException e) {
michael@0 607 Log.e("GeckoSmsManager", "The id we received is higher than the higher allowed value.");
michael@0 608 return -1;
michael@0 609 } catch (Exception e) {
michael@0 610 Log.e("GeckoSmsManager", "Something went wrong when trying to write a sent message", e);
michael@0 611 return -1;
michael@0 612 }
michael@0 613 }
michael@0 614
michael@0 615 @Override
michael@0 616 public void getMessage(int aMessageId, int aRequestId) {
michael@0 617 class GetMessageRunnable implements Runnable {
michael@0 618 private int mMessageId;
michael@0 619 private int mRequestId;
michael@0 620
michael@0 621 GetMessageRunnable(int aMessageId, int aRequestId) {
michael@0 622 mMessageId = aMessageId;
michael@0 623 mRequestId = aRequestId;
michael@0 624 }
michael@0 625
michael@0 626 @Override
michael@0 627 public void run() {
michael@0 628 Cursor cursor = null;
michael@0 629
michael@0 630 try {
michael@0 631 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
michael@0 632 Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId);
michael@0 633
michael@0 634 cursor = cr.query(message, kRequiredMessageRows, null, null, null);
michael@0 635 if (cursor == null || cursor.getCount() == 0) {
michael@0 636 throw new NotFoundException();
michael@0 637 }
michael@0 638
michael@0 639 if (cursor.getCount() != 1) {
michael@0 640 throw new TooManyResultsException();
michael@0 641 }
michael@0 642
michael@0 643 cursor.moveToFirst();
michael@0 644
michael@0 645 if (cursor.getInt(cursor.getColumnIndex("_id")) != mMessageId) {
michael@0 646 throw new UnmatchingIdException();
michael@0 647 }
michael@0 648
michael@0 649 int type = cursor.getInt(cursor.getColumnIndex("type"));
michael@0 650 int deliveryStatus;
michael@0 651 String sender = "";
michael@0 652 String receiver = "";
michael@0 653
michael@0 654 if (type == kSmsTypeInbox) {
michael@0 655 deliveryStatus = kDeliveryStatusSuccess;
michael@0 656 sender = cursor.getString(cursor.getColumnIndex("address"));
michael@0 657 } else if (type == kSmsTypeSentbox) {
michael@0 658 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
michael@0 659 receiver = cursor.getString(cursor.getColumnIndex("address"));
michael@0 660 } else {
michael@0 661 throw new InvalidTypeException();
michael@0 662 }
michael@0 663
michael@0 664 notifyGetSms(cursor.getInt(cursor.getColumnIndex("_id")),
michael@0 665 deliveryStatus,
michael@0 666 receiver, sender,
michael@0 667 cursor.getString(cursor.getColumnIndex("body")),
michael@0 668 cursor.getLong(cursor.getColumnIndex("date")),
michael@0 669 mRequestId);
michael@0 670 } catch (NotFoundException e) {
michael@0 671 Log.i("GeckoSmsManager", "Message id " + mMessageId + " not found");
michael@0 672 notifyGetSmsFailed(kNotFoundError, mRequestId);
michael@0 673 } catch (UnmatchingIdException e) {
michael@0 674 Log.e("GeckoSmsManager", "Requested message id (" + mMessageId +
michael@0 675 ") is different from the one we got.");
michael@0 676 notifyGetSmsFailed(kUnknownError, mRequestId);
michael@0 677 } catch (TooManyResultsException e) {
michael@0 678 Log.e("GeckoSmsManager", "Get too many results for id " + mMessageId);
michael@0 679 notifyGetSmsFailed(kUnknownError, mRequestId);
michael@0 680 } catch (InvalidTypeException e) {
michael@0 681 Log.i("GeckoSmsManager", "Message has an invalid type, we ignore it.");
michael@0 682 notifyGetSmsFailed(kNotFoundError, mRequestId);
michael@0 683 } catch (Exception e) {
michael@0 684 Log.e("GeckoSmsManager", "Error while trying to get message", e);
michael@0 685 notifyGetSmsFailed(kUnknownError, mRequestId);
michael@0 686 } finally {
michael@0 687 if (cursor != null) {
michael@0 688 cursor.close();
michael@0 689 }
michael@0 690 }
michael@0 691 }
michael@0 692 }
michael@0 693
michael@0 694 if (!SmsIOThread.getInstance().execute(new GetMessageRunnable(aMessageId, aRequestId))) {
michael@0 695 Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
michael@0 696 notifyGetSmsFailed(kUnknownError, aRequestId);
michael@0 697 }
michael@0 698 }
michael@0 699
michael@0 700 @Override
michael@0 701 public void deleteMessage(int aMessageId, int aRequestId) {
michael@0 702 class DeleteMessageRunnable implements Runnable {
michael@0 703 private int mMessageId;
michael@0 704 private int mRequestId;
michael@0 705
michael@0 706 DeleteMessageRunnable(int aMessageId, int aRequestId) {
michael@0 707 mMessageId = aMessageId;
michael@0 708 mRequestId = aRequestId;
michael@0 709 }
michael@0 710
michael@0 711 @Override
michael@0 712 public void run() {
michael@0 713 try {
michael@0 714 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
michael@0 715 Uri message = ContentUris.withAppendedId(kSmsContentUri, mMessageId);
michael@0 716
michael@0 717 int count = cr.delete(message, null, null);
michael@0 718
michael@0 719 if (count > 1) {
michael@0 720 throw new TooManyResultsException();
michael@0 721 }
michael@0 722
michael@0 723 notifySmsDeleted(count == 1, mRequestId);
michael@0 724 } catch (TooManyResultsException e) {
michael@0 725 Log.e("GeckoSmsManager", "Delete more than one message?", e);
michael@0 726 notifySmsDeleteFailed(kUnknownError, mRequestId);
michael@0 727 } catch (Exception e) {
michael@0 728 Log.e("GeckoSmsManager", "Error while trying to delete a message", e);
michael@0 729 notifySmsDeleteFailed(kUnknownError, mRequestId);
michael@0 730 }
michael@0 731 }
michael@0 732 }
michael@0 733
michael@0 734 if (!SmsIOThread.getInstance().execute(new DeleteMessageRunnable(aMessageId, aRequestId))) {
michael@0 735 Log.e("GeckoSmsManager", "Failed to add GetMessageRunnable to the SmsIOThread");
michael@0 736 notifySmsDeleteFailed(kUnknownError, aRequestId);
michael@0 737 }
michael@0 738 }
michael@0 739
michael@0 740 @Override
michael@0 741 public void createMessageList(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
michael@0 742 class CreateMessageListRunnable implements Runnable {
michael@0 743 private long mStartDate;
michael@0 744 private long mEndDate;
michael@0 745 private String[] mNumbers;
michael@0 746 private int mNumbersCount;
michael@0 747 private int mDeliveryState;
michael@0 748 private boolean mReverse;
michael@0 749 private int mRequestId;
michael@0 750
michael@0 751 CreateMessageListRunnable(long aStartDate, long aEndDate, String[] aNumbers, int aNumbersCount, int aDeliveryState, boolean aReverse, int aRequestId) {
michael@0 752 mStartDate = aStartDate;
michael@0 753 mEndDate = aEndDate;
michael@0 754 mNumbers = aNumbers;
michael@0 755 mNumbersCount = aNumbersCount;
michael@0 756 mDeliveryState = aDeliveryState;
michael@0 757 mReverse = aReverse;
michael@0 758 mRequestId = aRequestId;
michael@0 759 }
michael@0 760
michael@0 761 @Override
michael@0 762 public void run() {
michael@0 763 Cursor cursor = null;
michael@0 764 boolean closeCursor = true;
michael@0 765
michael@0 766 try {
michael@0 767 // TODO: should use the |selectionArgs| argument in |ContentResolver.query()|.
michael@0 768 ArrayList<String> restrictions = new ArrayList<String>();
michael@0 769
michael@0 770 if (mStartDate != 0) {
michael@0 771 restrictions.add("date >= " + mStartDate);
michael@0 772 }
michael@0 773
michael@0 774 if (mEndDate != 0) {
michael@0 775 restrictions.add("date <= " + mEndDate);
michael@0 776 }
michael@0 777
michael@0 778 if (mNumbersCount > 0) {
michael@0 779 String numberRestriction = "address IN ('" + mNumbers[0] + "'";
michael@0 780
michael@0 781 for (int i=1; i<mNumbersCount; ++i) {
michael@0 782 numberRestriction += ", '" + mNumbers[i] + "'";
michael@0 783 }
michael@0 784 numberRestriction += ")";
michael@0 785
michael@0 786 restrictions.add(numberRestriction);
michael@0 787 }
michael@0 788
michael@0 789 if (mDeliveryState == kDeliveryStateUnknown) {
michael@0 790 restrictions.add("type IN ('" + kSmsTypeSentbox + "', '" + kSmsTypeInbox + "')");
michael@0 791 } else if (mDeliveryState == kDeliveryStateSent) {
michael@0 792 restrictions.add("type = " + kSmsTypeSentbox);
michael@0 793 } else if (mDeliveryState == kDeliveryStateReceived) {
michael@0 794 restrictions.add("type = " + kSmsTypeInbox);
michael@0 795 } else {
michael@0 796 throw new UnexpectedDeliveryStateException();
michael@0 797 }
michael@0 798
michael@0 799 String restrictionText = restrictions.size() > 0 ? restrictions.get(0) : "";
michael@0 800
michael@0 801 for (int i=1; i<restrictions.size(); ++i) {
michael@0 802 restrictionText += " AND " + restrictions.get(i);
michael@0 803 }
michael@0 804
michael@0 805 ContentResolver cr = GeckoAppShell.getContext().getContentResolver();
michael@0 806 cursor = cr.query(kSmsContentUri, kRequiredMessageRows, restrictionText, null,
michael@0 807 mReverse ? "date DESC" : "date ASC");
michael@0 808
michael@0 809 if (cursor.getCount() == 0) {
michael@0 810 notifyNoMessageInList(mRequestId);
michael@0 811 return;
michael@0 812 }
michael@0 813
michael@0 814 cursor.moveToFirst();
michael@0 815
michael@0 816 int type = cursor.getInt(cursor.getColumnIndex("type"));
michael@0 817 int deliveryStatus;
michael@0 818 String sender = "";
michael@0 819 String receiver = "";
michael@0 820
michael@0 821 if (type == kSmsTypeInbox) {
michael@0 822 deliveryStatus = kDeliveryStatusSuccess;
michael@0 823 sender = cursor.getString(cursor.getColumnIndex("address"));
michael@0 824 } else if (type == kSmsTypeSentbox) {
michael@0 825 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
michael@0 826 receiver = cursor.getString(cursor.getColumnIndex("address"));
michael@0 827 } else {
michael@0 828 throw new UnexpectedDeliveryStateException();
michael@0 829 }
michael@0 830
michael@0 831 int listId = MessagesListManager.getInstance().add(cursor);
michael@0 832 closeCursor = false;
michael@0 833 notifyListCreated(listId,
michael@0 834 cursor.getInt(cursor.getColumnIndex("_id")),
michael@0 835 deliveryStatus,
michael@0 836 receiver, sender,
michael@0 837 cursor.getString(cursor.getColumnIndex("body")),
michael@0 838 cursor.getLong(cursor.getColumnIndex("date")),
michael@0 839 mRequestId);
michael@0 840 } catch (UnexpectedDeliveryStateException e) {
michael@0 841 Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
michael@0 842 notifyReadingMessageListFailed(kUnknownError, mRequestId);
michael@0 843 } catch (Exception e) {
michael@0 844 Log.e("GeckoSmsManager", "Error while trying to create a message list cursor", e);
michael@0 845 notifyReadingMessageListFailed(kUnknownError, mRequestId);
michael@0 846 } finally {
michael@0 847 // Close the cursor if MessagesListManager isn't taking care of it.
michael@0 848 // We could also just check if it is in the MessagesListManager list but
michael@0 849 // that would be less efficient.
michael@0 850 if (cursor != null && closeCursor) {
michael@0 851 cursor.close();
michael@0 852 }
michael@0 853 }
michael@0 854 }
michael@0 855 }
michael@0 856
michael@0 857 if (!SmsIOThread.getInstance().execute(new CreateMessageListRunnable(aStartDate, aEndDate, aNumbers, aNumbersCount, aDeliveryState, aReverse, aRequestId))) {
michael@0 858 Log.e("GeckoSmsManager", "Failed to add CreateMessageListRunnable to the SmsIOThread");
michael@0 859 notifyReadingMessageListFailed(kUnknownError, aRequestId);
michael@0 860 }
michael@0 861 }
michael@0 862
michael@0 863 @Override
michael@0 864 public void getNextMessageInList(int aListId, int aRequestId) {
michael@0 865 class GetNextMessageInListRunnable implements Runnable {
michael@0 866 private int mListId;
michael@0 867 private int mRequestId;
michael@0 868
michael@0 869 GetNextMessageInListRunnable(int aListId, int aRequestId) {
michael@0 870 mListId = aListId;
michael@0 871 mRequestId = aRequestId;
michael@0 872 }
michael@0 873
michael@0 874 @Override
michael@0 875 public void run() {
michael@0 876 try {
michael@0 877 Cursor cursor = MessagesListManager.getInstance().get(mListId);
michael@0 878
michael@0 879 if (!cursor.moveToNext()) {
michael@0 880 MessagesListManager.getInstance().remove(mListId);
michael@0 881 notifyNoMessageInList(mRequestId);
michael@0 882 return;
michael@0 883 }
michael@0 884
michael@0 885 int type = cursor.getInt(cursor.getColumnIndex("type"));
michael@0 886 int deliveryStatus;
michael@0 887 String sender = "";
michael@0 888 String receiver = "";
michael@0 889
michael@0 890 if (type == kSmsTypeInbox) {
michael@0 891 deliveryStatus = kDeliveryStatusSuccess;
michael@0 892 sender = cursor.getString(cursor.getColumnIndex("address"));
michael@0 893 } else if (type == kSmsTypeSentbox) {
michael@0 894 deliveryStatus = getGeckoDeliveryStatus(cursor.getInt(cursor.getColumnIndex("status")));
michael@0 895 receiver = cursor.getString(cursor.getColumnIndex("address"));
michael@0 896 } else {
michael@0 897 throw new UnexpectedDeliveryStateException();
michael@0 898 }
michael@0 899
michael@0 900 int listId = MessagesListManager.getInstance().add(cursor);
michael@0 901 notifyGotNextMessage(cursor.getInt(cursor.getColumnIndex("_id")),
michael@0 902 deliveryStatus,
michael@0 903 receiver, sender,
michael@0 904 cursor.getString(cursor.getColumnIndex("body")),
michael@0 905 cursor.getLong(cursor.getColumnIndex("date")),
michael@0 906 mRequestId);
michael@0 907 } catch (UnexpectedDeliveryStateException e) {
michael@0 908 Log.e("GeckoSmsManager", "Unexcepted delivery state type", e);
michael@0 909 notifyReadingMessageListFailed(kUnknownError, mRequestId);
michael@0 910 } catch (Exception e) {
michael@0 911 Log.e("GeckoSmsManager", "Error while trying to get the next message of a list", e);
michael@0 912 notifyReadingMessageListFailed(kUnknownError, mRequestId);
michael@0 913 }
michael@0 914 }
michael@0 915 }
michael@0 916
michael@0 917 if (!SmsIOThread.getInstance().execute(new GetNextMessageInListRunnable(aListId, aRequestId))) {
michael@0 918 Log.e("GeckoSmsManager", "Failed to add GetNextMessageInListRunnable to the SmsIOThread");
michael@0 919 notifyReadingMessageListFailed(kUnknownError, aRequestId);
michael@0 920 }
michael@0 921 }
michael@0 922
michael@0 923 @Override
michael@0 924 public void clearMessageList(int aListId) {
michael@0 925 MessagesListManager.getInstance().remove(aListId);
michael@0 926 }
michael@0 927
michael@0 928 @Override
michael@0 929 public void stop() {
michael@0 930 GeckoAppShell.getContext().unregisterReceiver(this);
michael@0 931 }
michael@0 932
michael@0 933 @Override
michael@0 934 public void shutdown() {
michael@0 935 SmsIOThread.getInstance().interrupt();
michael@0 936 MessagesListManager.getInstance().clear();
michael@0 937 }
michael@0 938
michael@0 939 private int getGeckoDeliveryStatus(int aDeliveryStatus) {
michael@0 940 if (aDeliveryStatus == kInternalDeliveryStatusNone) {
michael@0 941 return kDeliveryStatusNotApplicable;
michael@0 942 }
michael@0 943 if (aDeliveryStatus >= kInternalDeliveryStatusFailed) {
michael@0 944 return kDeliveryStatusError;
michael@0 945 }
michael@0 946 if (aDeliveryStatus >= kInternalDeliveryStatusPending) {
michael@0 947 return kDeliveryStatusPending;
michael@0 948 }
michael@0 949 return kDeliveryStatusSuccess;
michael@0 950 }
michael@0 951
michael@0 952 private int getGeckoMessageClass(MessageClass aMessageClass) {
michael@0 953 switch (aMessageClass) {
michael@0 954 case CLASS_0:
michael@0 955 return kMessageClassClass0;
michael@0 956 case CLASS_1:
michael@0 957 return kMessageClassClass1;
michael@0 958 case CLASS_2:
michael@0 959 return kMessageClassClass2;
michael@0 960 case CLASS_3:
michael@0 961 return kMessageClassClass3;
michael@0 962 default:
michael@0 963 return kMessageClassNormal;
michael@0 964 }
michael@0 965 }
michael@0 966
michael@0 967 class IdTooHighException extends Exception {
michael@0 968 private static final long serialVersionUID = 29935575131092050L;
michael@0 969 }
michael@0 970
michael@0 971 class InvalidTypeException extends Exception {
michael@0 972 private static final long serialVersionUID = 47436856832535912L;
michael@0 973 }
michael@0 974
michael@0 975 class NotFoundException extends Exception {
michael@0 976 private static final long serialVersionUID = 1940676816633984L;
michael@0 977 }
michael@0 978
michael@0 979 class TooManyResultsException extends Exception {
michael@0 980 private static final long serialVersionUID = 51883196784325305L;
michael@0 981 }
michael@0 982
michael@0 983 class UnexpectedDeliveryStateException extends Exception {
michael@0 984 private static final long serialVersionUID = 494122763684005716L;
michael@0 985 }
michael@0 986
michael@0 987 class UnmatchingIdException extends Exception {
michael@0 988 private static final long serialVersionUID = 158467542575633280L;
michael@0 989 }
michael@0 990
michael@0 991 private static native void notifySmsReceived(String aSender, String aBody, int aMessageClass, long aTimestamp);
michael@0 992 private static native void notifySmsSent(int aId, String aReceiver, String aBody, long aTimestamp, int aRequestId);
michael@0 993 private static native void notifySmsDelivery(int aId, int aDeliveryStatus, String aReceiver, String aBody, long aTimestamp);
michael@0 994 private static native void notifySmsSendFailed(int aError, int aRequestId);
michael@0 995 private static native void notifyGetSms(int aId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
michael@0 996 private static native void notifyGetSmsFailed(int aError, int aRequestId);
michael@0 997 private static native void notifySmsDeleted(boolean aDeleted, int aRequestId);
michael@0 998 private static native void notifySmsDeleteFailed(int aError, int aRequestId);
michael@0 999 private static native void notifyNoMessageInList(int aRequestId);
michael@0 1000 private static native void notifyListCreated(int aListId, int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
michael@0 1001 private static native void notifyGotNextMessage(int aMessageId, int aDeliveryStatus, String aReceiver, String aSender, String aBody, long aTimestamp, int aRequestId);
michael@0 1002 private static native void notifyReadingMessageListFailed(int aError, int aRequestId);
michael@0 1003 }

mercurial