• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onCreate()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
onStartCommand(Intent intent, int flags, int startId)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 
translateResultCode(int resultCode)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
onDestroy()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
onBind(Intent intent)175     public IBinder onBind(Intent intent) {
176         return null;
177     }
178 
179     private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper)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
handleMessage(Message msg)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 
handleServiceStateChanged(Intent intent)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 
handleSendMessage()232     private void handleSendMessage() {
233         if (!mSending) {
234             sendFirstQueuedMessage();
235         }
236     }
237 
handleSendInactiveMessage()238     private void handleSendInactiveMessage() {
239         // Inactive messages includes all messages in outbox and queued box.
240         moveOutboxMessagesToQueuedBox();
241         sendFirstQueuedMessage();
242     }
243 
sendFirstQueuedMessage()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 
handleSmsSent(Intent intent, int error)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 
messageFailedToSend(Uri uri, int error)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 
handleSmsReceived(Intent intent, int error)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 
handleBootCompleted()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      */
moveOutboxMessagesToQueuedBox()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      */
moveOutboxMessagesToFailedBox()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      */
insertMessage(Context context, SmsMessage[] msgs, int error, String format)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      */
replaceMessage(Context context, SmsMessage[] msgs, int error)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 
replaceFormFeeds(String s)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 
storeMessage(Context context, SmsMessage[] msgs, int error)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      */
extractContentValues(SmsMessage sms)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      */
displayClassZeroMessage(Context context, SmsMessage sms, String format)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 
registerForServiceStateChanges()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 
unRegisterForServiceStateChanges()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