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