SMSDispatcher.java revision a01726a7fdd872cd8e1e8a9dfa52fb9cdbf01019
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.Gservices.getInt(mResolver,
228                Settings.Gservices.SMS_OUTGOING_CHECK_INTERVAL_MS,
229                DEFAULT_SMS_CHECK_PERIOD);
230        int max_count = Settings.Gservices.getInt(mResolver,
231                Settings.Gservices.SMS_OUTGOING_CEHCK_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                notifyAndAcknowledgeLastIncomingSms(false, Intents.RESULT_SMS_GENERIC_ERROR, null);
307            }
308
309            break;
310
311        case EVENT_SEND_SMS_COMPLETE:
312            // An outbound SMS has been successfully transferred, or failed.
313            handleSendComplete((AsyncResult) msg.obj);
314            break;
315
316        case EVENT_SEND_RETRY:
317            sendSms((SmsTracker) msg.obj);
318            break;
319
320        case EVENT_NEW_SMS_STATUS_REPORT:
321            handleStatusReport((AsyncResult)msg.obj);
322            break;
323
324        case EVENT_ICC_FULL:
325            handleIccFull();
326            break;
327
328        case EVENT_POST_ALERT:
329            handleReachSentLimit((SmsTracker)(msg.obj));
330            break;
331
332        case EVENT_ALERT_TIMEOUT:
333            ((AlertDialog)(msg.obj)).dismiss();
334            msg.obj = null;
335            if (mSTrackers.isEmpty() == false) {
336                try {
337                    SmsTracker sTracker = (SmsTracker)mSTrackers.remove(0);
338                    sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
339                } catch (CanceledException ex) {
340                    Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
341                }
342            }
343            if (Config.LOGD) {
344                Log.d(TAG, "EVENT_ALERT_TIMEOUT, message stop sending");
345            }
346            break;
347
348        case EVENT_SEND_CONFIRMED_SMS:
349            if (mSTrackers.isEmpty() == false) {
350                SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
351                if (isMultipartTracker(sTracker)) {
352                    sendMultipartSms(sTracker);
353                } else {
354                    sendSms(sTracker);
355                }
356                removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
357            }
358            break;
359
360        case EVENT_STOP_SENDING:
361            if (mSTrackers.isEmpty() == false) {
362                // Remove the latest one.
363                try {
364                    SmsTracker sTracker = (SmsTracker)mSTrackers.remove(mSTrackers.size() - 1);
365                    sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
366                } catch (CanceledException ex) {
367                    Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
368                }
369                removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
370            }
371            break;
372        }
373    }
374
375    private void createWakelock() {
376        PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
377        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SMSDispatcher");
378        mWakeLock.setReferenceCounted(true);
379    }
380
381    /**
382     * Grabs a wake lock and sends intent as an ordered broadcast.
383     * The resultReceiver will check for errors and ACK/NACK back
384     * to the RIL.
385     *
386     * @param intent intent to broadcast
387     * @param permission Receivers are required to have this permission
388     */
389    void dispatch(Intent intent, String permission) {
390        // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
391        // receivers time to take their own wake locks.
392        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
393        mContext.sendOrderedBroadcast(intent, permission, mResultReceiver,
394                this, Activity.RESULT_OK, null, null);
395    }
396
397    /**
398     * Called when SIM_FULL message is received from the RIL.  Notifies interested
399     * parties that SIM storage for SMS messages is full.
400     */
401    private void handleIccFull(){
402        // broadcast SIM_FULL intent
403        Intent intent = new Intent(Intents.SIM_FULL_ACTION);
404        mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
405        mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
406    }
407
408    /**
409     * Called when a status report is received.  This should correspond to
410     * a previously successful SEND.
411     *
412     * @param ar AsyncResult passed into the message handler.  ar.result should
413     *           be a String representing the status report PDU, as ASCII hex.
414     */
415    protected abstract void handleStatusReport(AsyncResult ar);
416
417    /**
418     * Called when SMS send completes. Broadcasts a sentIntent on success.
419     * On failure, either sets up retries or broadcasts a sentIntent with
420     * the failure in the result code.
421     *
422     * @param ar AsyncResult passed into the message handler.  ar.result should
423     *           an SmsResponse instance if send was successful.  ar.userObj
424     *           should be an SmsTracker instance.
425     */
426    protected void handleSendComplete(AsyncResult ar) {
427        SmsTracker tracker = (SmsTracker) ar.userObj;
428        PendingIntent sentIntent = tracker.mSentIntent;
429
430        if (ar.exception == null) {
431            if (Config.LOGD) {
432                Log.d(TAG, "SMS send complete. Broadcasting "
433                        + "intent: " + sentIntent);
434            }
435
436            if (tracker.mDeliveryIntent != null) {
437                // Expecting a status report.  Add it to the list.
438                int messageRef = ((SmsResponse)ar.result).messageRef;
439                tracker.mMessageRef = messageRef;
440                deliveryPendingList.add(tracker);
441            }
442
443            if (sentIntent != null) {
444                try {
445                    sentIntent.send(Activity.RESULT_OK);
446                } catch (CanceledException ex) {}
447            }
448        } else {
449            if (Config.LOGD) {
450                Log.d(TAG, "SMS send failed");
451            }
452
453            int ss = mPhone.getServiceState().getState();
454
455            if (ss != ServiceState.STATE_IN_SERVICE) {
456                handleNotInService(ss, tracker);
457            } else if ((((CommandException)(ar.exception)).getCommandError()
458                    == CommandException.Error.SMS_FAIL_RETRY) &&
459                   tracker.mRetryCount < MAX_SEND_RETRIES) {
460                // Retry after a delay if needed.
461                // TODO: According to TS 23.040, 9.2.3.6, we should resend
462                //       with the same TP-MR as the failed message, and
463                //       TP-RD set to 1.  However, we don't have a means of
464                //       knowing the MR for the failed message (EF_SMSstatus
465                //       may or may not have the MR corresponding to this
466                //       message, depending on the failure).  Also, in some
467                //       implementations this retry is handled by the baseband.
468                tracker.mRetryCount++;
469                Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
470                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
471            } else if (tracker.mSentIntent != null) {
472                // Done retrying; return an error to the app.
473                try {
474                    Intent fillIn = new Intent();
475                    if (ar.result != null) {
476                        fillIn.putExtra("errorCode", ((SmsResponse)ar.result).errorCode);
477                    }
478                    tracker.mSentIntent.send(mContext, RESULT_ERROR_GENERIC_FAILURE, fillIn);
479                } catch (CanceledException ex) {}
480            }
481        }
482    }
483
484    /**
485     * Handles outbound message when the phone is not in service.
486     *
487     * @param ss     Current service state.  Valid values are:
488     *                  OUT_OF_SERVICE
489     *                  EMERGENCY_ONLY
490     *                  POWER_OFF
491     * @param tracker   An SmsTracker for the current message.
492     */
493    protected void handleNotInService(int ss, SmsTracker tracker) {
494        if (tracker.mSentIntent != null) {
495            try {
496                if (ss == ServiceState.STATE_POWER_OFF) {
497                    tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF);
498                } else {
499                    tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE);
500                }
501            } catch (CanceledException ex) {}
502        }
503    }
504
505    /**
506     * Dispatches an incoming SMS messages.
507     *
508     * @param sms the incoming message from the phone
509     * @return a result code from {@link Telephony.Sms.Intents}, or
510     *         {@link Activity#RESULT_OK} if the message has been broadcast
511     *         to applications
512     */
513    protected abstract int dispatchMessage(SmsMessageBase sms);
514
515
516    /**
517     * If this is the last part send the parts out to the application, otherwise
518     * the part is stored for later processing.
519     *
520     * NOTE: concatRef (naturally) needs to be non-null, but portAddrs can be null.
521     * @return a result code from {@link Telephony.Sms.Intents}, or
522     *         {@link Activity#RESULT_OK} if the message has been broadcast
523     *         to applications
524     */
525    protected int processMessagePart(SmsMessageBase sms,
526            SmsHeader.ConcatRef concatRef, SmsHeader.PortAddrs portAddrs) {
527
528        // Lookup all other related parts
529        StringBuilder where = new StringBuilder("reference_number =");
530        where.append(concatRef.refNumber);
531        where.append(" AND address = ?");
532        String[] whereArgs = new String[] {sms.getOriginatingAddress()};
533
534        byte[][] pdus = null;
535        Cursor cursor = null;
536        try {
537            cursor = mResolver.query(mRawUri, RAW_PROJECTION, where.toString(), whereArgs, null);
538            int cursorCount = cursor.getCount();
539            if (cursorCount != concatRef.msgCount - 1) {
540                // We don't have all the parts yet, store this one away
541                ContentValues values = new ContentValues();
542                values.put("date", new Long(sms.getTimestampMillis()));
543                values.put("pdu", HexDump.toHexString(sms.getPdu()));
544                values.put("address", sms.getOriginatingAddress());
545                values.put("reference_number", concatRef.refNumber);
546                values.put("count", concatRef.msgCount);
547                values.put("sequence", concatRef.seqNumber);
548                if (portAddrs != null) {
549                    values.put("destination_port", portAddrs.destPort);
550                }
551                mResolver.insert(mRawUri, values);
552                return Intents.RESULT_SMS_HANDLED;
553            }
554
555            // All the parts are in place, deal with them
556            int pduColumn = cursor.getColumnIndex("pdu");
557            int sequenceColumn = cursor.getColumnIndex("sequence");
558
559            pdus = new byte[concatRef.msgCount][];
560            for (int i = 0; i < cursorCount; i++) {
561                cursor.moveToNext();
562                int cursorSequence = (int)cursor.getLong(sequenceColumn);
563                pdus[cursorSequence - 1] = HexDump.hexStringToByteArray(
564                        cursor.getString(pduColumn));
565            }
566            // This one isn't in the DB, so add it
567            pdus[concatRef.seqNumber - 1] = sms.getPdu();
568
569            // Remove the parts from the database
570            mResolver.delete(mRawUri, where.toString(), whereArgs);
571        } catch (SQLException e) {
572            Log.e(TAG, "Can't access multipart SMS database", e);
573            // TODO:  Would OUT_OF_MEMORY be more appropriate?
574            return Intents.RESULT_SMS_GENERIC_ERROR;
575        } finally {
576            if (cursor != null) cursor.close();
577        }
578
579        /**
580         * TODO(cleanup): The following code has duplicated logic with
581         * the radio-specific dispatchMessage code, which is fragile,
582         * in addition to being redundant.  Instead, if this method
583         * maybe returned the reassembled message (or just contents),
584         * the following code (which is not really related to
585         * reconstruction) could be better consolidated.
586         */
587
588        // Dispatch the PDUs to applications
589        if (portAddrs != null) {
590            if (portAddrs.destPort == SmsHeader.PORT_WAP_PUSH) {
591                // Build up the data stream
592                ByteArrayOutputStream output = new ByteArrayOutputStream();
593                for (int i = 0; i < concatRef.msgCount; i++) {
594                    SmsMessage msg = SmsMessage.createFromPdu(pdus[i]);
595                    byte[] data = msg.getUserData();
596                    output.write(data, 0, data.length);
597                }
598                // Handle the PUSH
599                return mWapPush.dispatchWapPdu(output.toByteArray());
600            } else {
601                // The messages were sent to a port, so concoct a URI for it
602                dispatchPortAddressedPdus(pdus, portAddrs.destPort);
603            }
604        } else {
605            // The messages were not sent to a port
606            dispatchPdus(pdus);
607        }
608        return Activity.RESULT_OK;
609    }
610
611    /**
612     * Dispatches standard PDUs to interested applications
613     *
614     * @param pdus The raw PDUs making up the message
615     */
616    protected void dispatchPdus(byte[][] pdus) {
617        Intent intent = new Intent(Intents.SMS_RECEIVED_ACTION);
618        intent.putExtra("pdus", pdus);
619        dispatch(intent, "android.permission.RECEIVE_SMS");
620    }
621
622    /**
623     * Dispatches port addressed PDUs to interested applications
624     *
625     * @param pdus The raw PDUs making up the message
626     * @param port The destination port of the messages
627     */
628    protected void dispatchPortAddressedPdus(byte[][] pdus, int port) {
629        Uri uri = Uri.parse("sms://localhost:" + port);
630        Intent intent = new Intent(Intents.DATA_SMS_RECEIVED_ACTION, uri);
631        intent.putExtra("pdus", pdus);
632        dispatch(intent, "android.permission.RECEIVE_SMS");
633    }
634
635    /**
636     * Send a data based SMS to a specific application port.
637     *
638     * @param destAddr the address to send the message to
639     * @param scAddr is the service center address or null to use
640     *  the current default SMSC
641     * @param destPort the port to deliver the message to
642     * @param data the body of the message to send
643     * @param sentIntent if not NULL this <code>PendingIntent</code> is
644     *  broadcast when the message is sucessfully sent, or failed.
645     *  The result code will be <code>Activity.RESULT_OK<code> for success,
646     *  or one of these errors:<br>
647     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
648     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
649     *  <code>RESULT_ERROR_NULL_PDU</code><br>
650     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
651     *  the extra "errorCode" containing a radio technology specific value,
652     *  generally only useful for troubleshooting.<br>
653     *  The per-application based SMS control checks sentIntent. If sentIntent
654     *  is NULL the caller will be checked against all unknown applicaitons,
655     *  which cause smaller number of SMS to be sent in checking period.
656     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
657     *  broadcast when the message is delivered to the recipient.  The
658     *  raw pdu of the status report is in the extended data ("pdu").
659     */
660    protected abstract void sendData(String destAddr, String scAddr, int destPort,
661            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent);
662
663    /**
664     * Send a text based SMS.
665     *
666     * @param destAddr the address to send the message to
667     * @param scAddr is the service center address or null to use
668     *  the current default SMSC
669     * @param text the body of the message to send
670     * @param sentIntent if not NULL this <code>PendingIntent</code> is
671     *  broadcast when the message is sucessfully sent, or failed.
672     *  The result code will be <code>Activity.RESULT_OK<code> for success,
673     *  or one of these errors:<br>
674     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
675     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
676     *  <code>RESULT_ERROR_NULL_PDU</code><br>
677     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
678     *  the extra "errorCode" containing a radio technology specific value,
679     *  generally only useful for troubleshooting.<br>
680     *  The per-application based SMS control checks sentIntent. If sentIntent
681     *  is NULL the caller will be checked against all unknown applications,
682     *  which cause smaller number of SMS to be sent in checking period.
683     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
684     *  broadcast when the message is delivered to the recipient.  The
685     *  raw pdu of the status report is in the extended data ("pdu").
686     */
687    protected abstract void sendText(String destAddr, String scAddr,
688            String text, PendingIntent sentIntent, PendingIntent deliveryIntent);
689
690    /**
691     * Send a multi-part text based SMS.
692     *
693     * @param destAddr the address to send the message to
694     * @param scAddr is the service center address or null to use
695     *   the current default SMSC
696     * @param parts an <code>ArrayList</code> of strings that, in order,
697     *   comprise the original message
698     * @param sentIntents if not null, an <code>ArrayList</code> of
699     *   <code>PendingIntent</code>s (one for each message part) that is
700     *   broadcast when the corresponding message part has been sent.
701     *   The result code will be <code>Activity.RESULT_OK<code> for success,
702     *   or one of these errors:
703     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
704     *   <code>RESULT_ERROR_RADIO_OFF</code>
705     *   <code>RESULT_ERROR_NULL_PDU</code>.
706     *  The per-application based SMS control checks sentIntent. If sentIntent
707     *  is NULL the caller will be checked against all unknown applicaitons,
708     *  which cause smaller number of SMS to be sent in checking period.
709     * @param deliveryIntents if not null, an <code>ArrayList</code> of
710     *   <code>PendingIntent</code>s (one for each message part) that is
711     *   broadcast when the corresponding message part has been delivered
712     *   to the recipient.  The raw pdu of the status report is in the
713     *   extended data ("pdu").
714     */
715    protected abstract void sendMultipartText(String destAddr, String scAddr,
716            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
717            ArrayList<PendingIntent> deliveryIntents);
718
719    /**
720     * Send a SMS
721     *
722     * @param smsc the SMSC to send the message through, or NULL for the
723     *  defatult SMSC
724     * @param pdu the raw PDU to send
725     * @param sentIntent if not NULL this <code>Intent</code> is
726     *  broadcast when the message is sucessfully sent, or failed.
727     *  The result code will be <code>Activity.RESULT_OK<code> for success,
728     *  or one of these errors:
729     *  <code>RESULT_ERROR_GENERIC_FAILURE</code>
730     *  <code>RESULT_ERROR_RADIO_OFF</code>
731     *  <code>RESULT_ERROR_NULL_PDU</code>.
732     *  The per-application based SMS control checks sentIntent. If sentIntent
733     *  is NULL the caller will be checked against all unknown applicaitons,
734     *  which cause smaller number of SMS to be sent in checking period.
735     * @param deliveryIntent if not NULL this <code>Intent</code> is
736     *  broadcast when the message is delivered to the recipient.  The
737     *  raw pdu of the status report is in the extended data ("pdu").
738     */
739    protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
740            PendingIntent deliveryIntent) {
741        if (pdu == null) {
742            if (sentIntent != null) {
743                try {
744                    sentIntent.send(RESULT_ERROR_NULL_PDU);
745                } catch (CanceledException ex) {}
746            }
747            return;
748        }
749
750        HashMap<String, Object> map = new HashMap<String, Object>();
751        map.put("smsc", smsc);
752        map.put("pdu", pdu);
753
754        SmsTracker tracker = new SmsTracker(map, sentIntent,
755                deliveryIntent);
756        int ss = mPhone.getServiceState().getState();
757
758        if (ss != ServiceState.STATE_IN_SERVICE) {
759            handleNotInService(ss, tracker);
760        } else {
761            String appName = getAppNameByIntent(sentIntent);
762            if (mCounter.check(appName, SINGLE_PART_SMS)) {
763                sendSms(tracker);
764            } else {
765                sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
766            }
767        }
768    }
769
770    /**
771     * Post an alert while SMS needs user confirm.
772     *
773     * An SmsTracker for the current message.
774     */
775    protected void handleReachSentLimit(SmsTracker tracker) {
776        if (mSTrackers.size() >= MO_MSG_QUEUE_LIMIT) {
777            // Deny the sending when the queue limit is reached.
778            try {
779                tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
780            } catch (CanceledException ex) {
781                Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
782            }
783            return;
784        }
785
786        Resources r = Resources.getSystem();
787
788        String appName = getAppNameByIntent(tracker.mSentIntent);
789
790        AlertDialog d = new AlertDialog.Builder(mContext)
791                .setTitle(r.getString(R.string.sms_control_title))
792                .setMessage(appName + " " + r.getString(R.string.sms_control_message))
793                .setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
794                .setNegativeButton(r.getString(R.string.sms_control_no), mListener)
795                .create();
796
797        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
798        d.show();
799
800        mSTrackers.add(tracker);
801        sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
802                DEFAULT_SMS_TIMOUEOUT);
803    }
804
805    protected String getAppNameByIntent(PendingIntent intent) {
806        Resources r = Resources.getSystem();
807        return (intent != null) ? intent.getTargetPackage()
808            : r.getString(R.string.sms_control_default_app_name);
809    }
810
811    /**
812     * Send the message along to the radio.
813     *
814     * @param tracker holds the SMS message to send
815     */
816    protected abstract void sendSms(SmsTracker tracker);
817
818    /**
819     * Send the multi-part SMS based on multipart Sms tracker
820     *
821     * @param tracker holds the multipart Sms tracker ready to be sent
822     */
823    protected abstract void sendMultipartSms (SmsTracker tracker);
824
825    /**
826     * Activate or deactivate cell broadcast SMS.
827     *
828     * @param activate
829     *            0 = activate, 1 = deactivate
830     * @param response
831     *            Callback message is empty on completion
832     */
833    protected abstract void activateCellBroadcastSms(int activate, Message response);
834
835    /**
836     * Query the current configuration of cell broadcast SMS.
837     *
838     * @param response
839     *            Callback message contains the configuration from the modem on completion
840     *            @see #setCellBroadcastConfig
841     */
842    protected abstract void getCellBroadcastSmsConfig(Message response);
843
844    /**
845     * Configure cell broadcast SMS.
846     *
847     * @param configValuesArray
848     *          The first element defines the number of triples that follow.
849     *          A triple is made up of the service category, the language identifier
850     *          and a boolean that specifies whether the category is set active.
851     * @param response
852     *            Callback message is empty on completion
853     */
854    protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response);
855
856    /**
857     * Send an acknowledge message.
858     * @param success indicates that last message was successfully received.
859     * @param result result code indicating any error
860     * @param response callback message sent when operation completes.
861     */
862    protected abstract void acknowledgeLastIncomingSms(boolean success,
863            int result, Message response);
864
865    /**
866     * Notify interested apps if the framework has rejected an incoming SMS,
867     * and send an acknowledge message to the network.
868     * @param success indicates that last message was successfully received.
869     * @param result result code indicating any error
870     * @param response callback message sent when operation completes.
871     */
872    private void notifyAndAcknowledgeLastIncomingSms(boolean success,
873            int result, Message response) {
874        if (!success) {
875            // broadcast SMS_REJECTED_ACTION intent
876            Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
877            intent.putExtra("result", result);
878            mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
879            mContext.sendBroadcast(intent, "android.permission.RECEIVE_SMS");
880        }
881        acknowledgeLastIncomingSms(success, result, response);
882    }
883
884    /**
885     * Check if a SmsTracker holds multi-part Sms
886     *
887     * @param tracker a SmsTracker could hold a multi-part Sms
888     * @return true for tracker holds Multi-parts Sms
889     */
890    private boolean isMultipartTracker (SmsTracker tracker) {
891        HashMap map = tracker.mData;
892        return ( map.get("parts") != null);
893    }
894
895    /**
896     * Keeps track of an SMS that has been sent to the RIL, until it it has
897     * successfully been sent, or we're done trying.
898     *
899     */
900    static protected class SmsTracker {
901        // fields need to be public for derived SmsDispatchers
902        public HashMap mData;
903        public int mRetryCount;
904        public int mMessageRef;
905
906        public PendingIntent mSentIntent;
907        public PendingIntent mDeliveryIntent;
908
909        SmsTracker(HashMap data, PendingIntent sentIntent,
910                PendingIntent deliveryIntent) {
911            mData = data;
912            mSentIntent = sentIntent;
913            mDeliveryIntent = deliveryIntent;
914            mRetryCount = 0;
915        }
916    }
917
918    protected SmsTracker SmsTrackerFactory(HashMap data, PendingIntent sentIntent,
919            PendingIntent deliveryIntent) {
920        return new SmsTracker(data, sentIntent, deliveryIntent);
921    }
922
923    private DialogInterface.OnClickListener mListener =
924        new DialogInterface.OnClickListener() {
925
926            public void onClick(DialogInterface dialog, int which) {
927                if (which == DialogInterface.BUTTON_POSITIVE) {
928                    Log.d(TAG, "click YES to send out sms");
929                    sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
930                } else if (which == DialogInterface.BUTTON_NEGATIVE) {
931                    Log.d(TAG, "click NO to stop sending");
932                    sendMessage(obtainMessage(EVENT_STOP_SENDING));
933                }
934            }
935        };
936
937        private BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
938            @Override
939            public void onReceive(Context context, Intent intent) {
940                if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_LOW)) {
941                    mStorageAvailable = false;
942                    mCm.reportSmsMemoryStatus(false, null);
943                } else if (intent.getAction().equals(Intent.ACTION_DEVICE_STORAGE_OK)) {
944                    mStorageAvailable = true;
945                    mCm.reportSmsMemoryStatus(true, null);
946                } else {
947                    // Assume the intent is one of the SMS receive intents that
948                    // was sent as an ordered broadcast.  Check result and ACK.
949                    int rc = getResultCode();
950                    boolean success = (rc == Activity.RESULT_OK)
951                                        || (rc == Intents.RESULT_SMS_HANDLED);
952
953                    // For a multi-part message, this only ACKs the last part.
954                    // Previous parts were ACK'd as they were received.
955                    acknowledgeLastIncomingSms(success, rc, null);
956                }
957            }
958
959        };
960}
961