SMSDispatcher.java revision 3960ced4638fdb24ddf904fcb6734dae0959671e
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                    int result = dispatchMessage(sms.mWrappedSmsMessage);
293                    if (result != Activity.RESULT_OK) {
294                        // RESULT_OK means that message was broadcast for app(s) to handle.
295                        // Any other result, we should ack here.
296                        boolean handled = (result == Intents.RESULT_SMS_HANDLED);
297                        acknowledgeLastIncomingSms(handled, result, null);
298                    }
299                } else {
300                    acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_OUT_OF_MEMORY, null);
301                }
302            } catch (RuntimeException ex) {
303                acknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
304            }
305
306            break;
307
308        case EVENT_SEND_SMS_COMPLETE:
309            // An outbound SMS has been successfully transferred, or failed.
310            handleSendComplete((AsyncResult) msg.obj);
311            break;
312
313        case EVENT_SEND_RETRY:
314            sendSms((SmsTracker) msg.obj);
315            break;
316
317        case EVENT_NEW_SMS_STATUS_REPORT:
318            handleStatusReport((AsyncResult)msg.obj);
319            break;
320
321        case EVENT_ICC_FULL:
322            handleIccFull();
323            break;
324
325        case EVENT_POST_ALERT:
326            handleReachSentLimit((SmsTracker)(msg.obj));
327            break;
328
329        case EVENT_ALERT_TIMEOUT:
330            ((AlertDialog)(msg.obj)).dismiss();
331            msg.obj = null;
332            mSTracker = null;
333            break;
334
335        case EVENT_SEND_CONFIRMED_SMS:
336            if (mSTracker!=null) {
337                if (isMultipartTracker(mSTracker)) {
338                    sendMultipartSms(mSTracker);
339                } else {
340                    sendSms(mSTracker);
341                }
342                mSTracker = null;
343            }
344            break;
345        }
346    }
347
348    private void createWakelock() {
349        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
350        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
351        mWakeLock.setReferenceCounted(true);
352    }
353
354    /**
355     * Grabs a wake lock and sends intent as an ordered broadcast.
356     * The resultReceiver will check for errors and ACK/NACK back
357     * to the RIL.
358     *
359     * @param intent intent to broadcast
360     * @param permission Receivers are required to have this permission
361     */
362    void dispatch(Intent intent, String permission) {
363        // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
364        // receivers time to take their own wake locks.
365        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
366        mContext.sendOrderedBroadcast(intent, permission, mResultReceiver,
367                this, Activity.RESULT_OK, null, null);
368    }
369
370    /**
371     * Called when SIM_FULL message is received from the RIL.  Notifies interested
372     * parties that SIM storage for SMS messages is full.
373     */
374    private void handleIccFull(){
375        // broadcast SIM_FULL intent
376        Intent intent = new Intent(Intents.SIM_FULL_ACTION);
377        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
378        mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
379    }
380
381    /**
382     * Called when a status report is received.  This should correspond to
383     * a previously successful SEND.
384     *
385     * @param ar AsyncResult passed into the message handler.  ar.result should
386     *           be a String representing the status report PDU, as ASCII hex.
387     */
388    protected abstract void handleStatusReport(AsyncResult ar);
389
390    /**
391     * Called when SMS send completes. Broadcasts a sentIntent on success.
392     * On failure, either sets up retries or broadcasts a sentIntent with
393     * the failure in the result code.
394     *
395     * @param ar AsyncResult passed into the message handler.  ar.result should
396     *           an SmsResponse instance if send was successful.  ar.userObj
397     *           should be an SmsTracker instance.
398     */
399    protected void handleSendComplete(AsyncResult ar) {
400        SmsTracker tracker = (SmsTracker) ar.userObj;
401        PendingIntent sentIntent = tracker.mSentIntent;
402
403        if (ar.exception == null) {
404            if (Config.LOGD) {
405                Log.d(TAG, "SMS send complete. Broadcasting "
406                        + "intent: " + sentIntent);
407            }
408
409            if (tracker.mDeliveryIntent != null) {
410                // Expecting a status report.  Add it to the list.
411                int messageRef = ((SmsResponse)ar.result).messageRef;
412                tracker.mMessageRef = messageRef;
413                deliveryPendingList.add(tracker);
414            }
415
416            if (sentIntent != null) {
417                try {
418                    sentIntent.send(Activity.RESULT_OK);
419                } catch (CanceledException ex) {}
420            }
421        } else {
422            if (Config.LOGD) {
423                Log.d(TAG, "SMS send failed");
424            }
425
426            int ss = mPhone.getServiceState().getState();
427
428            if (ss != ServiceState.STATE_IN_SERVICE) {
429                handleNotInService(ss, tracker);
430            } else if ((((CommandException)(ar.exception)).getCommandError()
431                    == CommandException.Error.SMS_FAIL_RETRY) &&
432                   tracker.mRetryCount < MAX_SEND_RETRIES) {
433                // Retry after a delay if needed.
434                // TODO: According to TS 23.040, 9.2.3.6, we should resend
435                //       with the same TP-MR as the failed message, and
436                //       TP-RD set to 1.  However, we don't have a means of
437                //       knowing the MR for the failed message (EF_SMSstatus
438                //       may or may not have the MR corresponding to this
439                //       message, depending on the failure).  Also, in some
440                //       implementations this retry is handled by the baseband.
441                tracker.mRetryCount++;
442                Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
443                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
444            } else if (tracker.mSentIntent != null) {
445                // Done retrying; return an error to the app.
446                try {
447                    tracker.mSentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
448                } catch (CanceledException ex) {}
449            }
450        }
451    }
452
453    /**
454     * Handles outbound message when the phone is not in service.
455     *
456     * @param ss     Current service state.  Valid values are:
457     *                  OUT_OF_SERVICE
458     *                  EMERGENCY_ONLY
459     *                  POWER_OFF
460     * @param tracker   An SmsTracker for the current message.
461     */
462    protected void handleNotInService(int ss, SmsTracker tracker) {
463        if (tracker.mSentIntent != null) {
464            try {
465                if (ss == ServiceState.STATE_POWER_OFF) {
466                    tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF);
467                } else {
468                    tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE);
469                }
470            } catch (CanceledException ex) {}
471        }
472    }
473
474    /**
475     * Dispatches an incoming SMS messages.
476     *
477     * @param sms the incoming message from the phone
478     * @return a result code from {@link Telephony.Sms.Intents}, or
479     *         {@link Activity#RESULT_OK} if the message has been broadcast
480     *         to applications
481     */
482    protected abstract int dispatchMessage(SmsMessageBase sms);
483
484
485    /**
486     * If this is the last part send the parts out to the application, otherwise
487     * the part is stored for later processing.
488     *
489     * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null.
490     * @return a result code from {@link Telephony.Sms.Intents}, or
491     *         {@link Activity#RESULT_OK} if the message has been broadcast
492     *         to applications
493     */
494    protected int processMessagePart(SmsMessageBase sms,
495            SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) {
496
497        // Lookup all other related parts
498        StringBuilder where = new StringBuilder("reference_number =");
499        where.append(concatRef.refNumber);
500        where.append(" AND address = ?");
501        String[] whereArgs = new String[] {sms.getOriginatingAddress()};
502
503        byte[][] pdus = null;
504        Cursor cursor = null;
505        try {
506            cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
507            int cursorCount = cursor.getCount();
508            if (cursorCount != concatRef.msgCount - 1) {
509                // We don't have all the parts yet, store this one away
510                ContentValues values = new ContentValues();
511                values.put("date", new Long(sms.getTimestampMillis()));
512                values.put("pdu", HexDump.toHexString(sms.getPdu()));
513                values.put("address", sms.getOriginatingAddress());
514                values.put("reference_number", concatRef.refNumber);
515                values.put("count", concatRef.msgCount);
516                values.put("sequence", concatRef.seqNumber);
517                if (portAddrs != null) {
518                    values.put("destination_port", portAddrs.destPort);
519                }
520                mResolver.insert(mRawUri, values);
521                return Intents.RESULT_SMS_HANDLED;
522            }
523
524            // All the parts are in place, deal with them
525            int pduColumn = cursor.getColumnIndex("pdu");
526            int sequenceColumn = cursor.getColumnIndex("sequence");
527
528            pdus = new byte[concatRef.msgCount][];
529            for (int i = 0; i < cursorCount; i++) {
530                cursor.moveToNext();
531                int cursorSequence = (int)cursor.getLong(sequenceColumn);
532                pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
533                        cursor.getString(pduColumn));
534            }
535            // This one isn't in the DB, so add it
536            pdus[concatRef.seqNumber - 1] = sms.getPdu();
537
538            // Remove the parts from the database
539            mResolver.delete(mRawUri, where.toString(), whereArgs);
540        } catch (SQLException e) {
541            Log.e(TAG, "Can't access multipart SMS database", e);
542            // TODO:  Would OUT_OF_MEMORY be more appropriate?
543            return Intents.RESULT_SMS_GENERIC_ERROR;
544        } finally {
545            if (cursor != null) cursor.close();
546        }
547
548        /**
549         * TODO(cleanup): The following code has duplicated logic with
550         * the radio-specific dispatchMessage code, which is fragile,
551         * in addition to being redundant.  Instead, if this method
552         * maybe returned the reassembled message (or just contents),
553         * the following code (which is not really related to
554         * reconstruction) could be better consolidated.
555         */
556
557        // Dispatch the PDUs to applications
558        if (portAddrs != null) {
559            if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
560                // Build up the data stream
561                ByteArrayOutputStream output = new ByteArrayOutputStream();
562                for (int i = 0; i < concatRef.msgCount; i++) {
563                    SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
564                    byte[] data = msg.getUserData();
565                    output.write(data, 0, data.length);
566                }
567                // Handle the PUSH
568                return mWapPush.dispatchWapPdu(output.toByteArray());
569            } else {
570                // The messages were sent to a port, so concoct a URI for it
571                dispatchPortAddressedPdus(pdus, portAddrs.destPort);
572            }
573        } else {
574            // The messages were not sent to a port
575            dispatchPdus(pdus);
576        }
577        return Activity.RESULT_OK;
578    }
579
580    /**
581     * Dispatches standard PDUs to interested applications
582     *
583     * @param pdus The raw PDUs making up the message
584     */
585    protected void dispatchPdus(byte[][] pdus) {
586        Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
587        intent.putExtra("pdus", pdus);
588        dispatch(intent, "android.permission.RECEIVE_SMS");
589    }
590
591    /**
592     * Dispatches port addressed PDUs to interested applications
593     *
594     * @param pdus The raw PDUs making up the message
595     * @param port The destination port of the messages
596     */
597    protected void dispatchPortAddressedPdus(byte[][] pdus, int port) {
598        Uri uri = Uri.parse("sms://localhost:" + port);
599        Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
600        intent.putExtra("pdus", pdus);
601        dispatch(intent, "android.permission.RECEIVE_SMS");
602    }
603
604
605    /**
606     * Send a multi-part text based SMS.
607     *
608     * @param destinationAddress the address to send the message to
609     * @param scAddress is the service center address or null to use
610     *   the current default SMSC
611     * @param parts an <code>ArrayList</code> of strings that, in order,
612     *   comprise the original message
613     * @param sentIntents 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 sent.
616     *   The result code will be <code>Activity.RESULT_OK<code> for success,
617     *   or one of these errors:
618     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
619     *   <code>RESULT_ERROR_RADIO_OFF</code>
620     *   <code>RESULT_ERROR_NULL_PDU</code>.
621     *  The per-application based SMS control checks sentIntent. If sentIntent
622     *  is NULL the caller will be checked against all unknown applicaitons,
623     *  which cause smaller number of SMS to be sent in checking period.
624     * @param deliveryIntents if not null, an <code>ArrayList</code> of
625     *   <code>PendingIntent</code>s (one for each message part) that is
626     *   broadcast when the corresponding message part has been delivered
627     *   to the recipient.  The raw pdu of the status report is in the
628     *   extended data ("pdu").
629     */
630    protected abstract void sendMultipartText(String destinationAddress, String scAddress,
631            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
632            ArrayList<PendingIntent> deliveryIntents);
633
634    /**
635     * Send a SMS
636     *
637     * @param smsc the SMSC to send the message through, or NULL for the
638     *  defatult SMSC
639     * @param pdu the raw PDU to send
640     * @param sentIntent if not NULL this <code>Intent</code> is
641     *  broadcast when the message is sucessfully sent, or failed.
642     *  The result code will be <code>Activity.RESULT_OK<code> for success,
643     *  or one of these errors:
644     *  <code>RESULT_ERROR_GENERIC_FAILURE</code>
645     *  <code>RESULT_ERROR_RADIO_OFF</code>
646     *  <code>RESULT_ERROR_NULL_PDU</code>.
647     *  The per-application based SMS control checks sentIntent. If sentIntent
648     *  is NULL the caller will be checked against all unknown applicaitons,
649     *  which cause smaller number of SMS to be sent in checking period.
650     * @param deliveryIntent if not NULL this <code>Intent</code> is
651     *  broadcast when the message is delivered to the recipient.  The
652     *  raw pdu of the status report is in the extended data ("pdu").
653     */
654    protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
655            PendingIntent deliveryIntent) {
656        if (pdu == null) {
657            if (sentIntent != null) {
658                try {
659                    sentIntent.send(RESULT_ERROR_NULL_PDU);
660                } catch (CanceledException ex) {}
661            }
662            return;
663        }
664
665        HashMap<String, Object> map = new HashMap<String, Object>();
666        map.put("smsc", smsc);
667        map.put("pdu", pdu);
668
669        SmsTracker tracker = new SmsTracker(map, sentIntent,
670                deliveryIntent);
671        int ss = mPhone.getServiceState().getState();
672
673        if (ss != ServiceState.STATE_IN_SERVICE) {
674            handleNotInService(ss, tracker);
675        } else {
676            String appName = getAppNameByIntent(sentIntent);
677            if (mCounter.check(appName, SINGLE_PART_SMS)) {
678                sendSms(tracker);
679            } else {
680                sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
681            }
682        }
683    }
684
685    /**
686     * Post an alert while SMS needs user confirm.
687     *
688     * An SmsTracker for the current message.
689     */
690    protected void handleReachSentLimit(SmsTracker tracker) {
691
692        Resources r = Resources.getSystem();
693
694        String appName = getAppNameByIntent(tracker.mSentIntent);
695
696        AlertDialog d = new AlertDialog.Builder(mContext)
697                .setTitle(r.getString(R.string.sms_control_title))
698                .setMessage(appName + " " + r.getString(R.string.sms_control_message))
699                .setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
700                .setNegativeButton(r.getString(R.string.sms_control_no), null)
701                .create();
702
703        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
704        d.show();
705
706        mSTracker = tracker;
707        sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
708                DEFAULT_SMS_TIMOUEOUT);
709    }
710
711    protected String getAppNameByIntent(PendingIntent intent) {
712        Resources r = Resources.getSystem();
713        return (intent != null) ? intent.getTargetPackage()
714            : r.getString(R.string.sms_control_default_app_name);
715    }
716
717    /**
718     * Send the message along to the radio.
719     *
720     * @param tracker holds the SMS message to send
721     */
722    protected abstract void sendSms(SmsTracker tracker);
723
724    /**
725     * Send the multi-part SMS based on multipart Sms tracker
726     *
727     * @param tracker holds the multipart Sms tracker ready to be sent
728     */
729    protected abstract void sendMultipartSms (SmsTracker tracker);
730
731    /**
732     * Activate or deactivate cell broadcast SMS.
733     *
734     * @param activate
735     *            0 = activate, 1 = deactivate
736     * @param response
737     *            Callback message is empty on completion
738     */
739    protected abstract void activateCellBroadcastSms(int activate, Message response);
740
741    /**
742     * Query the current configuration of cell broadcast SMS.
743     *
744     * @param response
745     *            Callback message contains the configuration from the modem on completion
746     *            @see #setCellBroadcastConfig
747     */
748    protected abstract void getCellBroadcastSmsConfig(Message response);
749
750    /**
751     * Configure cell broadcast SMS.
752     *
753     * @param configValuesArray
754     *          The first element defines the number of triples that follow.
755     *          A triple is made up of the service category, the language identifier
756     *          and a boolean that specifies whether the category is set active.
757     * @param response
758     *            Callback message is empty on completion
759     */
760    protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response);
761
762    /**
763     * Send an acknowledge message.
764     * @param success indicates that last message was successfully received.
765     * @param result result code indicating any error
766     * @param response callback message sent when operation completes.
767     */
768    protected abstract void acknowledgeLastIncomingSms(boolean success,
769            int result, Message response);
770
771    /**
772     * Check if a SmsTracker holds multi-part Sms
773     *
774     * @param tracker a SmsTracker could hold a multi-part Sms
775     * @return true for tracker holds Multi-parts Sms
776     */
777    private boolean isMultipartTracker (SmsTracker tracker) {
778        HashMap map = tracker.mData;
779        return ( map.get("parts") != null);
780    }
781
782    /**
783     * Keeps track of an SMS that has been sent to the RIL, until it it has
784     * successfully been sent, or we're done trying.
785     *
786     */
787    static protected class SmsTracker {
788        // fields need to be public for derived SmsDispatchers
789        public HashMap mData;
790        public int mRetryCount;
791        public int mMessageRef;
792
793        public PendingIntent mSentIntent;
794        public PendingIntent mDeliveryIntent;
795
796        SmsTracker(HashMap data, PendingIntent sentIntent,
797                PendingIntent deliveryIntent) {
798            mData = data;
799            mSentIntent = sentIntent;
800            mDeliveryIntent = deliveryIntent;
801            mRetryCount = 0;
802        }
803    }
804
805    protected SmsTracker SmsTrackerFactory(HashMap data, PendingIntent sentIntent,
806            PendingIntent deliveryIntent) {
807        return new SmsTracker(data, sentIntent, deliveryIntent);
808    }
809
810    private DialogInterface.OnClickListener mListener =
811        new DialogInterface.OnClickListener() {
812
813            public void onClick(DialogInterface dialog, int which) {
814                if (which == DialogInterface.BUTTON_POSITIVE) {
815                    Log.d(TAG, "click YES to send out sms");
816                    sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
817                }
818            }
819        };
820
821        private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
822            @Override
823            public void onReceive(Context context, Intent intent) {
824                if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {
825                    mStorageAvailable = false;
826                    mCm.reportSmsMemoryStatus(false, null);
827                } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
828                    mStorageAvailable = true;
829                    mCm.reportSmsMemoryStatus(true, null);
830                } else {
831                    // Assume the intent is one of the SMS receive intents that
832                    // was sent as an ordered broadcast.  Check result and ACK.
833                    int rc = getResultCode();
834                    boolean success = (rc == Activity.RESULT_OK)
835                                        || (rc == Intents.RESULT_SMS_HANDLED);
836
837                    // For a multi-part message, this only ACKs the last part.
838                    // Previous parts were ACK'd as they were received.
839                    acknowledgeLastIncomingSms(success, rc, null);
840                }
841            }
842
843        };
844}
845