1 /* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mms.transaction; 19 20 import static android.content.Intent.ACTION_BOOT_COMPLETED; 21 import static android.provider.Telephony.Sms.Intents.SMS_DELIVER_ACTION; 22 23 import java.util.Calendar; 24 import java.util.GregorianCalendar; 25 26 import android.app.Activity; 27 import android.app.Service; 28 import android.content.ContentResolver; 29 import android.content.ContentUris; 30 import android.content.ContentValues; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.database.Cursor; 35 import android.database.sqlite.SqliteWrapper; 36 import android.net.Uri; 37 import android.os.Handler; 38 import android.os.HandlerThread; 39 import android.os.IBinder; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.Process; 43 import android.provider.Telephony.Sms; 44 import android.provider.Telephony.Sms.Inbox; 45 import android.provider.Telephony.Sms.Intents; 46 import android.provider.Telephony.Sms.Outbox; 47 import android.telephony.ServiceState; 48 import android.telephony.SmsManager; 49 import android.telephony.SmsMessage; 50 import android.text.TextUtils; 51 import android.util.Log; 52 import android.widget.Toast; 53 54 import com.android.internal.telephony.TelephonyIntents; 55 import com.android.mms.LogTag; 56 import com.android.mms.MmsConfig; 57 import com.android.mms.R; 58 import com.android.mms.data.Contact; 59 import com.android.mms.data.Conversation; 60 import com.android.mms.ui.ClassZeroActivity; 61 import com.android.mms.util.Recycler; 62 import com.android.mms.util.SendingProgressTokenManager; 63 import com.android.mms.widget.MmsWidgetProvider; 64 import com.google.android.mms.MmsException; 65 66 /** 67 * This service essentially plays the role of a "worker thread", allowing us to store 68 * incoming messages to the database, update notifications, etc. without blocking the 69 * main thread that SmsReceiver runs on. 70 */ 71 public class SmsReceiverService extends Service { 72 private static final String TAG = "SmsReceiverService"; 73 74 private ServiceHandler mServiceHandler; 75 private Looper mServiceLooper; 76 private boolean mSending; 77 78 public static final String MESSAGE_SENT_ACTION = 79 "com.android.mms.transaction.MESSAGE_SENT"; 80 81 // Indicates next message can be picked up and sent out. 82 public static final String EXTRA_MESSAGE_SENT_SEND_NEXT ="SendNextMsg"; 83 84 public static final String ACTION_SEND_MESSAGE = 85 "com.android.mms.transaction.SEND_MESSAGE"; 86 public static final String ACTION_SEND_INACTIVE_MESSAGE = 87 "com.android.mms.transaction.SEND_INACTIVE_MESSAGE"; 88 89 // This must match the column IDs below. 90 private static final String[] SEND_PROJECTION = new String[] { 91 Sms._ID, //0 92 Sms.THREAD_ID, //1 93 Sms.ADDRESS, //2 94 Sms.BODY, //3 95 Sms.STATUS, //4 96 97 }; 98 99 public Handler mToastHandler = new Handler(); 100 101 // This must match SEND_PROJECTION. 102 private static final int SEND_COLUMN_ID = 0; 103 private static final int SEND_COLUMN_THREAD_ID = 1; 104 private static final int SEND_COLUMN_ADDRESS = 2; 105 private static final int SEND_COLUMN_BODY = 3; 106 private static final int SEND_COLUMN_STATUS = 4; 107 108 private int mResultCode; 109 110 @Override 111 public void onCreate() { 112 // Temporarily removed for this duplicate message track down. 113 // if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 114 // Log.v(TAG, "onCreate"); 115 // } 116 117 // Start up the thread running the service. Note that we create a 118 // separate thread because the service normally runs in the process's 119 // main thread, which we don't want to block. 120 HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); 121 thread.start(); 122 123 mServiceLooper = thread.getLooper(); 124 mServiceHandler = new ServiceHandler(mServiceLooper); 125 } 126 127 @Override 128 public int onStartCommand(Intent intent, int flags, int startId) { 129 // Temporarily removed for this duplicate message track down. 130 131 mResultCode = intent != null ? intent.getIntExtra("result", 0) : 0; 132 133 if (mResultCode != 0) { 134 Log.v(TAG, "onStart: #" + startId + " mResultCode: " + mResultCode + 135 " = " + translateResultCode(mResultCode)); 136 } 137 138 Message msg = mServiceHandler.obtainMessage(); 139 msg.arg1 = startId; 140 msg.obj = intent; 141 mServiceHandler.sendMessage(msg); 142 return Service.START_NOT_STICKY; 143 } 144 145 private static String translateResultCode(int resultCode) { 146 switch (resultCode) { 147 case Activity.RESULT_OK: 148 return "Activity.RESULT_OK"; 149 case SmsManager.RESULT_ERROR_GENERIC_FAILURE: 150 return "SmsManager.RESULT_ERROR_GENERIC_FAILURE"; 151 case SmsManager.RESULT_ERROR_RADIO_OFF: 152 return "SmsManager.RESULT_ERROR_RADIO_OFF"; 153 case SmsManager.RESULT_ERROR_NULL_PDU: 154 return "SmsManager.RESULT_ERROR_NULL_PDU"; 155 case SmsManager.RESULT_ERROR_NO_SERVICE: 156 return "SmsManager.RESULT_ERROR_NO_SERVICE"; 157 case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED: 158 return "SmsManager.RESULT_ERROR_LIMIT_EXCEEDED"; 159 case SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE: 160 return "SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE"; 161 default: 162 return "Unknown error code"; 163 } 164 } 165 166 @Override 167 public void onDestroy() { 168 // Temporarily removed for this duplicate message track down. 169 // if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 170 // Log.v(TAG, "onDestroy"); 171 // } 172 mServiceLooper.quit(); 173 } 174 175 @Override 176 public IBinder onBind(Intent intent) { 177 return null; 178 } 179 180 private final class ServiceHandler extends Handler { 181 public ServiceHandler(Looper looper) { 182 super(looper); 183 } 184 185 /** 186 * Handle incoming transaction requests. 187 * The incoming requests are initiated by the MMSC Server or by the MMS Client itself. 188 */ 189 @Override 190 public void handleMessage(Message msg) { 191 int serviceId = msg.arg1; 192 Intent intent = (Intent)msg.obj; 193 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 194 Log.v(TAG, "handleMessage serviceId: " + serviceId + " intent: " + intent); 195 } 196 if (intent != null && MmsConfig.isSmsEnabled(getApplicationContext())) { 197 String action = intent.getAction(); 198 199 int error = intent.getIntExtra("errorCode", 0); 200 201 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 202 Log.v(TAG, "handleMessage action: " + action + " error: " + error); 203 } 204 205 if (MESSAGE_SENT_ACTION.equals(intent.getAction())) { 206 handleSmsSent(intent, error); 207 } else if (SMS_DELIVER_ACTION.equals(action)) { 208 handleSmsReceived(intent, error); 209 } else if (ACTION_BOOT_COMPLETED.equals(action)) { 210 handleBootCompleted(); 211 } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) { 212 handleServiceStateChanged(intent); 213 } else if (ACTION_SEND_MESSAGE.endsWith(action)) { 214 handleSendMessage(); 215 } else if (ACTION_SEND_INACTIVE_MESSAGE.equals(action)) { 216 handleSendInactiveMessage(); 217 } 218 } 219 // NOTE: We MUST not call stopSelf() directly, since we need to 220 // make sure the wake lock acquired by AlertReceiver is released. 221 SmsReceiver.finishStartingService(SmsReceiverService.this, serviceId); 222 } 223 } 224 225 private void handleServiceStateChanged(Intent intent) { 226 // If service just returned, start sending out the queued messages 227 ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras()); 228 if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) { 229 sendFirstQueuedMessage(); 230 } 231 } 232 233 private void handleSendMessage() { 234 if (!mSending) { 235 sendFirstQueuedMessage(); 236 } 237 } 238 239 private void handleSendInactiveMessage() { 240 // Inactive messages includes all messages in outbox and queued box. 241 moveOutboxMessagesToQueuedBox(); 242 sendFirstQueuedMessage(); 243 } 244 245 public synchronized void sendFirstQueuedMessage() { 246 boolean success = true; 247 // get all the queued messages from the database 248 final Uri uri = Uri.parse("content://sms/queued"); 249 ContentResolver resolver = getContentResolver(); 250 Cursor c = SqliteWrapper.query(this, resolver, uri, 251 SEND_PROJECTION, null, null, "date ASC"); // date ASC so we send out in 252 // same order the user tried 253 // to send messages. 254 if (c != null) { 255 try { 256 if (c.moveToFirst()) { 257 String msgText = c.getString(SEND_COLUMN_BODY); 258 String address = c.getString(SEND_COLUMN_ADDRESS); 259 int threadId = c.getInt(SEND_COLUMN_THREAD_ID); 260 int status = c.getInt(SEND_COLUMN_STATUS); 261 262 int msgId = c.getInt(SEND_COLUMN_ID); 263 Uri msgUri = ContentUris.withAppendedId(Sms.CONTENT_URI, msgId); 264 265 SmsMessageSender sender = new SmsSingleRecipientSender(this, 266 address, msgText, threadId, status == Sms.STATUS_PENDING, 267 msgUri); 268 269 if (LogTag.DEBUG_SEND || 270 LogTag.VERBOSE || 271 Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 272 Log.v(TAG, "sendFirstQueuedMessage " + msgUri + 273 ", address: " + address + 274 ", threadId: " + threadId); 275 } 276 277 try { 278 sender.sendMessage(SendingProgressTokenManager.NO_TOKEN);; 279 mSending = true; 280 } catch (MmsException e) { 281 Log.e(TAG, "sendFirstQueuedMessage: failed to send message " + msgUri 282 + ", caught ", e); 283 mSending = false; 284 messageFailedToSend(msgUri, SmsManager.RESULT_ERROR_GENERIC_FAILURE); 285 success = false; 286 // Sending current message fails. Try to send more pending messages 287 // if there is any. 288 sendBroadcast(new Intent(SmsReceiverService.ACTION_SEND_MESSAGE, 289 null, 290 this, 291 SmsReceiver.class)); 292 } 293 } 294 } finally { 295 c.close(); 296 } 297 } 298 if (success) { 299 // We successfully sent all the messages in the queue. We don't need to 300 // be notified of any service changes any longer. 301 unRegisterForServiceStateChanges(); 302 } 303 } 304 305 private void handleSmsSent(Intent intent, int error) { 306 Uri uri = intent.getData(); 307 mSending = false; 308 boolean sendNextMsg = intent.getBooleanExtra(EXTRA_MESSAGE_SENT_SEND_NEXT, false); 309 310 if (LogTag.DEBUG_SEND) { 311 Log.v(TAG, "handleSmsSent uri: " + uri + " sendNextMsg: " + sendNextMsg + 312 " mResultCode: " + mResultCode + 313 " = " + translateResultCode(mResultCode) + " error: " + error); 314 } 315 316 if (mResultCode == Activity.RESULT_OK) { 317 if (LogTag.DEBUG_SEND || Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 318 Log.v(TAG, "handleSmsSent move message to sent folder uri: " + uri); 319 } 320 if (!Sms.moveMessageToFolder(this, uri, Sms.MESSAGE_TYPE_SENT, error)) { 321 Log.e(TAG, "handleSmsSent: failed to move message " + uri + " to sent folder"); 322 } 323 if (sendNextMsg) { 324 sendFirstQueuedMessage(); 325 } 326 327 // Update the notification for failed messages since they may be deleted. 328 MessagingNotification.nonBlockingUpdateSendFailedNotification(this); 329 } else if ((mResultCode == SmsManager.RESULT_ERROR_RADIO_OFF) || 330 (mResultCode == SmsManager.RESULT_ERROR_NO_SERVICE)) { 331 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 332 Log.v(TAG, "handleSmsSent: no service, queuing message w/ uri: " + uri); 333 } 334 // We got an error with no service or no radio. Register for state changes so 335 // when the status of the connection/radio changes, we can try to send the 336 // queued up messages. 337 registerForServiceStateChanges(); 338 // We couldn't send the message, put in the queue to retry later. 339 Sms.moveMessageToFolder(this, uri, Sms.MESSAGE_TYPE_QUEUED, error); 340 mToastHandler.post(new Runnable() { 341 public void run() { 342 Toast.makeText(SmsReceiverService.this, getString(R.string.message_queued), 343 Toast.LENGTH_SHORT).show(); 344 } 345 }); 346 } else if (mResultCode == SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE) { 347 messageFailedToSend(uri, mResultCode); 348 mToastHandler.post(new Runnable() { 349 public void run() { 350 Toast.makeText(SmsReceiverService.this, getString(R.string.fdn_check_failure), 351 Toast.LENGTH_SHORT).show(); 352 } 353 }); 354 } else { 355 messageFailedToSend(uri, error); 356 if (sendNextMsg) { 357 sendFirstQueuedMessage(); 358 } 359 } 360 } 361 362 private void messageFailedToSend(Uri uri, int error) { 363 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 364 Log.v(TAG, "messageFailedToSend msg failed uri: " + uri + " error: " + error); 365 } 366 Sms.moveMessageToFolder(this, uri, Sms.MESSAGE_TYPE_FAILED, error); 367 MessagingNotification.notifySendFailed(getApplicationContext(), true); 368 } 369 370 private void handleSmsReceived(Intent intent, int error) { 371 SmsMessage[] msgs = Intents.getMessagesFromIntent(intent); 372 String format = intent.getStringExtra("format"); 373 Uri messageUri = insertMessage(this, msgs, error, format); 374 375 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 376 SmsMessage sms = msgs[0]; 377 Log.v(TAG, "handleSmsReceived" + (sms.isReplace() ? "(replace)" : "") + 378 " messageUri: " + messageUri + 379 ", address: " + sms.getOriginatingAddress() + 380 ", body: " + sms.getMessageBody()); 381 } 382 383 if (messageUri != null) { 384 long threadId = MessagingNotification.getSmsThreadId(this, messageUri); 385 // Called off of the UI thread so ok to block. 386 Log.d(TAG, "handleSmsReceived messageUri: " + messageUri + " threadId: " + threadId); 387 MessagingNotification.blockingUpdateNewMessageIndicator(this, threadId, false); 388 } 389 } 390 391 private void handleBootCompleted() { 392 // Some messages may get stuck in the outbox. At this point, they're probably irrelevant 393 // to the user, so mark them as failed and notify the user, who can then decide whether to 394 // resend them manually. 395 int numMoved = moveOutboxMessagesToFailedBox(); 396 if (numMoved > 0) { 397 MessagingNotification.notifySendFailed(getApplicationContext(), true); 398 } 399 400 // Send any queued messages that were waiting from before the reboot. 401 sendFirstQueuedMessage(); 402 403 // Called off of the UI thread so ok to block. 404 MessagingNotification.blockingUpdateNewMessageIndicator( 405 this, MessagingNotification.THREAD_ALL, false); 406 } 407 408 /** 409 * Move all messages that are in the outbox to the queued state 410 * @return The number of messages that were actually moved 411 */ 412 private int moveOutboxMessagesToQueuedBox() { 413 ContentValues values = new ContentValues(1); 414 415 values.put(Sms.TYPE, Sms.MESSAGE_TYPE_QUEUED); 416 417 int messageCount = SqliteWrapper.update( 418 getApplicationContext(), getContentResolver(), Outbox.CONTENT_URI, 419 values, "type = " + Sms.MESSAGE_TYPE_OUTBOX, null); 420 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 421 Log.v(TAG, "moveOutboxMessagesToQueuedBox messageCount: " + messageCount); 422 } 423 return messageCount; 424 } 425 426 /** 427 * Move all messages that are in the outbox to the failed state and set them to unread. 428 * @return The number of messages that were actually moved 429 */ 430 private int moveOutboxMessagesToFailedBox() { 431 ContentValues values = new ContentValues(3); 432 433 values.put(Sms.TYPE, Sms.MESSAGE_TYPE_FAILED); 434 values.put(Sms.ERROR_CODE, SmsManager.RESULT_ERROR_GENERIC_FAILURE); 435 values.put(Sms.READ, Integer.valueOf(0)); 436 437 int messageCount = SqliteWrapper.update( 438 getApplicationContext(), getContentResolver(), Outbox.CONTENT_URI, 439 values, "type = " + Sms.MESSAGE_TYPE_OUTBOX, null); 440 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 441 Log.v(TAG, "moveOutboxMessagesToFailedBox messageCount: " + messageCount); 442 } 443 return messageCount; 444 } 445 446 public static final String CLASS_ZERO_BODY_KEY = "CLASS_ZERO_BODY"; 447 448 // This must match the column IDs below. 449 private final static String[] REPLACE_PROJECTION = new String[] { 450 Sms._ID, 451 Sms.ADDRESS, 452 Sms.PROTOCOL 453 }; 454 455 // This must match REPLACE_PROJECTION. 456 private static final int REPLACE_COLUMN_ID = 0; 457 458 /** 459 * If the message is a class-zero message, display it immediately 460 * and return null. Otherwise, store it using the 461 * <code>ContentResolver</code> and return the 462 * <code>Uri</code> of the thread containing this message 463 * so that we can use it for notification. 464 */ 465 private Uri insertMessage(Context context, SmsMessage[] msgs, int error, String format) { 466 // Build the helper classes to parse the messages. 467 SmsMessage sms = msgs[0]; 468 469 if (sms.getMessageClass() == SmsMessage.MessageClass.CLASS_0) { 470 displayClassZeroMessage(context, sms, format); 471 return null; 472 } else if (sms.isReplace()) { 473 return replaceMessage(context, msgs, error); 474 } else { 475 return storeMessage(context, msgs, error); 476 } 477 } 478 479 /** 480 * This method is used if this is a "replace short message" SMS. 481 * We find any existing message that matches the incoming 482 * message's originating address and protocol identifier. If 483 * there is one, we replace its fields with those of the new 484 * message. Otherwise, we store the new message as usual. 485 * 486 * See TS 23.040 9.2.3.9. 487 */ 488 private Uri replaceMessage(Context context, SmsMessage[] msgs, int error) { 489 SmsMessage sms = msgs[0]; 490 ContentValues values = extractContentValues(sms); 491 values.put(Sms.ERROR_CODE, error); 492 int pduCount = msgs.length; 493 494 if (pduCount == 1) { 495 // There is only one part, so grab the body directly. 496 values.put(Inbox.BODY, replaceFormFeeds(sms.getDisplayMessageBody())); 497 } else { 498 // Build up the body from the parts. 499 StringBuilder body = new StringBuilder(); 500 for (int i = 0; i < pduCount; i++) { 501 sms = msgs[i]; 502 if (sms.mWrappedSmsMessage != null) { 503 body.append(sms.getDisplayMessageBody()); 504 } 505 } 506 values.put(Inbox.BODY, replaceFormFeeds(body.toString())); 507 } 508 509 ContentResolver resolver = context.getContentResolver(); 510 String originatingAddress = sms.getOriginatingAddress(); 511 int protocolIdentifier = sms.getProtocolIdentifier(); 512 String selection = 513 Sms.ADDRESS + " = ? AND " + 514 Sms.PROTOCOL + " = ?"; 515 String[] selectionArgs = new String[] { 516 originatingAddress, Integer.toString(protocolIdentifier) 517 }; 518 519 Cursor cursor = SqliteWrapper.query(context, resolver, Inbox.CONTENT_URI, 520 REPLACE_PROJECTION, selection, selectionArgs, null); 521 522 if (cursor != null) { 523 try { 524 if (cursor.moveToFirst()) { 525 long messageId = cursor.getLong(REPLACE_COLUMN_ID); 526 Uri messageUri = ContentUris.withAppendedId( 527 Sms.CONTENT_URI, messageId); 528 529 SqliteWrapper.update(context, resolver, messageUri, 530 values, null, null); 531 return messageUri; 532 } 533 } finally { 534 cursor.close(); 535 } 536 } 537 return storeMessage(context, msgs, error); 538 } 539 540 public static String replaceFormFeeds(String s) { 541 // Some providers send formfeeds in their messages. Convert those formfeeds to newlines. 542 return s == null ? "" : s.replace('\f', '\n'); 543 } 544 545 // private static int count = 0; 546 547 private Uri storeMessage(Context context, SmsMessage[] msgs, int error) { 548 SmsMessage sms = msgs[0]; 549 550 // Store the message in the content provider. 551 ContentValues values = extractContentValues(sms); 552 values.put(Sms.ERROR_CODE, error); 553 int pduCount = msgs.length; 554 555 if (pduCount == 1) { 556 // There is only one part, so grab the body directly. 557 values.put(Inbox.BODY, replaceFormFeeds(sms.getDisplayMessageBody())); 558 } else { 559 // Build up the body from the parts. 560 StringBuilder body = new StringBuilder(); 561 for (int i = 0; i < pduCount; i++) { 562 sms = msgs[i]; 563 if (sms.mWrappedSmsMessage != null) { 564 body.append(sms.getDisplayMessageBody()); 565 } 566 } 567 values.put(Inbox.BODY, replaceFormFeeds(body.toString())); 568 } 569 570 // Make sure we've got a thread id so after the insert we'll be able to delete 571 // excess messages. 572 Long threadId = values.getAsLong(Sms.THREAD_ID); 573 String address = values.getAsString(Sms.ADDRESS); 574 575 // Code for debugging and easy injection of short codes, non email addresses, etc. 576 // See Contact.isAlphaNumber() for further comments and results. 577 // switch (count++ % 8) { 578 // case 0: address = "AB12"; break; 579 // case 1: address = "12"; break; 580 // case 2: address = "Jello123"; break; 581 // case 3: address = "T-Mobile"; break; 582 // case 4: address = "Mobile1"; break; 583 // case 5: address = "Dogs77"; break; 584 // case 6: address = "****1"; break; 585 // case 7: address = "#4#5#6#"; break; 586 // } 587 588 if (!TextUtils.isEmpty(address)) { 589 Contact cacheContact = Contact.get(address,true); 590 if (cacheContact != null) { 591 address = cacheContact.getNumber(); 592 } 593 } else { 594 address = getString(R.string.unknown_sender); 595 values.put(Sms.ADDRESS, address); 596 } 597 598 if (((threadId == null) || (threadId == 0)) && (address != null)) { 599 threadId = Conversation.getOrCreateThreadId(context, address); 600 values.put(Sms.THREAD_ID, threadId); 601 } 602 603 ContentResolver resolver = context.getContentResolver(); 604 605 Uri insertedUri = SqliteWrapper.insert(context, resolver, Inbox.CONTENT_URI, values); 606 607 // Now make sure we're not over the limit in stored messages 608 Recycler.getSmsRecycler().deleteOldMessagesByThreadId(context, threadId); 609 MmsWidgetProvider.notifyDatasetChanged(context); 610 611 return insertedUri; 612 } 613 614 /** 615 * Extract all the content values except the body from an SMS 616 * message. 617 */ 618 private ContentValues extractContentValues(SmsMessage sms) { 619 // Store the message in the content provider. 620 ContentValues values = new ContentValues(); 621 622 values.put(Inbox.ADDRESS, sms.getDisplayOriginatingAddress()); 623 624 // Use now for the timestamp to avoid confusion with clock 625 // drift between the handset and the SMSC. 626 // Check to make sure the system is giving us a non-bogus time. 627 Calendar buildDate = new GregorianCalendar(2011, 8, 18); // 18 Sep 2011 628 Calendar nowDate = new GregorianCalendar(); 629 long now = System.currentTimeMillis(); 630 nowDate.setTimeInMillis(now); 631 632 if (nowDate.before(buildDate)) { 633 // It looks like our system clock isn't set yet because the current time right now 634 // is before an arbitrary time we made this build. Instead of inserting a bogus 635 // receive time in this case, use the timestamp of when the message was sent. 636 now = sms.getTimestampMillis(); 637 } 638 639 values.put(Inbox.DATE, new Long(now)); 640 values.put(Inbox.DATE_SENT, Long.valueOf(sms.getTimestampMillis())); 641 values.put(Inbox.PROTOCOL, sms.getProtocolIdentifier()); 642 values.put(Inbox.READ, 0); 643 values.put(Inbox.SEEN, 0); 644 if (sms.getPseudoSubject().length() > 0) { 645 values.put(Inbox.SUBJECT, sms.getPseudoSubject()); 646 } 647 values.put(Inbox.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0); 648 values.put(Inbox.SERVICE_CENTER, sms.getServiceCenterAddress()); 649 return values; 650 } 651 652 /** 653 * Displays a class-zero message immediately in a pop-up window 654 * with the number from where it received the Notification with 655 * the body of the message 656 * 657 */ 658 private void displayClassZeroMessage(Context context, SmsMessage sms, String format) { 659 // Using NEW_TASK here is necessary because we're calling 660 // startActivity from outside an activity. 661 Intent smsDialogIntent = new Intent(context, ClassZeroActivity.class) 662 .putExtra("pdu", sms.getPdu()) 663 .putExtra("format", format) 664 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 665 | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); 666 667 context.startActivity(smsDialogIntent); 668 } 669 670 private void registerForServiceStateChanges() { 671 Context context = getApplicationContext(); 672 unRegisterForServiceStateChanges(); 673 674 IntentFilter intentFilter = new IntentFilter(); 675 intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED); 676 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 677 Log.v(TAG, "registerForServiceStateChanges"); 678 } 679 680 context.registerReceiver(SmsReceiver.getInstance(), intentFilter); 681 } 682 683 private void unRegisterForServiceStateChanges() { 684 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) { 685 Log.v(TAG, "unRegisterForServiceStateChanges"); 686 } 687 try { 688 Context context = getApplicationContext(); 689 context.unregisterReceiver(SmsReceiver.getInstance()); 690 } catch (IllegalArgumentException e) { 691 // Allow un-matched register-unregister calls 692 } 693 } 694 } 695 696 697