SmsReceiverService.java revision 7015fa3f96383fa8a34d6b9e28f61d228e234d7e
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.google.android.mms.MmsException;
31import android.database.sqlite.SqliteWrapper;
32
33import android.app.Activity;
34import android.app.Service;
35import android.content.ContentResolver;
36import android.content.ContentUris;
37import android.content.ContentValues;
38import android.content.Context;
39import android.content.Intent;
40import android.content.IntentFilter;
41import android.database.Cursor;
42import android.net.Uri;
43import android.os.Handler;
44import android.os.HandlerThread;
45import android.os.IBinder;
46import android.os.Looper;
47import android.os.Message;
48import android.os.Process;
49import android.provider.Telephony.Sms;
50import android.provider.Telephony.Threads;
51import android.provider.Telephony.Sms.Inbox;
52import android.provider.Telephony.Sms.Intents;
53import android.provider.Telephony.Sms.Outbox;
54import android.telephony.ServiceState;
55import android.telephony.SmsManager;
56import android.telephony.SmsMessage;
57import android.text.TextUtils;
58import android.util.Log;
59import android.widget.Toast;
60
61import com.android.internal.telephony.TelephonyIntents;
62import com.android.mms.R;
63import com.android.mms.LogTag;
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 */
70public 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
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
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
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
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
173    public IBinder onBind(Intent intent) {
174        return null;
175    }
176
177    private final class ServiceHandler extends Handler {
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
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
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
228    private void handleSendMessage() {
229        if (!mSending) {
230            sendFirstQueuedMessage();
231        }
232    }
233
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
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.updateSendFailedNotification(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
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
352    private void handleSmsReceived(Intent intent, int error) {
353        SmsMessage[] msgs = Intents.getMessagesFromIntent(intent);
354        Uri messageUri = insertMessage(this, msgs, error);
355
356        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
357            SmsMessage sms = msgs[0];
358            Log.v(TAG, "handleSmsReceived" + (sms.isReplace() ? "(replace)" : "") +
359                    " messageUri: " + messageUri +
360                    ", address: " + sms.getOriginatingAddress() +
361                    ", body: " + sms.getMessageBody());
362        }
363
364        if (messageUri != null) {
365            // Called off of the UI thread so ok to block.
366            MessagingNotification.blockingUpdateNewMessageIndicator(this, true, false);
367        }
368    }
369
370    private void handleBootCompleted() {
371        // Some messages may get stuck in the outbox. At this point, they're probably irrelevant
372        // to the user, so mark them as failed and notify the user, who can then decide whether to
373        // resend them manually.
374        int numMoved = moveOutboxMessagesToFailedBox();
375        if (numMoved > 0) {
376            MessagingNotification.notifySendFailed(getApplicationContext(), true);
377        }
378
379        // Send any queued messages that were waiting from before the reboot.
380        sendFirstQueuedMessage();
381
382        // Called off of the UI thread so ok to block.
383        MessagingNotification.blockingUpdateNewMessageIndicator(this, true, false);
384    }
385
386    /**
387     * Move all messages that are in the outbox to the failed state and set them to unread.
388     * @return The number of messages that were actually moved
389     */
390    private int moveOutboxMessagesToFailedBox() {
391        ContentValues values = new ContentValues(3);
392
393        values.put(Sms.TYPE, Sms.MESSAGE_TYPE_FAILED);
394        values.put(Sms.ERROR_CODE, SmsManager.RESULT_ERROR_GENERIC_FAILURE);
395        values.put(Sms.READ, Integer.valueOf(0));
396
397        int messageCount = SqliteWrapper.update(
398                getApplicationContext(), getContentResolver(), Outbox.CONTENT_URI,
399                values, "type = " + Sms.MESSAGE_TYPE_OUTBOX, null);
400        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
401            Log.v(TAG, "moveOutboxMessagesToFailedBox messageCount: " + messageCount);
402        }
403        return messageCount;
404    }
405
406    public static final String CLASS_ZERO_BODY_KEY = "CLASS_ZERO_BODY";
407
408    // This must match the column IDs below.
409    private final static String[] REPLACE_PROJECTION = new String[] {
410        Sms._ID,
411        Sms.ADDRESS,
412        Sms.PROTOCOL
413    };
414
415    // This must match REPLACE_PROJECTION.
416    private static final int REPLACE_COLUMN_ID = 0;
417
418    /**
419     * If the message is a class-zero message, display it immediately
420     * and return null.  Otherwise, store it using the
421     * <code>ContentResolver</code> and return the
422     * <code>Uri</code> of the thread containing this message
423     * so that we can use it for notification.
424     */
425    private Uri insertMessage(Context context, SmsMessage[] msgs, int error) {
426        // Build the helper classes to parse the messages.
427        SmsMessage sms = msgs[0];
428
429        if (sms.getMessageClass() == SmsMessage.MessageClass.CLASS_0) {
430            displayClassZeroMessage(context, sms);
431            return null;
432        } else if (sms.isReplace()) {
433            return replaceMessage(context, msgs, error);
434        } else {
435            return storeMessage(context, msgs, error);
436        }
437    }
438
439    /**
440     * This method is used if this is a "replace short message" SMS.
441     * We find any existing message that matches the incoming
442     * message's originating address and protocol identifier.  If
443     * there is one, we replace its fields with those of the new
444     * message.  Otherwise, we store the new message as usual.
445     *
446     * See TS 23.040 9.2.3.9.
447     */
448    private Uri replaceMessage(Context context, SmsMessage[] msgs, int error) {
449        SmsMessage sms = msgs[0];
450        ContentValues values = extractContentValues(sms);
451
452        values.put(Inbox.BODY, sms.getMessageBody());
453        values.put(Sms.ERROR_CODE, error);
454
455        ContentResolver resolver = context.getContentResolver();
456        String originatingAddress = sms.getOriginatingAddress();
457        int protocolIdentifier = sms.getProtocolIdentifier();
458        String selection =
459                Sms.ADDRESS + " = ? AND " +
460                Sms.PROTOCOL + " = ?";
461        String[] selectionArgs = new String[] {
462            originatingAddress, Integer.toString(protocolIdentifier)
463        };
464
465        Cursor cursor = SqliteWrapper.query(context, resolver, Inbox.CONTENT_URI,
466                            REPLACE_PROJECTION, selection, selectionArgs, null);
467
468        if (cursor != null) {
469            try {
470                if (cursor.moveToFirst()) {
471                    long messageId = cursor.getLong(REPLACE_COLUMN_ID);
472                    Uri messageUri = ContentUris.withAppendedId(
473                            Sms.CONTENT_URI, messageId);
474
475                    SqliteWrapper.update(context, resolver, messageUri,
476                                        values, null, null);
477                    return messageUri;
478                }
479            } finally {
480                cursor.close();
481            }
482        }
483        return storeMessage(context, msgs, error);
484    }
485
486    public static String replaceFormFeeds(String s) {
487        // Some providers send formfeeds in their messages. Convert those formfeeds to newlines.
488        return s.replace('\f', '\n');
489    }
490
491//    private static int count = 0;
492
493    private Uri storeMessage(Context context, SmsMessage[] msgs, int error) {
494        SmsMessage sms = msgs[0];
495
496        // Store the message in the content provider.
497        ContentValues values = extractContentValues(sms);
498        values.put(Sms.ERROR_CODE, error);
499        int pduCount = msgs.length;
500
501        if (pduCount == 1) {
502            // There is only one part, so grab the body directly.
503            values.put(Inbox.BODY, replaceFormFeeds(sms.getDisplayMessageBody()));
504        } else {
505            // Build up the body from the parts.
506            StringBuilder body = new StringBuilder();
507            for (int i = 0; i < pduCount; i++) {
508                sms = msgs[i];
509                if (sms.mWrappedSmsMessage != null) {
510                    body.append(sms.getDisplayMessageBody());
511                }
512            }
513            values.put(Inbox.BODY, replaceFormFeeds(body.toString()));
514        }
515
516        // Make sure we've got a thread id so after the insert we'll be able to delete
517        // excess messages.
518        Long threadId = values.getAsLong(Sms.THREAD_ID);
519        String address = values.getAsString(Sms.ADDRESS);
520
521        // Code for debugging and easy injection of short codes, non email addresses, etc.
522        // See Contact.isAlphaNumber() for further comments and results.
523//        switch (count++ % 8) {
524//            case 0: address = "AB12"; break;
525//            case 1: address = "12"; break;
526//            case 2: address = "Jello123"; break;
527//            case 3: address = "T-Mobile"; break;
528//            case 4: address = "Mobile1"; break;
529//            case 5: address = "Dogs77"; break;
530//            case 6: address = "****1"; break;
531//            case 7: address = "#4#5#6#"; break;
532//        }
533
534        if (!TextUtils.isEmpty(address)) {
535            Contact cacheContact = Contact.get(address,true);
536            if (cacheContact != null) {
537                address = cacheContact.getNumber();
538            }
539        } else {
540            address = getString(R.string.unknown_sender);
541            values.put(Sms.ADDRESS, address);
542        }
543
544        if (((threadId == null) || (threadId == 0)) && (address != null)) {
545            threadId = Threads.getOrCreateThreadId(context, address);
546            values.put(Sms.THREAD_ID, threadId);
547        }
548
549        ContentResolver resolver = context.getContentResolver();
550
551        Uri insertedUri = SqliteWrapper.insert(context, resolver, Inbox.CONTENT_URI, values);
552
553        // Now make sure we're not over the limit in stored messages
554        Recycler.getSmsRecycler().deleteOldMessagesByThreadId(getApplicationContext(), threadId);
555
556        return insertedUri;
557    }
558
559    /**
560     * Extract all the content values except the body from an SMS
561     * message.
562     */
563    private ContentValues extractContentValues(SmsMessage sms) {
564        // Store the message in the content provider.
565        ContentValues values = new ContentValues();
566
567        values.put(Inbox.ADDRESS, sms.getDisplayOriginatingAddress());
568
569        // Use now for the timestamp to avoid confusion with clock
570        // drift between the handset and the SMSC.
571        // Check to make sure the system is giving us a non-bogus time.
572        Calendar buildDate = new GregorianCalendar(2011, 8, 18);    // 18 Sep 2011
573        Calendar nowDate = new GregorianCalendar();
574        long now = System.currentTimeMillis();
575        nowDate.setTimeInMillis(now);
576
577        if (nowDate.before(buildDate)) {
578            // It looks like our system clock isn't set yet because the current time right now
579            // is before an arbitrary time we made this build. Instead of inserting a bogus
580            // receive time in this case, use the timestamp of when the message was sent.
581            now = sms.getTimestampMillis();
582        }
583
584        values.put(Inbox.DATE, new Long(now));
585        values.put(Inbox.DATE_SENT, Long.valueOf(sms.getTimestampMillis()));
586        values.put(Inbox.PROTOCOL, sms.getProtocolIdentifier());
587        values.put(Inbox.READ, 0);
588        values.put(Inbox.SEEN, 0);
589        if (sms.getPseudoSubject().length() > 0) {
590            values.put(Inbox.SUBJECT, sms.getPseudoSubject());
591        }
592        values.put(Inbox.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0);
593        values.put(Inbox.SERVICE_CENTER, sms.getServiceCenterAddress());
594        return values;
595    }
596
597    /**
598     * Displays a class-zero message immediately in a pop-up window
599     * with the number from where it received the Notification with
600     * the body of the message
601     *
602     */
603    private void displayClassZeroMessage(Context context, SmsMessage sms) {
604        // Using NEW_TASK here is necessary because we're calling
605        // startActivity from outside an activity.
606        Intent smsDialogIntent = new Intent(context, ClassZeroActivity.class)
607                .putExtra("pdu", sms.getPdu())
608                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
609                          | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
610
611        context.startActivity(smsDialogIntent);
612    }
613
614    private void registerForServiceStateChanges() {
615        Context context = getApplicationContext();
616        unRegisterForServiceStateChanges();
617
618        IntentFilter intentFilter = new IntentFilter();
619        intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
620        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
621            Log.v(TAG, "registerForServiceStateChanges");
622        }
623
624        context.registerReceiver(SmsReceiver.getInstance(), intentFilter);
625    }
626
627    private void unRegisterForServiceStateChanges() {
628        if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE) || LogTag.DEBUG_SEND) {
629            Log.v(TAG, "unRegisterForServiceStateChanges");
630        }
631        try {
632            Context context = getApplicationContext();
633            context.unregisterReceiver(SmsReceiver.getInstance());
634        } catch (IllegalArgumentException e) {
635            // Allow un-matched register-unregister calls
636        }
637    }
638
639}
640
641
642