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