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_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
    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
    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 
    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
    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
    183     public IBinder onBind(Intent intent) {
    184         return null;
    185     }
    186 
    187     private final class ServiceHandler extends Handler {
    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
    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 
    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 
    240     private void handleSendMessage() {
    241         if (!mSending) {
    242             sendFirstQueuedMessage();
    243         }
    244     }
    245 
    246     private void handleSendInactiveMessage() {
    247         // Inactive messages includes all messages in outbox and queued box.
    248         moveOutboxMessagesToQueuedBox();
    249         sendFirstQueuedMessage();
    250     }
    251 
    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 
    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 
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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      */
    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      */
    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 
    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 
    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