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