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