SMSDispatcher.java revision 18573e9281d6e5621fa1663dac29b558291913d7
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.provider.Telephony;
39import android.provider.Telephony.Sms.Intents;
40import android.provider.Settings;
41import android.telephony.SmsMessage;
42import android.telephony.ServiceState;
43import android.util.Config;
44import android.util.Log;
45import android.view.WindowManager;
46
47import com.android.internal.telephony.CommandsInterface;
48import com.android.internal.telephony.SmsMessageBase;
49import com.android.internal.telephony.SmsResponse;
50import com.android.internal.telephony.WapPushOverSms;
51import com.android.internal.util.HexDump;
52
53import java.io.ByteArrayOutputStream;
54import java.util.ArrayList;
55import java.util.HashMap;
56import java.util.Random;
57
58import com.android.internal.R;
59
60import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
61import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
62import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
63import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
64import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
65import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
66
67
68public abstract class SMSDispatcher extends Handler {
69    private static final String TAG = "SMS";
70
71    /** Default checking period for SMS sent without user permit */
72    private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
73
74    /** Default number of SMS sent in checking period without user permit */
75    private static final int DEFAULT_SMS_MAX_COUNT = 100;
76
77    /** Default timeout for SMS sent query */
78    private static final int DEFAULT_SMS_TIMOUEOUT = 6000;
79
80    protected static final String[] RAW_PROJECTION = new String[] {
81        "pdu",
82        "sequence",
83        "destination_port",
84    };
85
86    static final int MAIL_SEND_SMS = 1;
87
88    static final protected int EVENT_NEW_SMS = 1;
89
90    static final protected int EVENT_SEND_SMS_COMPLETE = 2;
91
92    /** Retry sending a previously failed SMS message */
93    static final protected int EVENT_SEND_RETRY = 3;
94
95    /** Status report received */
96    static final protected int EVENT_NEW_SMS_STATUS_REPORT = 5;
97
98    /** SIM/RUIM storage is full */
99    static final protected int EVENT_ICC_FULL = 6;
100
101    /** SMS confirm required */
102    static final protected int EVENT_POST_ALERT = 7;
103
104    /** Send the user confirmed SMS */
105    static final protected int EVENT_SEND_CONFIRMED_SMS = 8;
106
107    /** Alert is timeout */
108    static final protected int EVENT_ALERT_TIMEOUT = 9;
109
110    /** Stop the sending */
111    static final protected int EVENT_STOP_SENDING = 10;
112
113    /** Memory status reporting is acknowledged by RIL */
114    static final protected int EVENT_REPORT_MEMORY_STATUS_DONE = 11;
115
116    /** Radio is ON */
117    static final protected int EVENT_RADIO_ON = 12;
118
119    protected Phone mPhone;
120    protected Context mContext;
121    protected ContentResolver mResolver;
122    protected CommandsInterface mCm;
123
124    protected final WapPushOverSms mWapPush;
125
126    protected final Uri mRawUri = Uri.withAppendedPath(Telephony.Sms.CONTENT_URI, "raw");
127
128    /** Maximum number of times to retry sending a failed SMS. */
129    private static final int MAX_SEND_RETRIES = 3;
130    /** Delay before next send attempt on a failed SMS, in milliseconds. */
131    private static final int SEND_RETRY_DELAY = 2000;
132    /** single part SMS */
133    private static final int SINGLE_PART_SMS = 1;
134    /** Message sending queue limit */
135    private static final int MO_MSG_QUEUE_LIMIT = 5;
136
137    /**
138     * Message reference for a CONCATENATED_8_BIT_REFERENCE or
139     * CONCATENATED_16_BIT_REFERENCE message set.  Should be
140     * incremented for each set of concatenated messages.
141     */
142    private static int sConcatenatedRef;
143
144    private SmsCounter mCounter;
145
146    private ArrayList mSTrackers = new ArrayList(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 final int WAKE_LOCK_TIMEOUT = 5000;
156
157    private static SmsMessage mSmsMessage;
158    private static SmsMessageBase mSmsMessageBase;
159    private SmsMessageBase.SubmitPduBase mSubmitPduBase;
160
161    protected boolean mStorageAvailable = true;
162    protected boolean mReportMemoryStatusPending = false;
163
164    protected static int getNextConcatenatedRef() {
165        sConcatenatedRef += 1;
166        return sConcatenatedRef;
167    }
168
169    /**
170     *  Implement the per-application based SMS control, which only allows
171     *  a limit on the number of SMS/MMS messages an app can send in checking
172     *  period.
173     */
174    private class SmsCounter {
175        private int mCheckPeriod;
176        private int mMaxAllowed;
177        private HashMap<String, ArrayList<Long>> mSmsStamp;
178
179        /**
180         * Create SmsCounter
181         * @param mMax is the number of SMS allowed without user permit
182         * @param mPeriod is the checking period
183         */
184        SmsCounter(int mMax, int mPeriod) {
185            mMaxAllowed = mMax;
186            mCheckPeriod = mPeriod;
187            mSmsStamp = new HashMap<String, ArrayList<Long>> ();
188        }
189
190        /**
191         * Check to see if an application allow to send new SMS messages
192         *
193         * @param appName is the application sending sms
194         * @param smsWaiting is the number of new sms wants to be sent
195         * @return true if application is allowed to send the requested number
196         *         of new sms messages
197         */
198        boolean check(String appName, int smsWaiting) {
199            if (!mSmsStamp.containsKey(appName)) {
200                mSmsStamp.put(appName, new ArrayList<Long>());
201            }
202
203            return isUnderLimit(mSmsStamp.get(appName), smsWaiting);
204        }
205
206        private boolean isUnderLimit(ArrayList<Long> sent, int smsWaiting) {
207            Long ct =  System.currentTimeMillis();
208
209            Log.d(TAG, "SMS send size=" + sent.size() + "time=" + ct);
210
211            while (sent.size() > 0 && (ct - sent.get(0)) > mCheckPeriod ) {
212                    sent.remove(0);
213            }
214
215
216            if ( (sent.size() + smsWaiting) <= mMaxAllowed) {
217                for (int i = 0; i < smsWaiting; i++ ) {
218                    sent.add(ct);
219                }
220                return true;
221            }
222            return false;
223        }
224    }
225
226    protected SMSDispatcher(PhoneBase phone) {
227        mPhone = phone;
228        mWapPush = new WapPushOverSms(phone, this);
229        mContext = phone.getContext();
230        mResolver = mContext.getContentResolver();
231        mCm = phone.mCM;
232
233        createWakelock();
234
235        int check_period = Settings.Gservices.getInt(mResolver,
236                Settings.Gservices.SMS_OUTGOING_CHECK_INTERVAL_MS,
237                DEFAULT_SMS_CHECK_PERIOD);
238        int max_count = Settings.Gservices.getInt(mResolver,
239                Settings.Gservices.SMS_OUTGOING_CEHCK_MAX_COUNT,
240                DEFAULT_SMS_MAX_COUNT);
241        mCounter = new SmsCounter(max_count, check_period);
242
243        mCm.setOnNewSMS(this, EVENT_NEW_SMS, null);
244        mCm.setOnSmsStatus(this, EVENT_NEW_SMS_STATUS_REPORT, null);
245        mCm.setOnIccSmsFull(this, EVENT_ICC_FULL, null);
246        mCm.registerForOn(this, EVENT_RADIO_ON, null);
247
248        // Don't always start message ref at 0.
249        sConcatenatedRef = new Random().nextInt(256);
250
251        // Register for device storage intents.  Use these to notify the RIL
252        // that storage for SMS is or is not available.
253        // TODO: Revisit this for a later release.  Storage reporting should
254        // rely more on application indication.
255        IntentFilter filter = new IntentFilter();
256        filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
257        filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
258        mContext.registerReceiver(mResultReceiver, filter);
259    }
260
261    public void dispose() {
262        mCm.unSetOnNewSMS(this);
263        mCm.unSetOnSmsStatus(this);
264        mCm.unSetOnIccSmsFull(this);
265        mCm.unregisterForOn(this);
266    }
267
268    protected void finalize() {
269        Log.d(TAG, "SMSDispatcher finalized");
270    }
271
272
273    /* TODO: Need to figure out how to keep track of status report routing in a
274     *       persistent manner. If the phone process restarts (reboot or crash),
275     *       we will lose this list and any status reports that come in after
276     *       will be dropped.
277     */
278    /** Sent messages awaiting a delivery status report. */
279    protected final ArrayList<SmsTracker> deliveryPendingList = new ArrayList<SmsTracker>();
280
281    /**
282     * Handles events coming from the phone stack. Overridden from handler.
283     *
284     * @param msg the message to handle
285     */
286    @Override
287    public void handleMessage(Message msg) {
288        AsyncResult ar;
289
290        switch (msg.what) {
291        case EVENT_NEW_SMS:
292            // A new SMS has been received by the device
293            if (Config.LOGD) {
294                Log.d(TAG, "New SMS Message Received");
295            }
296
297            SmsMessage sms;
298
299            ar = (AsyncResult) msg.obj;
300
301            if (ar.exception != null) {
302                Log.e(TAG, "Exception processing incoming SMS. Exception:" + ar.exception);
303                return;
304            }
305
306            sms = (SmsMessage) ar.result;
307            try {
308                int result = dispatchMessage(sms.mWrappedSmsMessage);
309                if (result != Activity.RESULT_OK) {
310                    // RESULT_OK means that message was broadcast for app(s) to handle.
311                    // Any other result, we should ack here.
312                    boolean handled = (result == Intents.RESULT_SMS_HANDLED);
313                    notifyAndAcknowledgeLastIncomingSms(handled, result, null);
314                }
315            } catch (RuntimeException ex) {
316                Log.e(TAG, "Exception dispatching message", ex);
317                notifyAndAcknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
318            }
319
320            break;
321
322        case EVENT_SEND_SMS_COMPLETE:
323            // An outbound SMS has been successfully transferred, or failed.
324            handleSendComplete((AsyncResult) msg.obj);
325            break;
326
327        case EVENT_SEND_RETRY:
328            sendSms((SmsTracker) msg.obj);
329            break;
330
331        case EVENT_NEW_SMS_STATUS_REPORT:
332            handleStatusReport((AsyncResult)msg.obj);
333            break;
334
335        case EVENT_ICC_FULL:
336            handleIccFull();
337            break;
338
339        case EVENT_POST_ALERT:
340            handleReachSentLimit((SmsTracker)(msg.obj));
341            break;
342
343        case EVENT_ALERT_TIMEOUT:
344            ((AlertDialog)(msg.obj)).dismiss();
345            msg.obj = null;
346            if (mSTrackers.isEmpty() == false) {
347                try {
348                    SmsTracker sTracker = (SmsTracker)mSTrackers.remove(0);
349                    sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
350                } catch (CanceledException ex) {
351                    Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
352                }
353            }
354            if (Config.LOGD) {
355                Log.d(TAG, "EVENT_ALERT_TIMEOUT, message stop sending");
356            }
357            break;
358
359        case EVENT_SEND_CONFIRMED_SMS:
360            if (mSTrackers.isEmpty() == false) {
361                SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
362                if (isMultipartTracker(sTracker)) {
363                    sendMultipartSms(sTracker);
364                } else {
365                    sendSms(sTracker);
366                }
367                removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
368            }
369            break;
370
371        case EVENT_STOP_SENDING:
372            if (mSTrackers.isEmpty() == false) {
373                // Remove the latest one.
374                try {
375                    SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
376                    sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
377                } catch (CanceledException ex) {
378                    Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
379                }
380                removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
381            }
382            break;
383
384        case EVENT_REPORT_MEMORY_STATUS_DONE:
385            ar = (AsyncResult)msg.obj;
386            if (ar.exception != null) {
387                mReportMemoryStatusPending = true;
388                Log.v(TAG, "Memory status report to modem pending : mStorageAvailable = "
389                        + mStorageAvailable);
390            } else {
391                mReportMemoryStatusPending = false;
392            }
393            break;
394
395        case EVENT_RADIO_ON:
396            if (mReportMemoryStatusPending) {
397                Log.v(TAG, "Sending pending memory status report : mStorageAvailable = "
398                        + mStorageAvailable);
399                mCm.reportSmsMemoryStatus(mStorageAvailable,
400                        obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
401            }
402            break;
403        }
404    }
405
406    private void createWakelock() {
407        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
408        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
409        mWakeLock.setReferenceCounted(true);
410    }
411
412    /**
413     * Grabs a wake lock and sends intent as an ordered broadcast.
414     * The resultReceiver will check for errors and ACK/NACK back
415     * to the RIL.
416     *
417     * @param intent intent to broadcast
418     * @param permission Receivers are required to have this permission
419     */
420    void dispatch(Intent intent, String permission) {
421        // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
422        // receivers time to take their own wake locks.
423        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
424        mContext.sendOrderedBroadcast(intent, permission, mResultReceiver,
425                this, Activity.RESULT_OK, null, null);
426    }
427
428    /**
429     * Called when SIM_FULL message is received from the RIL.  Notifies interested
430     * parties that SIM storage for SMS messages is full.
431     */
432    private void handleIccFull(){
433        // broadcast SIM_FULL intent
434        Intent intent = new Intent(Intents.SIM_FULL_ACTION);
435        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
436        mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
437    }
438
439    /**
440     * Called when a status report is received.  This should correspond to
441     * a previously successful SEND.
442     *
443     * @param ar AsyncResult passed into the message handler.  ar.result should
444     *           be a String representing the status report PDU, as ASCII hex.
445     */
446    protected abstract void handleStatusReport(AsyncResult ar);
447
448    /**
449     * Called when SMS send completes. Broadcasts a sentIntent on success.
450     * On failure, either sets up retries or broadcasts a sentIntent with
451     * the failure in the result code.
452     *
453     * @param ar AsyncResult passed into the message handler.  ar.result should
454     *           an SmsResponse instance if send was successful.  ar.userObj
455     *           should be an SmsTracker instance.
456     */
457    protected void handleSendComplete(AsyncResult ar) {
458        SmsTracker tracker = (SmsTracker) ar.userObj;
459        PendingIntent sentIntent = tracker.mSentIntent;
460
461        if (ar.exception == null) {
462            if (Config.LOGD) {
463                Log.d(TAG, "SMS send complete. Broadcasting "
464                        + "intent: " + sentIntent);
465            }
466
467            if (tracker.mDeliveryIntent != null) {
468                // Expecting a status report.  Add it to the list.
469                int messageRef = ((SmsResponse)ar.result).messageRef;
470                tracker.mMessageRef = messageRef;
471                deliveryPendingList.add(tracker);
472            }
473
474            if (sentIntent != null) {
475                try {
476                    sentIntent.send(Activity.RESULT_OK);
477                } catch (CanceledException ex) {}
478            }
479        } else {
480            if (Config.LOGD) {
481                Log.d(TAG, "SMS send failed");
482            }
483
484            int ss = mPhone.getServiceState().getState();
485
486            if (ss != ServiceState.STATE_IN_SERVICE) {
487                handleNotInService(ss, tracker);
488            } else if ((((CommandException)(ar.exception)).getCommandError()
489                    == CommandException.Error.SMS_FAIL_RETRY) &&
490                   tracker.mRetryCount < MAX_SEND_RETRIES) {
491                // Retry after a delay if needed.
492                // TODO: According to TS 23.040, 9.2.3.6, we should resend
493                //       with the same TP-MR as the failed message, and
494                //       TP-RD set to 1.  However, we don't have a means of
495                //       knowing the MR for the failed message (EF_SMSstatus
496                //       may or may not have the MR corresponding to this
497                //       message, depending on the failure).  Also, in some
498                //       implementations this retry is handled by the baseband.
499                tracker.mRetryCount++;
500                Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
501                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
502            } else if (tracker.mSentIntent != null) {
503                int error = RESULT_ERROR_GENERIC_FAILURE;
504
505                if (((CommandException)(ar.exception)).getCommandError()
506                        == CommandException.Error.FDN_CHECK_FAILURE) {
507                    error = RESULT_ERROR_FDN_CHECK_FAILURE;
508                }
509                // Done retrying; return an error to the app.
510                try {
511                    Intent fillIn = new Intent();
512                    if (ar.result != null) {
513                        fillIn.putExtra("errorCode", ((SmsResponse)ar.result).errorCode);
514                    }
515                    tracker.mSentIntent.send(mContext, error, fillIn);
516
517                } catch (CanceledException ex) {}
518            }
519        }
520    }
521
522    /**
523     * Handles outbound message when the phone is not in service.
524     *
525     * @param ss     Current service state.  Valid values are:
526     *                  OUT_OF_SERVICE
527     *                  EMERGENCY_ONLY
528     *                  POWER_OFF
529     * @param tracker   An SmsTracker for the current message.
530     */
531    protected void handleNotInService(int ss, SmsTracker tracker) {
532        if (tracker.mSentIntent != null) {
533            try {
534                if (ss == ServiceState.STATE_POWER_OFF) {
535                    tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF);
536                } else {
537                    tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE);
538                }
539            } catch (CanceledException ex) {}
540        }
541    }
542
543    /**
544     * Dispatches an incoming SMS messages.
545     *
546     * @param sms the incoming message from the phone
547     * @return a result code from {@link Telephony.Sms.Intents}, or
548     *         {@link Activity#RESULT_OK} if the message has been broadcast
549     *         to applications
550     */
551    protected abstract int dispatchMessage(SmsMessageBase sms);
552
553
554    /**
555     * If this is the last part send the parts out to the application, otherwise
556     * the part is stored for later processing.
557     *
558     * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null.
559     * @return a result code from {@link Telephony.Sms.Intents}, or
560     *         {@link Activity#RESULT_OK} if the message has been broadcast
561     *         to applications
562     */
563    protected int processMessagePart(SmsMessageBase sms,
564            SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) {
565
566        // Lookup all other related parts
567        StringBuilder where = new StringBuilder("reference_number =");
568        where.append(concatRef.refNumber);
569        where.append(" AND address = ?");
570        String[] whereArgs = new String[] {sms.getOriginatingAddress()};
571
572        byte[][] pdus = null;
573        Cursor cursor = null;
574        try {
575            cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
576            int cursorCount = cursor.getCount();
577            if (cursorCount != concatRef.msgCount - 1) {
578                // We don't have all the parts yet, store this one away
579                ContentValues values = new ContentValues();
580                values.put("date", new Long(sms.getTimestampMillis()));
581                values.put("pdu", HexDump.toHexString(sms.getPdu()));
582                values.put("address", sms.getOriginatingAddress());
583                values.put("reference_number", concatRef.refNumber);
584                values.put("count", concatRef.msgCount);
585                values.put("sequence", concatRef.seqNumber);
586                if (portAddrs != null) {
587                    values.put("destination_port", portAddrs.destPort);
588                }
589                mResolver.insert(mRawUri, values);
590                return Intents.RESULT_SMS_HANDLED;
591            }
592
593            // All the parts are in place, deal with them
594            int pduColumn = cursor.getColumnIndex("pdu");
595            int sequenceColumn = cursor.getColumnIndex("sequence");
596
597            pdus = new byte[concatRef.msgCount][];
598            for (int i = 0; i < cursorCount; i++) {
599                cursor.moveToNext();
600                int cursorSequence = (int)cursor.getLong(sequenceColumn);
601                pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
602                        cursor.getString(pduColumn));
603            }
604            // This one isn't in the DB, so add it
605            pdus[concatRef.seqNumber - 1] = sms.getPdu();
606
607            // Remove the parts from the database
608            mResolver.delete(mRawUri, where.toString(), whereArgs);
609        } catch (SQLException e) {
610            Log.e(TAG, "Can't access multipart SMS database", e);
611            // TODO:  Would OUT_OF_MEMORY be more appropriate?
612            return Intents.RESULT_SMS_GENERIC_ERROR;
613        } finally {
614            if (cursor != null) cursor.close();
615        }
616
617        /**
618         * TODO(cleanup): The following code has duplicated logic with
619         * the radio-specific dispatchMessage code, which is fragile,
620         * in addition to being redundant.  Instead, if this method
621         * maybe returned the reassembled message (or just contents),
622         * the following code (which is not really related to
623         * reconstruction) could be better consolidated.
624         */
625
626        // Dispatch the PDUs to applications
627        if (portAddrs != null) {
628            if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
629                // Build up the data stream
630                ByteArrayOutputStream output = new ByteArrayOutputStream();
631                for (int i = 0; i < concatRef.msgCount; i++) {
632                    SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
633                    byte[] data = msg.getUserData();
634                    output.write(data, 0, data.length);
635                }
636                // Handle the PUSH
637                return mWapPush.dispatchWapPdu(output.toByteArray());
638            } else {
639                // The messages were sent to a port, so concoct a URI for it
640                dispatchPortAddressedPdus(pdus, portAddrs.destPort);
641            }
642        } else {
643            // The messages were not sent to a port
644            dispatchPdus(pdus);
645        }
646        return Activity.RESULT_OK;
647    }
648
649    /**
650     * Dispatches standard PDUs to interested applications
651     *
652     * @param pdus The raw PDUs making up the message
653     */
654    protected void dispatchPdus(byte[][] pdus) {
655        Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
656        intent.putExtra("pdus", pdus);
657        dispatch(intent, "android.permission.RECEIVE_SMS");
658    }
659
660    /**
661     * Dispatches port addressed PDUs to interested applications
662     *
663     * @param pdus The raw PDUs making up the message
664     * @param port The destination port of the messages
665     */
666    protected void dispatchPortAddressedPdus(byte[][] pdus, int port) {
667        Uri uri = Uri.parse("sms://localhost:" + port);
668        Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
669        intent.putExtra("pdus", pdus);
670        dispatch(intent, "android.permission.RECEIVE_SMS");
671    }
672
673    /**
674     * Send a data based SMS to a specific application port.
675     *
676     * @param destAddr the address to send the message to
677     * @param scAddr is the service center address or null to use
678     *  the current default SMSC
679     * @param destPort the port to deliver the message to
680     * @param data the body of the message to send
681     * @param sentIntent if not NULL this <code>PendingIntent</code> is
682     *  broadcast when the message is sucessfully sent, or failed.
683     *  The result code will be <code>Activity.RESULT_OK<code> for success,
684     *  or one of these errors:<br>
685     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
686     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
687     *  <code>RESULT_ERROR_NULL_PDU</code><br>
688     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
689     *  the extra "errorCode" containing a radio technology specific value,
690     *  generally only useful for troubleshooting.<br>
691     *  The per-application based SMS control checks sentIntent. If sentIntent
692     *  is NULL the caller will be checked against all unknown applicaitons,
693     *  which cause smaller number of SMS to be sent in checking period.
694     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
695     *  broadcast when the message is delivered to the recipient.  The
696     *  raw pdu of the status report is in the extended data ("pdu").
697     */
698    protected abstract void sendData(String destAddr, String scAddr, int destPort,
699            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent);
700
701    /**
702     * Send a text based SMS.
703     *
704     * @param destAddr the address to send the message to
705     * @param scAddr is the service center address or null to use
706     *  the current default SMSC
707     * @param text the body of the message to send
708     * @param sentIntent if not NULL this <code>PendingIntent</code> is
709     *  broadcast when the message is sucessfully sent, or failed.
710     *  The result code will be <code>Activity.RESULT_OK<code> for success,
711     *  or one of these errors:<br>
712     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
713     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
714     *  <code>RESULT_ERROR_NULL_PDU</code><br>
715     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
716     *  the extra "errorCode" containing a radio technology specific value,
717     *  generally only useful for troubleshooting.<br>
718     *  The per-application based SMS control checks sentIntent. If sentIntent
719     *  is NULL the caller will be checked against all unknown applications,
720     *  which cause smaller number of SMS to be sent in checking period.
721     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
722     *  broadcast when the message is delivered to the recipient.  The
723     *  raw pdu of the status report is in the extended data ("pdu").
724     */
725    protected abstract void sendText(String destAddr, String scAddr,
726            String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
727
728    /**
729     * Send a multi-part text based SMS.
730     *
731     * @param destAddr the address to send the message to
732     * @param scAddr is the service center address or null to use
733     *   the current default SMSC
734     * @param parts an <code>ArrayList</code> of strings that, in order,
735     *   comprise the original message
736     * @param sentIntents if not null, an <code>ArrayList</code> of
737     *   <code>PendingIntent</code>s (one for each message part) that is
738     *   broadcast when the corresponding message part has been sent.
739     *   The result code will be <code>Activity.RESULT_OK<code> for success,
740     *   or one of these errors:
741     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
742     *   <code>RESULT_ERROR_RADIO_OFF</code>
743     *   <code>RESULT_ERROR_NULL_PDU</code>.
744     *  The per-application based SMS control checks sentIntent. If sentIntent
745     *  is NULL the caller will be checked against all unknown applicaitons,
746     *  which cause smaller number of SMS to be sent in checking period.
747     * @param deliveryIntents if not null, an <code>ArrayList</code> of
748     *   <code>PendingIntent</code>s (one for each message part) that is
749     *   broadcast when the corresponding message part has been delivered
750     *   to the recipient.  The raw pdu of the status report is in the
751     *   extended data ("pdu").
752     */
753    protected abstract void sendMultipartText(String destAddr, String scAddr,
754            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
755            ArrayList<PendingIntent> deliveryIntents);
756
757    /**
758     * Send a SMS
759     *
760     * @param smsc the SMSC to send the message through, or NULL for the
761     *  defatult SMSC
762     * @param pdu the raw PDU to send
763     * @param sentIntent if not NULL this <code>Intent</code> is
764     *  broadcast when the message is sucessfully sent, or failed.
765     *  The result code will be <code>Activity.RESULT_OK<code> for success,
766     *  or one of these errors:
767     *  <code>RESULT_ERROR_GENERIC_FAILURE</code>
768     *  <code>RESULT_ERROR_RADIO_OFF</code>
769     *  <code>RESULT_ERROR_NULL_PDU</code>.
770     *  The per-application based SMS control checks sentIntent. If sentIntent
771     *  is NULL the caller will be checked against all unknown applicaitons,
772     *  which cause smaller number of SMS to be sent in checking period.
773     * @param deliveryIntent if not NULL this <code>Intent</code> is
774     *  broadcast when the message is delivered to the recipient.  The
775     *  raw pdu of the status report is in the extended data ("pdu").
776     */
777    protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
778            PendingIntent deliveryIntent) {
779        if (pdu == null) {
780            if (sentIntent != null) {
781                try {
782                    sentIntent.send(RESULT_ERROR_NULL_PDU);
783                } catch (CanceledException ex) {}
784            }
785            return;
786        }
787
788        HashMap<String, Object> map = new HashMap<String, Object>();
789        map.put("smsc", smsc);
790        map.put("pdu", pdu);
791
792        SmsTracker tracker = new SmsTracker(map, sentIntent,
793                deliveryIntent);
794        int ss = mPhone.getServiceState().getState();
795
796        if (ss != ServiceState.STATE_IN_SERVICE) {
797            handleNotInService(ss, tracker);
798        } else {
799            String appName = getAppNameByIntent(sentIntent);
800            if (mCounter.check(appName, SINGLE_PART_SMS)) {
801                sendSms(tracker);
802            } else {
803                sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
804            }
805        }
806    }
807
808    /**
809     * Post an alert while SMS needs user confirm.
810     *
811     * An SmsTracker for the current message.
812     */
813    protected void handleReachSentLimit(SmsTracker tracker) {
814        if (mSTrackers.size() >= MO_MSG_QUEUE_LIMIT) {
815            // Deny the sending when the queue limit is reached.
816            try {
817                tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
818            } catch (CanceledException ex) {
819                Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
820            }
821            return;
822        }
823
824        Resources r = Resources.getSystem();
825
826        String appName = getAppNameByIntent(tracker.mSentIntent);
827
828        AlertDialog d = new AlertDialog.Builder(mContext)
829                .setTitle(r.getString(R.string.sms_control_title))
830                .setMessage(appName + " " + r.getString(R.string.sms_control_message))
831                .setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
832                .setNegativeButton(r.getString(R.string.sms_control_no), mListener)
833                .create();
834
835        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
836        d.show();
837
838        mSTrackers.add(tracker);
839        sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
840                DEFAULT_SMS_TIMOUEOUT);
841    }
842
843    protected String getAppNameByIntent(PendingIntent intent) {
844        Resources r = Resources.getSystem();
845        return (intent != null) ? intent.getTargetPackage()
846            : r.getString(R.string.sms_control_default_app_name);
847    }
848
849    /**
850     * Send the message along to the radio.
851     *
852     * @param tracker holds the SMS message to send
853     */
854    protected abstract void sendSms(SmsTracker tracker);
855
856    /**
857     * Send the multi-part SMS based on multipart Sms tracker
858     *
859     * @param tracker holds the multipart Sms tracker ready to be sent
860     */
861    protected abstract void sendMultipartSms (SmsTracker tracker);
862
863    /**
864     * Activate or deactivate cell broadcast SMS.
865     *
866     * @param activate
867     *            0 = activate, 1 = deactivate
868     * @param response
869     *            Callback message is empty on completion
870     */
871    protected abstract void activateCellBroadcastSms(int activate, Message response);
872
873    /**
874     * Query the current configuration of cell broadcast SMS.
875     *
876     * @param response
877     *            Callback message contains the configuration from the modem on completion
878     *            @see #setCellBroadcastConfig
879     */
880    protected abstract void getCellBroadcastSmsConfig(Message response);
881
882    /**
883     * Configure cell broadcast SMS.
884     *
885     * @param configValuesArray
886     *          The first element defines the number of triples that follow.
887     *          A triple is made up of the service category, the language identifier
888     *          and a boolean that specifies whether the category is set active.
889     * @param response
890     *            Callback message is empty on completion
891     */
892    protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response);
893
894    /**
895     * Send an acknowledge message.
896     * @param success indicates that last message was successfully received.
897     * @param result result code indicating any error
898     * @param response callback message sent when operation completes.
899     */
900    protected abstract void acknowledgeLastIncomingSms(boolean success,
901            int result, Message response);
902
903    /**
904     * Notify interested apps if the framework has rejected an incoming SMS,
905     * and send an acknowledge message to the network.
906     * @param success indicates that last message was successfully received.
907     * @param result result code indicating any error
908     * @param response callback message sent when operation completes.
909     */
910    private void notifyAndAcknowledgeLastIncomingSms(boolean success,
911            int result, Message response) {
912        if (!success) {
913            // broadcast SMS_REJECTED_ACTION intent
914            Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
915            intent.putExtra("result", result);
916            mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
917            mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
918        }
919        acknowledgeLastIncomingSms(success, result, response);
920    }
921
922    /**
923     * Check if a SmsTracker holds multi-part Sms
924     *
925     * @param tracker a SmsTracker could hold a multi-part Sms
926     * @return true for tracker holds Multi-parts Sms
927     */
928    private boolean isMultipartTracker (SmsTracker tracker) {
929        HashMap map = tracker.mData;
930        return ( map.get("parts") != null);
931    }
932
933    /**
934     * Keeps track of an SMS that has been sent to the RIL, until it it has
935     * successfully been sent, or we're done trying.
936     *
937     */
938    static protected class SmsTracker {
939        // fields need to be public for derived SmsDispatchers
940        public HashMap mData;
941        public int mRetryCount;
942        public int mMessageRef;
943
944        public PendingIntent mSentIntent;
945        public PendingIntent mDeliveryIntent;
946
947        SmsTracker(HashMap data, PendingIntent sentIntent,
948                PendingIntent deliveryIntent) {
949            mData = data;
950            mSentIntent = sentIntent;
951            mDeliveryIntent = deliveryIntent;
952            mRetryCount = 0;
953        }
954    }
955
956    protected SmsTracker SmsTrackerFactory(HashMap data, PendingIntent sentIntent,
957            PendingIntent deliveryIntent) {
958        return new SmsTracker(data, sentIntent, deliveryIntent);
959    }
960
961    private DialogInterface.OnClickListener mListener =
962        new DialogInterface.OnClickListener() {
963
964            public void onClick(DialogInterface dialog, int which) {
965                if (which == DialogInterface.BUTTON_POSITIVE) {
966                    Log.d(TAG, "click YES to send out sms");
967                    sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
968                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
969                    Log.d(TAG, "click NO to stop sending");
970                    sendMessage(obtainMessage(EVENT_STOP_SENDING));
971                }
972            }
973        };
974
975        private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
976            @Override
977            public void onReceive(Context context, Intent intent) {
978                if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {
979                    mStorageAvailable = false;
980                    mCm.reportSmsMemoryStatus(false, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
981                } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
982                    mStorageAvailable = true;
983                    mCm.reportSmsMemoryStatus(true, obtainMessage(EVENT_REPORT_MEMORY_STATUS_DONE));
984                } else {
985                    // Assume the intent is one of the SMS receive intents that
986                    // was sent as an ordered broadcast.  Check result and ACK.
987                    int rc = getResultCode();
988                    boolean success = (rc == Activity.RESULT_OK)
989                                        || (rc == Intents.RESULT_SMS_HANDLED);
990
991                    // For a multi-part message, this only ACKs the last part.
992                    // Previous parts were ACK'd as they were received.
993                    acknowledgeLastIncomingSms(success, rc, null);
994                }
995            }
996
997        };
998}
999