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