embedding/android/GeckoSmsManager.java

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial