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