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