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