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