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