SmsReceiverService.java revision c7aa632be8e7d3ebe71f236f534ea2f0af71e04a
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_RECEIVED_ACTION;
22
23import java.util.Calendar;
24import java.util.GregorianCalendar;
25
26import com.android.mms.data.Contact;
27import com.android.mms.ui.ClassZeroActivity;
28import com.android.mms.util.Recycler;
29import com.android.mms.util.SendingProgressTokenManager;
30import com.android.mms.widget.MmsWidgetProvider;
31import com.google.android.mms.MmsException;
32import android.database.sqlite.SqliteWrapper;
33
34import android.app.Activity;
35import android.app.Service;
36import android.content.ContentResolver;
37import android.content.ContentUris;
38import android.content.ContentValues;
39import android.content.Context;
40import android.content.Intent;
41import android.content.IntentFilter;
42import android.database.Cursor;
43import android.net.Uri;
44import android.os.Handler;
45import android.os.HandlerThread;
46import android.os.IBinder;
47import android.os.Looper;
48import android.os.Message;
49import android.os.Process;
50import android.provider.Telephony.Sms;
51import android.provider.Telephony.Threads;
52import android.provider.Telephony.Sms.Inbox;
53import android.provider.Telephony.Sms.Intents;
54import android.provider.Telephony.Sms.Outbox;
55import android.telephony.ServiceState;
56import android.telephony.SmsManager;
57import android.telephony.SmsMessage;
58import android.text.TextUtils;
59import android.util.Log;
60import android.widget.Toast;
61
62import com.android.internal.telephony.TelephonyIntents;
63import com.android.mms.R;
64import com.android.mms.LogTag;
65
66/**
67 * This service essentially plays the role of a "worker thread", allowing us to store
68 * incoming messages to the database, update notifications, etc. without blocking the
69 * main thread that SmsReceiver runs on.
70 */
71public class SmsReceiverService extends Service {
72    private static final String TAG = "SmsReceiverService";
73
74    private ServiceHandler mServiceHandler;
75    private Looper mServiceLooper;
76    private boolean mSending;
77
78    public static final String MESSAGE_SENT_ACTION =
79        "com.android.mms.transaction.MESSAGE_SENT";
80
81    // Indicates next message can be picked up and sent out.
82    public static final String EXTRA_MESSAGE_SENT_SEND_NEXT ="SendNextMsg";
83
84    public static final String ACTION_SEND_MESSAGE =
85        "com.android.mms.transaction.SEND_MESSAGE";
86
87    // This must match the column IDs below.
88    private static final String[] SEND_PROJECTION = new String[] {
89        Sms._ID,        //0
90        Sms.THREAD_ID,  //1
91        Sms.ADDRESS,    //2
92        Sms.BODY,       //3
93        Sms.STATUS,     //4
94
95    };
96
97    public Handler mToastHandler = new Handler();
98
99    // This must match SEND_PROJECTION.
100    private static final int SEND_COLUMN_ID         = 0;
101    private static final int SEND_COLUMN_THREAD_ID  = 1;
102    private static final int SEND_COLUMN_ADDRESS    = 2;
103    private static final int SEND_COLUMN_BODY       = 3;
104    private static final int SEND_COLUMN_STATUS     = 4;
105
106    private int mResultCode;
107
108    @Override
109    public void onCreate() {
110        // Temporarily removed for this duplicate message track down.
111//        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
112//            Log.v(TAG, "onCreate");
113//        }
114
115        // Start up the thread running the service.  Note that we create a
116        // separate thread because the service normally runs in the process's
117        // main thread, which we don't want to block.
118        HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
119        thread.start();
120
121        mServiceLooper = thread.getLooper();
122        mServiceHandler = new ServiceHandler(mServiceLooper);
123    }
124
125    @Override
126    public int onStartCommand(Intent intent, int flags, int startId) {
127        // Temporarily removed for this duplicate message track down.
128
129        mResultCode = intent != null ? intent.getIntExtra("result", 0) : 0;
130
131        if (mResultCode != 0) {
132            Log.v(TAG, "onStart: #" + startId + " mResultCode: " + mResultCode +
133                    " = " + translateResultCode(mResultCode));
134        }
135
136        Message msg = mServiceHandler.obtainMessage();
137        msg.arg1 = startId;
138        msg.obj = intent;
139        mServiceHandler.sendMessage(msg);
140        return Service.START_NOT_STICKY;
141    }
142
143    private static String translateResultCode(int resultCode) {
144        switch (resultCode) {
145            case Activity.RESULT_OK:
146                return "Activity.RESULT_OK";
147            case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
148                return "SmsManager.RESULT_ERROR_GENERIC_FAILURE";
149            case SmsManager.RESULT_ERROR_RADIO_OFF:
150                return "SmsManager.RESULT_ERROR_RADIO_OFF";
151            case SmsManager.RESULT_ERROR_NULL_PDU:
152                return "SmsManager.RESULT_ERROR_NULL_PDU";
153            case SmsManager.RESULT_ERROR_NO_SERVICE:
154                return "SmsManager.RESULT_ERROR_NO_SERVICE";
155            case SmsManager.RESULT_ERROR_LIMIT_EXCEEDED:
156                return "SmsManager.RESULT_ERROR_LIMIT_EXCEEDED";
157            case SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE:
158                return "SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE";
159            default:
160                return "Unknown error code";
161        }
162    }
163
164    @Override
165    public void onDestroy() {
166        // Temporarily removed for this duplicate message track down.
167//        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
168//            Log.v(TAG, "onDestroy");
169//        }
170        mServiceLooper.quit();
171    }
172
173    @Override
174    public IBinder onBind(Intent intent) {
175        return null;
176    }
177
178    private final class ServiceHandler extends Handler {
179        public ServiceHandler(Looper looper) {
180            super(looper);
181        }
182
183        /**
184         * Handle incoming transaction requests.
185         * The incoming requests are initiated by the MMSC Server or by the MMS Client itself.
186         */
187        @Override
188        public void handleMessage(Message msg) {
189            int serviceId = msg.arg1;
190            Intent intent = (Intent)msg.obj;
191            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
192                Log.v(TAG, "handleMessage serviceId: " + serviceId + " intent: " + intent);
193            }
194            if (intent != null) {
195                String action = intent.getAction();
196
197                int error = intent.getIntExtra("errorCode", 0);
198
199                if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
200                    Log.v(TAG, "handleMessage action: " + action + " error: " + error);
201                }
202
203                if (MESSAGE_SENT_ACTION.equals(intent.getAction())) {
204                    handleSmsSent(intent, error);
205                } else if (SMS_RECEIVED_ACTION.equals(action)) {
206                    handleSmsReceived(intent, error);
207                } else if (ACTION_BOOT_COMPLETED.equals(action)) {
208                    handleBootCompleted();
209                } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
210                    handleServiceStateChanged(intent);
211                } else if (ACTION_SEND_MESSAGE.endsWith(action)) {
212                    handleSendMessage();
213                }
214            }
215            // NOTE: We MUST not call stopSelf() directly, since we need to
216            // make sure the wake lock acquired by AlertReceiver is released.
217            SmsReceiver.finishStartingService(SmsReceiverService.this, serviceId);
218        }
219    }
220
221    private void handleServiceStateChanged(Intent intent) {
222        // If service just returned, start sending out the queued messages
223        ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
224        if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
225            sendFirstQueuedMessage();
226        }
227    }
228
229    private void handleSendMessage() {
230        if (!mSending) {
231            sendFirstQueuedMessage();
232        }
233    }
234
235    public synchronized void sendFirstQueuedMessage() {
236        boolean success = true;
237        // get all the queued messages from the database
238        final Uri uri = Uri.parse("content://sms/queued");
239        ContentResolver resolver = getContentResolver();
240        Cursor c = SqliteWrapper.query(this, resolver, uri,
241                        SEND_PROJECTION, null, null, "date ASC");   // date ASC so we send out in
242                                                                    // same order the user tried
243                                                                    // to send messages.
244        if (c != null) {
245            try {
246                if (c.moveToFirst()) {
247                    String msgText = c.getString(SEND_COLUMN_BODY);
248                    String address = c.getString(SEND_COLUMN_ADDRESS);
249                    int threadId = c.getInt(SEND_COLUMN_THREAD_ID);
250                    int status = c.getInt(SEND_COLUMN_STATUS);
251
252                    int msgId = c.getInt(SEND_COLUMN_ID);
253                    Uri msgUri = ContentUris.withAppendedId(Sms.CONTENT_URI, msgId);
254
255                    SmsMessageSender sender = new SmsSingleRecipientSender(this,
256                            address, msgText, threadId, status == Sms.STATUS_PENDING,
257                            msgUri);
258
259                    if (LogTag.DEBUG_SEND ||
260                            LogTag.VERBOSE ||
261                            Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
262                        Log.v(TAG, "sendFirstQueuedMessage " + msgUri +
263                                ", address: " + address +
264                                ", threadId: " + threadId);
265                    }
266
267                    try {
268                        sender.sendMessage(SendingProgressTokenManager.NO_TOKEN);;
269                        mSending = true;
270                    } catch (MmsException e) {
271                        Log.e(TAG, "sendFirstQueuedMessage: failed to send message " + msgUri
272                                + ", caught ", e);
273                        mSending = false;
274                        messageFailedToSend(msgUri, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
275                        success = false;
276                    }
277                }
278            } finally {
279                c.close();
280            }
281        }
282        if (success) {
283            // We successfully sent all the messages in the queue. We don't need to
284            // be notified of any service changes any longer.
285            unRegisterForServiceStateChanges();
286        }
287    }
288
289    private void handleSmsSent(Intent intent, int error) {
290        Uri uri = intent.getData();
291        mSending = false;
292        boolean sendNextMsg = intent.getBooleanExtra(EXTRA_MESSAGE_SENT_SEND_NEXT, false);
293
294        if (LogTag.DEBUG_SEND) {
295            Log.v(TAG, "handleSmsSent uri: " + uri + " sendNextMsg: " + sendNextMsg +
296                    " mResultCode: " + mResultCode +
297                    " = " + translateResultCode(mResultCode) + " error: " + error);
298        }
299
300        if (mResultCode == Activity.RESULT_OK) {
301            if (LogTag.DEBUG_SEND || Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
302                Log.v(TAG, "handleSmsSent move message to sent folder uri: " + uri);
303            }
304            if (!Sms.moveMessageToFolder(this, uri, Sms.MESSAGE_TYPE_SENT, error)) {
305                Log.e(TAG, "handleSmsSent: failed to move message " + uri + " to sent folder");
306            }
307            if (sendNextMsg) {
308                sendFirstQueuedMessage();
309            }
310
311            // Update the notification for failed messages since they may be deleted.
312            MessagingNotification.updateSendFailedNotification(this);
313        } else if ((mResultCode == SmsManager.RESULT_ERROR_RADIO_OFF) ||
314                (mResultCode == SmsManager.RESULT_ERROR_NO_SERVICE)) {
315            if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
316                Log.v(TAG, "handleSmsSent: no service, queuing message w/ uri: " + uri);
317            }
318            // We got an error with no service or no radio. Register for state changes so
319            // when the status of the connection/radio changes, we can try to send the
320            // queued up messages.
321            registerForServiceStateChanges();
322            // We couldn't send the message, put in the queue to retry later.
323            Sms.moveMessageToFolder(this, uri, Sms.MESSAGE_TYPE_QUEUED, error);
324            mToastHandler.post(new Runnable() {
325                public void run() {
326                    Toast.makeText(SmsReceiverService.this, getString(R.string.message_queued),
327                            Toast.LENGTH_SHORT).show();
328                }
329            });
330        } else if (mResultCode == SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE) {
331            mToastHandler.post(new Runnable() {
332                public void run() {
333                    Toast.makeText(SmsReceiverService.this, getString(R.string.fdn_check_failure),
334                            Toast.LENGTH_SHORT).show();
335                }
336            });
337        } else {
338            messageFailedToSend(uri, error);
339            if (sendNextMsg) {
340                sendFirstQueuedMessage();
341            }
342        }
343    }
344
345    private void messageFailedToSend(Uri uri, int error) {
346        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
347            Log.v(TAG, "messageFailedToSend msg failed uri: " + uri + " error: " + error);
348        }
349        Sms.moveMessageToFolder(this, uri, Sms.MESSAGE_TYPE_FAILED, error);
350        MessagingNotification.notifySendFailed(getApplicationContext(), true);
351    }
352
353    private void handleSmsReceived(Intent intent, int error) {
354        SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);
355        String format = intent.getStringExtra("format");
356        Uri messageUri = insertMessage(this, msgs, error, format);
357
358        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
359            SmsMessage sms = msgs[0];
360            Log.v(TAG, "handleSmsReceived" + (sms.isReplace() ? "(replace)" : "") +
361                    " messageUri: " + messageUri +
362                    ", address: " + sms.getOriginatingAddress() +
363                    ", body: " + sms.getMessageBody());
364        }
365
366        if (messageUri != null) {
367            long threadId = MessagingNotification.getSmsThreadId(this, messageUri);
368            // Called off of the UI thread so ok to block.
369            MessagingNotification.blockingUpdateNewMessageIndicator(this, threadId, false);
370        }
371    }
372
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     */
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     */
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     */
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
504    public static String replaceFormFeeds(String s) {
505        // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
506        return s.replace('\f', '\n');
507    }
508
509//    private static int count = 0;
510
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 = Threads.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     */
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     */
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
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
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