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