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