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