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