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