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
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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 
translateResultCode(int resultCode)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
onDestroy()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
onBind(Intent intent)183     public IBinder onBind(Intent intent) {
184         return null;
185     }
186 
187     private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper)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
handleMessage(Message msg)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 
handleServiceStateChanged(Intent intent)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 
handleSendMessage()240     private void handleSendMessage() {
241         if (!mSending) {
242             sendFirstQueuedMessage();
243         }
244     }
245 
handleSendInactiveMessage()246     private void handleSendInactiveMessage() {
247         // Inactive messages includes all messages in outbox and queued box.
248         moveOutboxMessagesToQueuedBox();
249         sendFirstQueuedMessage();
250     }
251 
sendFirstQueuedMessage()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 
handleSmsSent(Intent intent, int error)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 
messageFailedToSend(Uri uri, int error)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 
handleSmsReceived(Intent intent, int error)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 
handleBootCompleted()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      */
moveOutboxMessagesToQueuedBox()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      */
moveOutboxMessagesToFailedBox()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      */
insertMessage(Context context, SmsMessage[] msgs, int error, String format)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      */
replaceMessage(Context context, SmsMessage[] msgs, int error)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 
replaceFormFeeds(String s)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 
storeMessage(Context context, SmsMessage[] msgs, int error)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      */
extractContentValues(SmsMessage sms)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      */
displayClassZeroMessage(Context context, SmsMessage sms, String format)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 
registerForServiceStateChanges()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 
unRegisterForServiceStateChanges()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