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