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