SMSDispatcher.java revision 0825495a331bb44df395a0cdb79fab85e68db5d5
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony;
18
19import android.app.Activity;
20import android.app.AlertDialog;
21import android.app.PendingIntent;
22import android.app.PendingIntent.CanceledException;
23import android.content.BroadcastReceiver;
24import android.content.ContentResolver;
25import android.content.ContentValues;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.content.res.Resources;
32import android.database.Cursor;
33import android.database.SQLException;
34import android.net.Uri;
35import android.os.AsyncResult;
36import android.os.Binder;
37import android.os.Handler;
38import android.os.Message;
39import android.os.PowerManager;
40import android.os.SystemProperties;
41import android.provider.Telephony;
42import android.provider.Telephony.Sms.Intents;
43import android.telephony.PhoneNumberUtils;
44import android.telephony.ServiceState;
45import android.telephony.SmsCbMessage;
46import android.telephony.SmsMessage;
47import android.telephony.TelephonyManager;
48import android.text.Html;
49import android.text.Spanned;
50import android.util.Log;
51import android.view.WindowManager;
52
53import com.android.internal.R;
54import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
55import com.android.internal.telephony.SmsConstants;
56import com.android.internal.util.HexDump;
57
58import java.io.ByteArrayOutputStream;
59import java.util.ArrayList;
60import java.util.Arrays;
61import java.util.HashMap;
62import java.util.Random;
63
64import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
65import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
66import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
67import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
68import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
69import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
70
71public abstract class SMSDispatcher extends Handler {
72    static final String TAG = "SMS";    // accessed from inner class
73    private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
74
75    /** Permission required to receive SMS and SMS-CB messages. */
76    public static final String RECEIVE_SMS_PERMISSION = "android.permission.RECEIVE_SMS";
77
78    /** Permission required to receive ETWS and CMAS emergency broadcasts. */
79    public static final String RECEIVE_EMERGENCY_BROADCAST_PERMISSION =
80            "android.permission.RECEIVE_EMERGENCY_BROADCAST";
81
82    /** Permission required to send SMS to short codes without user confirmation. */
83    private static final String SEND_SMS_NO_CONFIRMATION_PERMISSION =
84            "android.permission.SEND_SMS_NO_CONFIRMATION";
85
86    /** Query projection for checking for duplicate message segments. */
87    private static final String[] PDU_PROJECTION = new String[] {
88            "pdu"
89    };
90
91    /** Query projection for combining concatenated message segments. */
92    private static final String[] PDU_SEQUENCE_PORT_PROJECTION = new String[] {
93            "pdu",
94            "sequence",
95            "destination_port"
96    };
97
98    private static final int PDU_COLUMN = 0;
99    private static final int SEQUENCE_COLUMN = 1;
100    private static final int DESTINATION_PORT_COLUMN = 2;
101
102    /** New SMS received. */
103    protected static final int EVENT_NEW_SMS = 1;
104
105    /** SMS send complete. */
106    protected static final int EVENT_SEND_SMS_COMPLETE = 2;
107
108    /** Retry sending a previously failed SMS message */
109    private static final int EVENT_SEND_RETRY = 3;
110
111    /** Confirmation required for sending a large number of messages. */
112    private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;
113
114    /** Send the user confirmed SMS */
115    static final int EVENT_SEND_CONFIRMED_SMS = 5;  // accessed from inner class
116
117    /** Don't send SMS (user did not confirm). */
118    static final int EVENT_STOP_SENDING = 7;        // accessed from inner class
119
120    /** Confirmation required for third-party apps sending to an SMS short code. */
121    private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;
122
123    /** Confirmation required for third-party apps sending to an SMS short code. */
124    private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9;
125
126    protected final Phone mPhone;
127    protected final Context mContext;
128    protected final ContentResolver mResolver;
129    protected final CommandsInterface mCm;
130    protected final SmsStorageMonitor mStorageMonitor;
131    protected final TelephonyManager mTelephonyManager;
132
133    protected final WapPushOverSms mWapPush;
134
135    protected static final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
136
137    /** Maximum number of times to retry sending a failed SMS. */
138    private static final int MAX_SEND_RETRIES = 3;
139    /** Delay before next send attempt on a failed SMS, in milliseconds. */
140    private static final int SEND_RETRY_DELAY = 2000;
141    /** single part SMS */
142    private static final int SINGLE_PART_SMS = 1;
143    /** Message sending queue limit */
144    private static final int MO_MSG_QUEUE_LIMIT = 5;
145
146    /**
147     * Message reference for a CONCATENATED_8_BIT_REFERENCE or
148     * CONCATENATED_16_BIT_REFERENCE message set.  Should be
149     * incremented for each set of concatenated messages.
150     * Static field shared by all dispatcher objects.
151     */
152    private static int sConcatenatedRef = new Random().nextInt(256);
153
154    /** Outgoing message counter. Shared by all dispatchers. */
155    private final SmsUsageMonitor mUsageMonitor;
156
157    /** Number of outgoing SmsTrackers waiting for user confirmation. */
158    private int mPendingTrackerCount;
159
160    /** Wake lock to ensure device stays awake while dispatching the SMS intent. */
161    private PowerManager.WakeLock mWakeLock;
162
163    /**
164     * Hold the wake lock for 5 seconds, which should be enough time for
165     * any receiver(s) to grab its own wake lock.
166     */
167    private static final int WAKE_LOCK_TIMEOUT = 5000;
168
169    /* Flags indicating whether the current device allows sms service */
170    protected boolean mSmsCapable = true;
171    protected boolean mSmsReceiveDisabled;
172    protected boolean mSmsSendDisabled;
173
174    protected int mRemainingMessages = -1;
175
176    protected static int getNextConcatenatedRef() {
177        sConcatenatedRef += 1;
178        return sConcatenatedRef;
179    }
180
181    /**
182     * Create a new SMS dispatcher.
183     * @param phone the Phone to use
184     * @param storageMonitor the SmsStorageMonitor to use
185     * @param usageMonitor the SmsUsageMonitor to use
186     */
187    protected SMSDispatcher(PhoneBase phone, SmsStorageMonitor storageMonitor,
188            SmsUsageMonitor usageMonitor) {
189        mPhone = phone;
190        mWapPush = new WapPushOverSms(phone, this);
191        mContext = phone.getContext();
192        mResolver = mContext.getContentResolver();
193        mCm = phone.mCM;
194        mStorageMonitor = storageMonitor;
195        mUsageMonitor = usageMonitor;
196        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
197
198        createWakelock();
199
200        mSmsCapable = mContext.getResources().getBoolean(
201                com.android.internal.R.bool.config_sms_capable);
202        mSmsReceiveDisabled = !SystemProperties.getBoolean(
203                                TelephonyProperties.PROPERTY_SMS_RECEIVE, mSmsCapable);
204        mSmsSendDisabled = !SystemProperties.getBoolean(
205                                TelephonyProperties.PROPERTY_SMS_SEND, mSmsCapable);
206        Log.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat()
207                + " mSmsReceiveDisabled=" + mSmsReceiveDisabled
208                + " mSmsSendDisabled=" + mSmsSendDisabled);
209    }
210
211    /** Unregister for incoming SMS events. */
212    public abstract void dispose();
213
214    /**
215     * The format of the message PDU in the associated broadcast intent.
216     * This will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format
217     * or "3gpp2" for CDMA/LTE messages in 3GPP2 format.
218     *
219     * Note: All applications which handle incoming SMS messages by processing the
220     * SMS_RECEIVED_ACTION broadcast intent MUST pass the "format" extra from the intent
221     * into the new methods in {@link android.telephony.SmsMessage} which take an
222     * extra format parameter. This is required in order to correctly decode the PDU on
223     * devices which require support for both 3GPP and 3GPP2 formats at the same time,
224     * such as CDMA/LTE devices and GSM/CDMA world phones.
225     *
226     * @return the format of the message PDU
227     */
228    protected abstract String getFormat();
229
230    @Override
231    protected void finalize() {
232        Log.d(TAG, "SMSDispatcher finalized");
233    }
234
235
236    /* TODO: Need to figure out how to keep track of status report routing in a
237     *       persistent manner. If the phone process restarts (reboot or crash),
238     *       we will lose this list and any status reports that come in after
239     *       will be dropped.
240     */
241    /** Sent messages awaiting a delivery status report. */
242    protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>();
243
244    /**
245     * Handles events coming from the phone stack. Overridden from handler.
246     *
247     * @param msg the message to handle
248     */
249    @Override
250    public void handleMessage(Message msg) {
251        AsyncResult ar;
252
253        switch (msg.what) {
254        case EVENT_NEW_SMS:
255            // A new SMS has been received by the device
256            if (false) {
257                Log.d(TAG, "New SMS Message Received");
258            }
259
260            SmsMessage sms;
261
262            ar = (AsyncResult) msg.obj;
263
264            if (ar.exception != null) {
265                Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
266                return;
267            }
268
269            sms = (SmsMessage) ar.result;
270            try {
271                int result = dispatchMessage(sms.mWrappedSmsMessage);
272                if (result != Activity.RESULT_OK) {
273                    // RESULT_OK means that message was broadcast for app(s) to handle.
274                    // Any other result, we should ack here.
275                    boolean handled = (result == Intents.RESULT_SMS_HANDLED);
276                    notifyAndAcknowledgeLastIncomingSms(handled, result, null);
277                }
278            } catch (RuntimeException ex) {
279                Log.e(TAG, "Exception dispatching message", ex);
280                notifyAndAcknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
281            }
282
283            break;
284
285        case EVENT_SEND_SMS_COMPLETE:
286            // An outbound SMS has been successfully transferred, or failed.
287            handleSendComplete((AsyncResult) msg.obj);
288            break;
289
290        case EVENT_SEND_RETRY:
291            sendSms((SmsTracker) msg.obj);
292            break;
293
294        case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
295            handleReachSentLimit((SmsTracker)(msg.obj));
296            break;
297
298        case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
299            handleConfirmShortCode(false, (SmsTracker)(msg.obj));
300            break;
301
302        case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
303            handleConfirmShortCode(true, (SmsTracker)(msg.obj));
304            break;
305
306        case EVENT_SEND_CONFIRMED_SMS:
307        {
308            SmsTracker tracker = (SmsTracker) msg.obj;
309            if (tracker.isMultipart()) {
310                sendMultipartSms(tracker);
311            } else {
312                sendSms(tracker);
313            }
314            mPendingTrackerCount--;
315            break;
316        }
317
318        case EVENT_STOP_SENDING:
319        {
320            SmsTracker tracker = (SmsTracker) msg.obj;
321            if (tracker.mSentIntent != null) {
322                try {
323                    tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
324                } catch (CanceledException ex) {
325                    Log.e(TAG, "failed to send RESULT_ERROR_LIMIT_EXCEEDED");
326                }
327            }
328            mPendingTrackerCount--;
329            break;
330        }
331        }
332    }
333
334    private void createWakelock() {
335        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
336        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
337        mWakeLock.setReferenceCounted(true);
338    }
339
340    /**
341     * Grabs a wake lock and sends intent as an ordered broadcast.
342     * The resultReceiver will check for errors and ACK/NACK back
343     * to the RIL.
344     *
345     * @param intent intent to broadcast
346     * @param permission Receivers are required to have this permission
347     */
348    public void dispatch(Intent intent, String permission) {
349        // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
350        // receivers time to take their own wake locks.
351        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
352        mContext.sendOrderedBroadcast(intent, permission, mResultReceiver,
353                this, Activity.RESULT_OK, null, null);
354    }
355
356    /**
357     * Called when SMS send completes. Broadcasts a sentIntent on success.
358     * On failure, either sets up retries or broadcasts a sentIntent with
359     * the failure in the result code.
360     *
361     * @param ar AsyncResult passed into the message handler.  ar.result should
362     *           an SmsResponse instance if send was successful.  ar.userObj
363     *           should be an SmsTracker instance.
364     */
365    protected void handleSendComplete(AsyncResult ar) {
366        SmsTracker tracker = (SmsTracker) ar.userObj;
367        PendingIntent sentIntent = tracker.mSentIntent;
368
369        if (ar.exception == null) {
370            if (false) {
371                Log.d(TAG, "SMS send complete. Broadcasting "
372                        + "intent: " + sentIntent);
373            }
374
375            if (tracker.mDeliveryIntent != null) {
376                // Expecting a status report.  Add it to the list.
377                int messageRef = ((SmsResponse)ar.result).messageRef;
378                tracker.mMessageRef = messageRef;
379                deliveryPendingList.add(tracker);
380            }
381
382            if (sentIntent != null) {
383                try {
384                    if (mRemainingMessages > -1) {
385                        mRemainingMessages--;
386                    }
387
388                    if (mRemainingMessages == 0) {
389                        Intent sendNext = new Intent();
390                        sendNext.putExtra(SEND_NEXT_MSG_EXTRA, true);
391                        sentIntent.send(mContext, Activity.RESULT_OK, sendNext);
392                    } else {
393                        sentIntent.send(Activity.RESULT_OK);
394                    }
395                } catch (CanceledException ex) {}
396            }
397        } else {
398            if (false) {
399                Log.d(TAG, "SMS send failed");
400            }
401
402            int ss = mPhone.getServiceState().getState();
403
404            if (ss != ServiceState.STATE_IN_SERVICE) {
405                handleNotInService(ss, tracker.mSentIntent);
406            } else if ((((CommandException)(ar.exception)).getCommandError()
407                    == CommandException.Error.SMS_FAIL_RETRY) &&
408                   tracker.mRetryCount < MAX_SEND_RETRIES) {
409                // Retry after a delay if needed.
410                // TODO: According to TS 23.040, 9.2.3.6, we should resend
411                //       with the same TP-MR as the failed message, and
412                //       TP-RD set to 1.  However, we don't have a means of
413                //       knowing the MR for the failed message (EF_SMSstatus
414                //       may or may not have the MR corresponding to this
415                //       message, depending on the failure).  Also, in some
416                //       implementations this retry is handled by the baseband.
417                tracker.mRetryCount++;
418                Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
419                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
420            } else if (tracker.mSentIntent != null) {
421                int error = RESULT_ERROR_GENERIC_FAILURE;
422
423                if (((CommandException)(ar.exception)).getCommandError()
424                        == CommandException.Error.FDN_CHECK_FAILURE) {
425                    error = RESULT_ERROR_FDN_CHECK_FAILURE;
426                }
427                // Done retrying; return an error to the app.
428                try {
429                    Intent fillIn = new Intent();
430                    if (ar.result != null) {
431                        fillIn.putExtra("errorCode", ((SmsResponse)ar.result).errorCode);
432                    }
433                    if (mRemainingMessages > -1) {
434                        mRemainingMessages--;
435                    }
436
437                    if (mRemainingMessages == 0) {
438                        fillIn.putExtra(SEND_NEXT_MSG_EXTRA, true);
439                    }
440
441                    tracker.mSentIntent.send(mContext, error, fillIn);
442                } catch (CanceledException ex) {}
443            }
444        }
445    }
446
447    /**
448     * Handles outbound message when the phone is not in service.
449     *
450     * @param ss     Current service state.  Valid values are:
451     *                  OUT_OF_SERVICE
452     *                  EMERGENCY_ONLY
453     *                  POWER_OFF
454     * @param sentIntent the PendingIntent to send the error to
455     */
456    protected static void handleNotInService(int ss, PendingIntent sentIntent) {
457        if (sentIntent != null) {
458            try {
459                if (ss == ServiceState.STATE_POWER_OFF) {
460                    sentIntent.send(RESULT_ERROR_RADIO_OFF);
461                } else {
462                    sentIntent.send(RESULT_ERROR_NO_SERVICE);
463                }
464            } catch (CanceledException ex) {}
465        }
466    }
467
468    /**
469     * Dispatches an incoming SMS messages.
470     *
471     * @param sms the incoming message from the phone
472     * @return a result code from {@link Telephony.Sms.Intents}, or
473     *         {@link Activity#RESULT_OK} if the message has been broadcast
474     *         to applications
475     */
476    public abstract int dispatchMessage(SmsMessageBase sms);
477
478    /**
479     * Dispatch a normal incoming SMS. This is called from the format-specific
480     * {@link #dispatchMessage(SmsMessageBase)} if no format-specific handling is required.
481     *
482     * @param sms
483     * @return
484     */
485    protected int dispatchNormalMessage(SmsMessageBase sms) {
486        SmsHeader smsHeader = sms.getUserDataHeader();
487
488        // See if message is partial or port addressed.
489        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
490            // Message is not partial (not part of concatenated sequence).
491            byte[][] pdus = new byte[1][];
492            pdus[0] = sms.getPdu();
493
494            if (smsHeader != null && smsHeader.portAddrs != null) {
495                if (smsHeader.portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
496                    // GSM-style WAP indication
497                    return mWapPush.dispatchWapPdu(sms.getUserData());
498                } else {
499                    // The message was sent to a port, so concoct a URI for it.
500                    dispatchPortAddressedPdus(pdus, smsHeader.portAddrs.destPort);
501                }
502            } else {
503                // Normal short and non-port-addressed message, dispatch it.
504                dispatchPdus(pdus);
505            }
506            return Activity.RESULT_OK;
507        } else {
508            // Process the message part.
509            SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
510            SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
511            return processMessagePart(sms.getPdu(), sms.getOriginatingAddress(),
512                    concatRef.refNumber, concatRef.seqNumber, concatRef.msgCount,
513                    sms.getTimestampMillis(), (portAddrs != null ? portAddrs.destPort : -1), false);
514        }
515    }
516
517    /**
518     * If this is the last part send the parts out to the application, otherwise
519     * the part is stored for later processing. Handles both 3GPP concatenated messages
520     * as well as 3GPP2 format WAP push messages processed by
521     * {@link com.android.internal.telephony.cdma.CdmaSMSDispatcher#processCdmaWapPdu}.
522     *
523     * @param pdu the message PDU, or the datagram portion of a CDMA WDP datagram segment
524     * @param address the originating address
525     * @param referenceNumber distinguishes concatenated messages from the same sender
526     * @param sequenceNumber the order of this segment in the message
527     *          (starting at 0 for CDMA WDP datagrams and 1 for concatenated messages).
528     * @param messageCount the number of segments in the message
529     * @param timestamp the service center timestamp in millis
530     * @param destPort the destination port for the message, or -1 for no destination port
531     * @param isCdmaWapPush true if pdu is a CDMA WDP datagram segment and not an SM PDU
532     *
533     * @return a result code from {@link Telephony.Sms.Intents}, or
534     *         {@link Activity#RESULT_OK} if the message has been broadcast
535     *         to applications
536     */
537    protected int processMessagePart(byte[] pdu, String address, int referenceNumber,
538            int sequenceNumber, int messageCount, long timestamp, int destPort,
539            boolean isCdmaWapPush) {
540        byte[][] pdus = null;
541        Cursor cursor = null;
542        try {
543            // used by several query selection arguments
544            String refNumber = Integer.toString(referenceNumber);
545            String seqNumber = Integer.toString(sequenceNumber);
546
547            // Check for duplicate message segment
548            cursor = mResolver.query(mRawUri, PDU_PROJECTION,
549                    "address=? AND reference_number=? AND sequence=?",
550                    new String[] {address, refNumber, seqNumber}, null);
551
552            // moveToNext() returns false if no duplicates were found
553            if (cursor.moveToNext()) {
554                Log.w(TAG, "Discarding duplicate message segment from address=" + address
555                        + " refNumber=" + refNumber + " seqNumber=" + seqNumber);
556                String oldPduString = cursor.getString(PDU_COLUMN);
557                byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
558                if (!Arrays.equals(oldPdu, pdu)) {
559                    Log.e(TAG, "Warning: dup message segment PDU of length " + pdu.length
560                            + " is different from existing PDU of length " + oldPdu.length);
561                }
562                return Intents.RESULT_SMS_HANDLED;
563            }
564            cursor.close();
565
566            // not a dup, query for all other segments of this concatenated message
567            String where = "address=? AND reference_number=?";
568            String[] whereArgs = new String[] {address, refNumber};
569            cursor = mResolver.query(mRawUri, PDU_SEQUENCE_PORT_PROJECTION, where, whereArgs, null);
570
571            int cursorCount = cursor.getCount();
572            if (cursorCount != messageCount - 1) {
573                // We don't have all the parts yet, store this one away
574                ContentValues values = new ContentValues();
575                values.put("date", timestamp);
576                values.put("pdu", HexDump.toHexString(pdu));
577                values.put("address", address);
578                values.put("reference_number", referenceNumber);
579                values.put("count", messageCount);
580                values.put("sequence", sequenceNumber);
581                if (destPort != -1) {
582                    values.put("destination_port", destPort);
583                }
584                mResolver.insert(mRawUri, values);
585                return Intents.RESULT_SMS_HANDLED;
586            }
587
588            // All the parts are in place, deal with them
589            pdus = new byte[messageCount][];
590            for (int i = 0; i < cursorCount; i++) {
591                cursor.moveToNext();
592                int cursorSequence = cursor.getInt(SEQUENCE_COLUMN);
593                // GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
594                if (!isCdmaWapPush) {
595                    cursorSequence--;
596                }
597                pdus[cursorSequence] = HexDump.hexStringToByteArray(
598                        cursor.getString(PDU_COLUMN));
599
600                // Read the destination port from the first segment (needed for CDMA WAP PDU).
601                // It's not a bad idea to prefer the port from the first segment for 3GPP as well.
602                if (cursorSequence == 0 && !cursor.isNull(DESTINATION_PORT_COLUMN)) {
603                    destPort = cursor.getInt(DESTINATION_PORT_COLUMN);
604                }
605            }
606            // This one isn't in the DB, so add it
607            // GSM sequence numbers start at 1; CDMA WDP datagram sequence numbers start at 0
608            if (isCdmaWapPush) {
609                pdus[sequenceNumber] = pdu;
610            } else {
611                pdus[sequenceNumber - 1] = pdu;
612            }
613
614            // Remove the parts from the database
615            mResolver.delete(mRawUri, where, whereArgs);
616        } catch (SQLException e) {
617            Log.e(TAG, "Can't access multipart SMS database", e);
618            return Intents.RESULT_SMS_GENERIC_ERROR;
619        } finally {
620            if (cursor != null) cursor.close();
621        }
622
623        // Special handling for CDMA WDP datagrams
624        if (isCdmaWapPush) {
625            // Build up the data stream
626            ByteArrayOutputStream output = new ByteArrayOutputStream();
627            for (int i = 0; i < messageCount; i++) {
628                // reassemble the (WSP-)pdu
629                output.write(pdus[i], 0, pdus[i].length);
630            }
631            byte[] datagram = output.toByteArray();
632
633            // Dispatch the PDU to applications
634            if (destPort == SmsHeader.PORT_WAP_PUSH) {
635                // Handle the PUSH
636                return mWapPush.dispatchWapPdu(datagram);
637            } else {
638                pdus = new byte[1][];
639                pdus[0] = datagram;
640                // The messages were sent to any other WAP port
641                dispatchPortAddressedPdus(pdus, destPort);
642                return Activity.RESULT_OK;
643            }
644        }
645
646        // Dispatch the PDUs to applications
647        if (destPort != -1) {
648            if (destPort == SmsHeader.PORT_WAP_PUSH) {
649                // Build up the data stream
650                ByteArrayOutputStream output = new ByteArrayOutputStream();
651                for (int i = 0; i < messageCount; i++) {
652                    SmsMessage msg = SmsMessage.createFromPdu(pdus[i], getFormat());
653                    byte[] data = msg.getUserData();
654                    output.write(data, 0, data.length);
655                }
656                // Handle the PUSH
657                return mWapPush.dispatchWapPdu(output.toByteArray());
658            } else {
659                // The messages were sent to a port, so concoct a URI for it
660                dispatchPortAddressedPdus(pdus, destPort);
661            }
662        } else {
663            // The messages were not sent to a port
664            dispatchPdus(pdus);
665        }
666        return Activity.RESULT_OK;
667    }
668
669    /**
670     * Dispatches standard PDUs to interested applications
671     *
672     * @param pdus The raw PDUs making up the message
673     */
674    protected void dispatchPdus(byte[][] pdus) {
675        Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
676        intent.putExtra("pdus", pdus);
677        intent.putExtra("format", getFormat());
678        dispatch(intent, RECEIVE_SMS_PERMISSION);
679    }
680
681    /**
682     * Dispatches port addressed PDUs to interested applications
683     *
684     * @param pdus The raw PDUs making up the message
685     * @param port The destination port of the messages
686     */
687    protected void dispatchPortAddressedPdus(byte[][] pdus, int port) {
688        Uri uri = Uri.parse("sms://localhost:" + port);
689        Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
690        intent.putExtra("pdus", pdus);
691        intent.putExtra("format", getFormat());
692        dispatch(intent, RECEIVE_SMS_PERMISSION);
693    }
694
695    /**
696     * Send a data based SMS to a specific application port.
697     *
698     * @param destAddr the address to send the message to
699     * @param scAddr is the service center address or null to use
700     *  the current default SMSC
701     * @param destPort the port to deliver the message to
702     * @param data the body of the message to send
703     * @param sentIntent if not NULL this <code>PendingIntent</code> is
704     *  broadcast when the message is successfully sent, or failed.
705     *  The result code will be <code>Activity.RESULT_OK<code> for success,
706     *  or one of these errors:<br>
707     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
708     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
709     *  <code>RESULT_ERROR_NULL_PDU</code><br>
710     *  <code>RESULT_ERROR_NO_SERVICE</code><br>.
711     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
712     *  the extra "errorCode" containing a radio technology specific value,
713     *  generally only useful for troubleshooting.<br>
714     *  The per-application based SMS control checks sentIntent. If sentIntent
715     *  is NULL the caller will be checked against all unknown applications,
716     *  which cause smaller number of SMS to be sent in checking period.
717     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
718     *  broadcast when the message is delivered to the recipient.  The
719     *  raw pdu of the status report is in the extended data ("pdu").
720     */
721    protected abstract void sendData(String destAddr, String scAddr, int destPort,
722            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent);
723
724    /**
725     * Send a text based SMS.
726     *
727     * @param destAddr the address to send the message to
728     * @param scAddr is the service center address or null to use
729     *  the current default SMSC
730     * @param text the body of the message to send
731     * @param sentIntent if not NULL this <code>PendingIntent</code> is
732     *  broadcast when the message is successfully sent, or failed.
733     *  The result code will be <code>Activity.RESULT_OK<code> for success,
734     *  or one of these errors:<br>
735     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
736     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
737     *  <code>RESULT_ERROR_NULL_PDU</code><br>
738     *  <code>RESULT_ERROR_NO_SERVICE</code><br>.
739     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
740     *  the extra "errorCode" containing a radio technology specific value,
741     *  generally only useful for troubleshooting.<br>
742     *  The per-application based SMS control checks sentIntent. If sentIntent
743     *  is NULL the caller will be checked against all unknown applications,
744     *  which cause smaller number of SMS to be sent in checking period.
745     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
746     *  broadcast when the message is delivered to the recipient.  The
747     *  raw pdu of the status report is in the extended data ("pdu").
748     */
749    protected abstract void sendText(String destAddr, String scAddr,
750            String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
751
752    /**
753     * Calculate the number of septets needed to encode the message.
754     *
755     * @param messageBody the message to encode
756     * @param use7bitOnly ignore (but still count) illegal characters if true
757     * @return TextEncodingDetails
758     */
759    protected abstract TextEncodingDetails calculateLength(CharSequence messageBody,
760            boolean use7bitOnly);
761
762    /**
763     * Send a multi-part text based SMS.
764     *
765     * @param destAddr the address to send the message to
766     * @param scAddr is the service center address or null to use
767     *   the current default SMSC
768     * @param parts an <code>ArrayList</code> of strings that, in order,
769     *   comprise the original message
770     * @param sentIntents if not null, an <code>ArrayList</code> of
771     *   <code>PendingIntent</code>s (one for each message part) that is
772     *   broadcast when the corresponding message part has been sent.
773     *   The result code will be <code>Activity.RESULT_OK<code> for success,
774     *   or one of these errors:
775     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
776     *   <code>RESULT_ERROR_RADIO_OFF</code>
777     *   <code>RESULT_ERROR_NULL_PDU</code>
778     *   <code>RESULT_ERROR_NO_SERVICE</code>.
779     *  The per-application based SMS control checks sentIntent. If sentIntent
780     *  is NULL the caller will be checked against all unknown applications,
781     *  which cause smaller number of SMS to be sent in checking period.
782     * @param deliveryIntents if not null, an <code>ArrayList</code> of
783     *   <code>PendingIntent</code>s (one for each message part) that is
784     *   broadcast when the corresponding message part has been delivered
785     *   to the recipient.  The raw pdu of the status report is in the
786     *   extended data ("pdu").
787     */
788    protected void sendMultipartText(String destAddr, String scAddr,
789            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
790            ArrayList<PendingIntent> deliveryIntents) {
791
792        int refNumber = getNextConcatenatedRef() & 0x00FF;
793        int msgCount = parts.size();
794        int encoding = SmsConstants.ENCODING_UNKNOWN;
795
796        mRemainingMessages = msgCount;
797
798        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
799        for (int i = 0; i < msgCount; i++) {
800            TextEncodingDetails details = calculateLength(parts.get(i), false);
801            if (encoding != details.codeUnitSize
802                    && (encoding == SmsConstants.ENCODING_UNKNOWN
803                            || encoding == SmsConstants.ENCODING_7BIT)) {
804                encoding = details.codeUnitSize;
805            }
806            encodingForParts[i] = details;
807        }
808
809        for (int i = 0; i < msgCount; i++) {
810            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
811            concatRef.refNumber = refNumber;
812            concatRef.seqNumber = i + 1;  // 1-based sequence
813            concatRef.msgCount = msgCount;
814            // TODO: We currently set this to true since our messaging app will never
815            // send more than 255 parts (it converts the message to MMS well before that).
816            // However, we should support 3rd party messaging apps that might need 16-bit
817            // references
818            // Note:  It's not sufficient to just flip this bit to true; it will have
819            // ripple effects (several calculations assume 8-bit ref).
820            concatRef.isEightBits = true;
821            SmsHeader smsHeader = new SmsHeader();
822            smsHeader.concatRef = concatRef;
823
824            // Set the national language tables for 3GPP 7-bit encoding, if enabled.
825            if (encoding == SmsConstants.ENCODING_7BIT) {
826                smsHeader.languageTable = encodingForParts[i].languageTable;
827                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
828            }
829
830            PendingIntent sentIntent = null;
831            if (sentIntents != null && sentIntents.size() > i) {
832                sentIntent = sentIntents.get(i);
833            }
834
835            PendingIntent deliveryIntent = null;
836            if (deliveryIntents != null && deliveryIntents.size() > i) {
837                deliveryIntent = deliveryIntents.get(i);
838            }
839
840            sendNewSubmitPdu(destAddr, scAddr, parts.get(i), smsHeader, encoding,
841                    sentIntent, deliveryIntent, (i == (msgCount - 1)));
842        }
843
844    }
845
846    /**
847     * Create a new SubmitPdu and send it.
848     */
849    protected abstract void sendNewSubmitPdu(String destinationAddress, String scAddress,
850            String message, SmsHeader smsHeader, int encoding,
851            PendingIntent sentIntent, PendingIntent deliveryIntent, boolean lastPart);
852
853    /**
854     * Send a SMS
855     *
856     * @param smsc the SMSC to send the message through, or NULL for the
857     *  default SMSC
858     * @param pdu the raw PDU to send
859     * @param sentIntent if not NULL this <code>Intent</code> is
860     *  broadcast when the message is successfully sent, or failed.
861     *  The result code will be <code>Activity.RESULT_OK<code> for success,
862     *  or one of these errors:
863     *  <code>RESULT_ERROR_GENERIC_FAILURE</code>
864     *  <code>RESULT_ERROR_RADIO_OFF</code>
865     *  <code>RESULT_ERROR_NULL_PDU</code>
866     *  <code>RESULT_ERROR_NO_SERVICE</code>.
867     *  The per-application based SMS control checks sentIntent. If sentIntent
868     *  is NULL the caller will be checked against all unknown applications,
869     *  which cause smaller number of SMS to be sent in checking period.
870     * @param deliveryIntent if not NULL this <code>Intent</code> is
871     *  broadcast when the message is delivered to the recipient.  The
872     *  raw pdu of the status report is in the extended data ("pdu").
873     * @param destAddr the destination phone number (for short code confirmation)
874     */
875    protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
876            PendingIntent deliveryIntent, String destAddr) {
877        if (mSmsSendDisabled) {
878            if (sentIntent != null) {
879                try {
880                    sentIntent.send(RESULT_ERROR_NO_SERVICE);
881                } catch (CanceledException ex) {}
882            }
883            Log.d(TAG, "Device does not support sending sms.");
884            return;
885        }
886
887        if (pdu == null) {
888            if (sentIntent != null) {
889                try {
890                    sentIntent.send(RESULT_ERROR_NULL_PDU);
891                } catch (CanceledException ex) {}
892            }
893            return;
894        }
895
896        HashMap<String, Object> map = new HashMap<String, Object>();
897        map.put("smsc", smsc);
898        map.put("pdu", pdu);
899
900        // Get calling app package name via UID from Binder call
901        PackageManager pm = mContext.getPackageManager();
902        String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
903
904        if (packageNames == null || packageNames.length == 0) {
905            // Refuse to send SMS if we can't get the calling package name.
906            Log.e(TAG, "Can't get calling app package name: refusing to send SMS");
907            if (sentIntent != null) {
908                try {
909                    sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
910                } catch (CanceledException ex) {
911                    Log.e(TAG, "failed to send error result");
912                }
913            }
914            return;
915        }
916
917        String appPackage = packageNames[0];
918
919        // Strip non-digits from destination phone number before checking for short codes
920        // and before displaying the number to the user if confirmation is required.
921        SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent, appPackage,
922                PhoneNumberUtils.extractNetworkPortion(destAddr));
923
924        // checkDestination() returns true if the destination is not a premium short code or the
925        // sending app is approved to send to short codes. Otherwise, a message is sent to our
926        // handler with the SmsTracker to request user confirmation before sending.
927        if (checkDestination(tracker)) {
928            // check for excessive outgoing SMS usage by this app
929            if (!mUsageMonitor.check(appPackage, SINGLE_PART_SMS)) {
930                sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
931                return;
932            }
933
934            int ss = mPhone.getServiceState().getState();
935
936            if (ss != ServiceState.STATE_IN_SERVICE) {
937                handleNotInService(ss, tracker.mSentIntent);
938            } else {
939                sendSms(tracker);
940            }
941        }
942    }
943
944    /**
945     * Check if destination is a potential premium short code and sender is not pre-approved to
946     * send to short codes.
947     *
948     * @param tracker the tracker for the SMS to send
949     * @return true if the destination is approved; false if user confirmation event was sent
950     */
951    boolean checkDestination(SmsTracker tracker) {
952        if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION_PERMISSION)
953                == PackageManager.PERMISSION_GRANTED) {
954            return true;            // app is pre-approved to send to short codes
955        } else {
956            String countryIso = mTelephonyManager.getSimCountryIso();
957            if (countryIso == null || countryIso.length() != 2) {
958                Log.e(TAG, "Can't get SIM country code: trying network country code");
959                countryIso = mTelephonyManager.getNetworkCountryIso();
960            }
961
962            switch (mUsageMonitor.checkDestination(tracker.mDestAddress, countryIso)) {
963                case SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE:
964                    sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE,
965                            tracker));
966                    return false;   // wait for user confirmation before sending
967
968                case SmsUsageMonitor.CATEGORY_PREMIUM_SHORT_CODE:
969                    sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE,
970                            tracker));
971                    return false;   // wait for user confirmation before sending
972
973                case SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE:
974                case SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE:
975                case SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE:
976                default:
977                    return true;    // destination is not a premium short code
978            }
979        }
980    }
981
982    /**
983     * Deny sending an SMS if the outgoing queue limit is reached. Used when the message
984     * must be confirmed by the user due to excessive usage or potential premium SMS detected.
985     * @param tracker the SmsTracker for the message to send
986     * @return true if the message was denied; false to continue with send confirmation
987     */
988    private boolean denyIfQueueLimitReached(SmsTracker tracker) {
989        if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) {
990            // Deny sending message when the queue limit is reached.
991            try {
992                tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
993            } catch (CanceledException ex) {
994                Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
995            }
996            return true;
997        }
998        mPendingTrackerCount++;
999        return false;
1000    }
1001
1002    /**
1003     * Returns the label for the specified app package name.
1004     * @param appPackage the package name of the app requesting to send an SMS
1005     * @return the label for the specified app, or the package name if getApplicationInfo() fails
1006     */
1007    private CharSequence getAppLabel(String appPackage) {
1008        PackageManager pm = mContext.getPackageManager();
1009        try {
1010            ApplicationInfo appInfo = pm.getApplicationInfo(appPackage, 0);
1011            return appInfo.loadLabel(pm);
1012        } catch (PackageManager.NameNotFoundException e) {
1013            Log.e(TAG, "PackageManager Name Not Found for package " + appPackage);
1014            return appPackage;  // fall back to package name if we can't get app label
1015        }
1016    }
1017
1018    /**
1019     * Post an alert when SMS needs confirmation due to excessive usage.
1020     * @param tracker an SmsTracker for the current message.
1021     */
1022    protected void handleReachSentLimit(SmsTracker tracker) {
1023        if (denyIfQueueLimitReached(tracker)) {
1024            return;     // queue limit reached; error was returned to caller
1025        }
1026
1027        CharSequence appLabel = getAppLabel(tracker.mAppPackage);
1028        Resources r = Resources.getSystem();
1029        Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));
1030
1031        ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
1032
1033        AlertDialog d = new AlertDialog.Builder(mContext)
1034                .setTitle(R.string.sms_control_title)
1035                .setIcon(R.drawable.stat_sys_warning)
1036                .setMessage(messageText)
1037                .setPositiveButton(r.getString(R.string.sms_control_yes), listener)
1038                .setNegativeButton(r.getString(R.string.sms_control_no), listener)
1039                .setOnCancelListener(listener)
1040                .create();
1041
1042        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
1043        d.show();
1044    }
1045
1046    /**
1047     * Post an alert for user confirmation when sending to a potential short code.
1048     * @param isPremium true if the destination is known to be a premium short code
1049     * @param tracker the SmsTracker for the current message.
1050     */
1051    protected void handleConfirmShortCode(boolean isPremium, SmsTracker tracker) {
1052        if (denyIfQueueLimitReached(tracker)) {
1053            return;     // queue limit reached; error was returned to caller
1054        }
1055
1056        int messageId;
1057        int titleId;
1058        if (isPremium) {
1059            messageId = R.string.sms_premium_short_code_confirm_message;
1060            titleId = R.string.sms_premium_short_code_confirm_title;
1061        } else {
1062            messageId = R.string.sms_short_code_confirm_message;
1063            titleId = R.string.sms_short_code_confirm_title;
1064        }
1065
1066        CharSequence appLabel = getAppLabel(tracker.mAppPackage);
1067        Resources r = Resources.getSystem();
1068        Spanned messageText = Html.fromHtml(r.getString(messageId, appLabel, tracker.mDestAddress));
1069
1070        ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
1071
1072        AlertDialog d = new AlertDialog.Builder(mContext)
1073                .setTitle(titleId)
1074                .setIcon(R.drawable.stat_sys_warning)
1075                .setMessage(messageText)
1076                .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener)
1077                .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener)
1078// TODO: add third button for "Report malicious app" feature
1079//                .setNeutralButton(r.getString(R.string.sms_short_code_confirm_report), listener)
1080                .setOnCancelListener(listener)
1081                .create();
1082
1083        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
1084        d.show();
1085    }
1086
1087    /**
1088     * Send the message along to the radio.
1089     *
1090     * @param tracker holds the SMS message to send
1091     */
1092    protected abstract void sendSms(SmsTracker tracker);
1093
1094    /**
1095     * Send the multi-part SMS based on multipart Sms tracker
1096     *
1097     * @param tracker holds the multipart Sms tracker ready to be sent
1098     */
1099    private void sendMultipartSms(SmsTracker tracker) {
1100        ArrayList<String> parts;
1101        ArrayList<PendingIntent> sentIntents;
1102        ArrayList<PendingIntent> deliveryIntents;
1103
1104        HashMap<String, Object> map = tracker.mData;
1105
1106        String destinationAddress = (String) map.get("destination");
1107        String scAddress = (String) map.get("scaddress");
1108
1109        parts = (ArrayList<String>) map.get("parts");
1110        sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
1111        deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");
1112
1113        // check if in service
1114        int ss = mPhone.getServiceState().getState();
1115        if (ss != ServiceState.STATE_IN_SERVICE) {
1116            for (int i = 0, count = parts.size(); i < count; i++) {
1117                PendingIntent sentIntent = null;
1118                if (sentIntents != null && sentIntents.size() > i) {
1119                    sentIntent = sentIntents.get(i);
1120                }
1121                handleNotInService(ss, sentIntent);
1122            }
1123            return;
1124        }
1125
1126        sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents);
1127    }
1128
1129    /**
1130     * Send an acknowledge message.
1131     * @param success indicates that last message was successfully received.
1132     * @param result result code indicating any error
1133     * @param response callback message sent when operation completes.
1134     */
1135    protected abstract void acknowledgeLastIncomingSms(boolean success,
1136            int result, Message response);
1137
1138    /**
1139     * Notify interested apps if the framework has rejected an incoming SMS,
1140     * and send an acknowledge message to the network.
1141     * @param success indicates that last message was successfully received.
1142     * @param result result code indicating any error
1143     * @param response callback message sent when operation completes.
1144     */
1145    private void notifyAndAcknowledgeLastIncomingSms(boolean success,
1146            int result, Message response) {
1147        if (!success) {
1148            // broadcast SMS_REJECTED_ACTION intent
1149            Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
1150            intent.putExtra("result", result);
1151            mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
1152            mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
1153        }
1154        acknowledgeLastIncomingSms(success, result, response);
1155    }
1156
1157    /**
1158     * Keeps track of an SMS that has been sent to the RIL, until it has
1159     * successfully been sent, or we're done trying.
1160     *
1161     */
1162    protected static final class SmsTracker {
1163        // fields need to be public for derived SmsDispatchers
1164        public final HashMap<String, Object> mData;
1165        public int mRetryCount;
1166        public int mMessageRef;
1167
1168        public final PendingIntent mSentIntent;
1169        public final PendingIntent mDeliveryIntent;
1170
1171        public final String mAppPackage;
1172        public final String mDestAddress;
1173
1174        public SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
1175                PendingIntent deliveryIntent, String appPackage, String destAddr) {
1176            mData = data;
1177            mSentIntent = sentIntent;
1178            mDeliveryIntent = deliveryIntent;
1179            mRetryCount = 0;
1180            mAppPackage = appPackage;
1181            mDestAddress = destAddr;
1182        }
1183
1184        /**
1185         * Returns whether this tracker holds a multi-part SMS.
1186         * @return true if the tracker holds a multi-part SMS; false otherwise
1187         */
1188        protected boolean isMultipart() {
1189            HashMap map = mData;
1190            return map.containsKey("parts");
1191        }
1192    }
1193
1194    /**
1195     * Dialog listener for SMS confirmation dialog.
1196     */
1197    private final class ConfirmDialogListener
1198            implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
1199
1200        private final SmsTracker mTracker;
1201
1202        ConfirmDialogListener(SmsTracker tracker) {
1203            mTracker = tracker;
1204        }
1205
1206        @Override
1207        public void onClick(DialogInterface dialog, int which) {
1208            if (which == DialogInterface.BUTTON_POSITIVE) {
1209                Log.d(TAG, "CONFIRM sending SMS");
1210                sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTracker));
1211            } else if (which == DialogInterface.BUTTON_NEGATIVE) {
1212                Log.d(TAG, "DENY sending SMS");
1213                sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
1214            }
1215        }
1216
1217        @Override
1218        public void onCancel(DialogInterface dialog) {
1219            Log.d(TAG, "dialog dismissed: don't send SMS");
1220            sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
1221        }
1222    }
1223
1224    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
1225        @Override
1226        public void onReceive(Context context, Intent intent) {
1227            // Assume the intent is one of the SMS receive intents that
1228            // was sent as an ordered broadcast.  Check result and ACK.
1229            int rc = getResultCode();
1230            boolean success = (rc == Activity.RESULT_OK)
1231                    || (rc == Intents.RESULT_SMS_HANDLED);
1232
1233            // For a multi-part message, this only ACKs the last part.
1234            // Previous parts were ACK'd as they were received.
1235            acknowledgeLastIncomingSms(success, rc, null);
1236        }
1237    };
1238
1239    protected void dispatchBroadcastMessage(SmsCbMessage message) {
1240        if (message.isEmergencyMessage()) {
1241            Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
1242            intent.putExtra("message", message);
1243            Log.d(TAG, "Dispatching emergency SMS CB");
1244            dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION);
1245        } else {
1246            Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
1247            intent.putExtra("message", message);
1248            Log.d(TAG, "Dispatching SMS CB");
1249            dispatch(intent, RECEIVE_SMS_PERMISSION);
1250        }
1251    }
1252}
1253