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