Home | History | Annotate | Download | only in transaction
      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