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