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 static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PERIOD_NOT_SPECIFIED;
20import static com.android.internal.telephony.IccSmsInterfaceManager.SMS_MESSAGE_PRIORITY_NOT_SPECIFIED;
21
22import android.app.Activity;
23import android.app.PendingIntent;
24import android.app.PendingIntent.CanceledException;
25import android.content.Context;
26import android.content.Intent;
27import android.net.Uri;
28import android.os.AsyncResult;
29import android.os.Handler;
30import android.os.Message;
31import android.provider.Telephony.Sms;
32import android.provider.Telephony.Sms.Intents;
33import android.telephony.Rlog;
34import android.telephony.SmsManager;
35import android.telephony.SmsMessage;
36import android.util.Pair;
37
38import com.android.internal.annotations.VisibleForTesting;
39import com.android.internal.telephony.cdma.CdmaInboundSmsHandler;
40import com.android.internal.telephony.cdma.CdmaSMSDispatcher;
41import com.android.internal.telephony.gsm.GsmInboundSmsHandler;
42import com.android.internal.telephony.gsm.GsmSMSDispatcher;
43
44import java.util.ArrayList;
45import java.util.HashMap;
46
47/**
48 *
49 */
50public class SmsDispatchersController extends Handler {
51    private static final String TAG = "SmsDispatchersController";
52
53    /** Radio is ON */
54    private static final int EVENT_RADIO_ON = 11;
55
56    /** IMS registration/SMS format changed */
57    private static final int EVENT_IMS_STATE_CHANGED = 12;
58
59    /** Callback from RIL_REQUEST_IMS_REGISTRATION_STATE */
60    private static final int EVENT_IMS_STATE_DONE = 13;
61
62    private SMSDispatcher mCdmaDispatcher;
63    private SMSDispatcher mGsmDispatcher;
64    private ImsSmsDispatcher mImsSmsDispatcher;
65
66    private GsmInboundSmsHandler mGsmInboundSmsHandler;
67    private CdmaInboundSmsHandler mCdmaInboundSmsHandler;
68
69    private Phone mPhone;
70    /** Outgoing message counter. Shared by all dispatchers. */
71    private final SmsUsageMonitor mUsageMonitor;
72    private final CommandsInterface mCi;
73    private final Context mContext;
74
75    /** true if IMS is registered and sms is supported, false otherwise.*/
76    private boolean mIms = false;
77    private String mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN;
78
79    public SmsDispatchersController(Phone phone, SmsStorageMonitor storageMonitor,
80            SmsUsageMonitor usageMonitor) {
81        Rlog.d(TAG, "SmsDispatchersController created");
82
83        mContext = phone.getContext();
84        mUsageMonitor = usageMonitor;
85        mCi = phone.mCi;
86        mPhone = phone;
87
88        // Create dispatchers, inbound SMS handlers and
89        // broadcast undelivered messages in raw table.
90        mImsSmsDispatcher = new ImsSmsDispatcher(phone, this);
91        mCdmaDispatcher = new CdmaSMSDispatcher(phone, this);
92        mGsmInboundSmsHandler = GsmInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
93                storageMonitor, phone);
94        mCdmaInboundSmsHandler = CdmaInboundSmsHandler.makeInboundSmsHandler(phone.getContext(),
95                storageMonitor, phone, (CdmaSMSDispatcher) mCdmaDispatcher);
96        mGsmDispatcher = new GsmSMSDispatcher(phone, this, mGsmInboundSmsHandler);
97        SmsBroadcastUndelivered.initialize(phone.getContext(),
98                mGsmInboundSmsHandler, mCdmaInboundSmsHandler);
99        InboundSmsHandler.registerNewMessageNotificationActionHandler(phone.getContext());
100
101        mCi.registerForOn(this, EVENT_RADIO_ON, null);
102        mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
103    }
104
105    /* Updates the phone object when there is a change */
106    protected void updatePhoneObject(Phone phone) {
107        Rlog.d(TAG, "In IMS updatePhoneObject ");
108        mCdmaDispatcher.updatePhoneObject(phone);
109        mGsmDispatcher.updatePhoneObject(phone);
110        mGsmInboundSmsHandler.updatePhoneObject(phone);
111        mCdmaInboundSmsHandler.updatePhoneObject(phone);
112    }
113
114    public void dispose() {
115        mCi.unregisterForOn(this);
116        mCi.unregisterForImsNetworkStateChanged(this);
117        mGsmDispatcher.dispose();
118        mCdmaDispatcher.dispose();
119        mGsmInboundSmsHandler.dispose();
120        mCdmaInboundSmsHandler.dispose();
121    }
122
123    /**
124     * Handles events coming from the phone stack. Overridden from handler.
125     *
126     * @param msg the message to handle
127     */
128    @Override
129    public void handleMessage(Message msg) {
130        AsyncResult ar;
131
132        switch (msg.what) {
133            case EVENT_RADIO_ON:
134            case EVENT_IMS_STATE_CHANGED: // received unsol
135                mCi.getImsRegistrationState(this.obtainMessage(EVENT_IMS_STATE_DONE));
136                break;
137
138            case EVENT_IMS_STATE_DONE:
139                ar = (AsyncResult) msg.obj;
140
141                if (ar.exception == null) {
142                    updateImsInfo(ar);
143                } else {
144                    Rlog.e(TAG, "IMS State query failed with exp "
145                            + ar.exception);
146                }
147                break;
148
149            default:
150                if (isCdmaMo()) {
151                    mCdmaDispatcher.handleMessage(msg);
152                } else {
153                    mGsmDispatcher.handleMessage(msg);
154                }
155        }
156    }
157
158    private void setImsSmsFormat(int format) {
159        switch (format) {
160            case PhoneConstants.PHONE_TYPE_GSM:
161                mImsSmsFormat = SmsConstants.FORMAT_3GPP;
162                break;
163            case PhoneConstants.PHONE_TYPE_CDMA:
164                mImsSmsFormat = SmsConstants.FORMAT_3GPP2;
165                break;
166            default:
167                mImsSmsFormat = SmsConstants.FORMAT_UNKNOWN;
168                break;
169        }
170    }
171
172    private void updateImsInfo(AsyncResult ar) {
173        int[] responseArray = (int[]) ar.result;
174        setImsSmsFormat(responseArray[1]);
175        mIms = responseArray[0] == 1 && !SmsConstants.FORMAT_UNKNOWN.equals(mImsSmsFormat);
176        Rlog.d(TAG, "IMS registration state: " + mIms + " format: " + mImsSmsFormat);
177    }
178
179    /**
180     * Inject an SMS PDU into the android platform only if it is class 1.
181     *
182     * @param pdu is the byte array of pdu to be injected into android telephony layer
183     * @param format is the format of SMS pdu (3gpp or 3gpp2)
184     * @param callback if not NULL this callback is triggered when the message is successfully
185     *                 received by the android telephony layer. This callback is triggered at
186     *                 the same time an SMS received from radio is responded back.
187     */
188    @VisibleForTesting
189    public void injectSmsPdu(byte[] pdu, String format, SmsInjectionCallback callback) {
190        // TODO We need to decide whether we should allow injecting GSM(3gpp)
191        // SMS pdus when the phone is camping on CDMA(3gpp2) network and vice versa.
192        android.telephony.SmsMessage msg =
193                android.telephony.SmsMessage.createFromPdu(pdu, format);
194        injectSmsPdu(msg, format, callback, false /* ignoreClass */);
195    }
196
197    /**
198     * Inject an SMS PDU into the android platform.
199     *
200     * @param msg is the {@link SmsMessage} to be injected into android telephony layer
201     * @param format is the format of SMS pdu (3gpp or 3gpp2)
202     * @param callback if not NULL this callback is triggered when the message is successfully
203     *                 received by the android telephony layer. This callback is triggered at
204     *                 the same time an SMS received from radio is responded back.
205     * @param ignoreClass if set to false, this method will inject class 1 sms only.
206     */
207    @VisibleForTesting
208    public void injectSmsPdu(SmsMessage msg, String format, SmsInjectionCallback callback,
209            boolean ignoreClass) {
210        Rlog.d(TAG, "SmsDispatchersController:injectSmsPdu");
211        try {
212            if (msg == null) {
213                Rlog.e(TAG, "injectSmsPdu: createFromPdu returned null");
214                callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR);
215                return;
216            }
217
218            if (!ignoreClass
219                    && msg.getMessageClass() != android.telephony.SmsMessage.MessageClass.CLASS_1) {
220                Rlog.e(TAG, "injectSmsPdu: not class 1");
221                callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR);
222                return;
223            }
224
225            AsyncResult ar = new AsyncResult(callback, msg, null);
226
227            if (format.equals(SmsConstants.FORMAT_3GPP)) {
228                Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg
229                        + ", format=" + format + "to mGsmInboundSmsHandler");
230                mGsmInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar);
231            } else if (format.equals(SmsConstants.FORMAT_3GPP2)) {
232                Rlog.i(TAG, "SmsDispatchersController:injectSmsText Sending msg=" + msg
233                        + ", format=" + format + "to mCdmaInboundSmsHandler");
234                mCdmaInboundSmsHandler.sendMessage(InboundSmsHandler.EVENT_INJECT_SMS, ar);
235            } else {
236                // Invalid pdu format.
237                Rlog.e(TAG, "Invalid pdu format: " + format);
238                callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR);
239            }
240        } catch (Exception e) {
241            Rlog.e(TAG, "injectSmsPdu failed: ", e);
242            callback.onSmsInjectedResult(Intents.RESULT_SMS_GENERIC_ERROR);
243        }
244    }
245
246    /**
247     * Retry the message along to the radio.
248     *
249     * @param tracker holds the SMS message to send
250     */
251    public void sendRetrySms(SMSDispatcher.SmsTracker tracker) {
252        String oldFormat = tracker.mFormat;
253
254        // newFormat will be based on voice technology
255        String newFormat =
256                (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType())
257                        ? mCdmaDispatcher.getFormat() : mGsmDispatcher.getFormat();
258
259        // was previously sent sms format match with voice tech?
260        if (oldFormat.equals(newFormat)) {
261            if (isCdmaFormat(newFormat)) {
262                Rlog.d(TAG, "old format matched new format (cdma)");
263                mCdmaDispatcher.sendSms(tracker);
264                return;
265            } else {
266                Rlog.d(TAG, "old format matched new format (gsm)");
267                mGsmDispatcher.sendSms(tracker);
268                return;
269            }
270        }
271
272        // format didn't match, need to re-encode.
273        HashMap map = tracker.getData();
274
275        // to re-encode, fields needed are:  scAddr, destAddr, and
276        //   text if originally sent as sendText or
277        //   data and destPort if originally sent as sendData.
278        if (!(map.containsKey("scAddr") && map.containsKey("destAddr")
279                && (map.containsKey("text")
280                || (map.containsKey("data") && map.containsKey("destPort"))))) {
281            // should never come here...
282            Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
283            tracker.onFailed(mContext, SmsManager.RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/);
284            return;
285        }
286        String scAddr = (String) map.get("scAddr");
287        String destAddr = (String) map.get("destAddr");
288
289        SmsMessageBase.SubmitPduBase pdu = null;
290        //    figure out from tracker if this was sendText/Data
291        if (map.containsKey("text")) {
292            Rlog.d(TAG, "sms failed was text");
293            String text = (String) map.get("text");
294
295            if (isCdmaFormat(newFormat)) {
296                Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
297                pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
298                        scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
299            } else {
300                Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
301                pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
302                        scAddr, destAddr, text, (tracker.mDeliveryIntent != null), null);
303            }
304        } else if (map.containsKey("data")) {
305            Rlog.d(TAG, "sms failed was data");
306            byte[] data = (byte[]) map.get("data");
307            Integer destPort = (Integer) map.get("destPort");
308
309            if (isCdmaFormat(newFormat)) {
310                Rlog.d(TAG, "old format (gsm) ==> new format (cdma)");
311                pdu = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
312                            scAddr, destAddr, destPort.intValue(), data,
313                            (tracker.mDeliveryIntent != null));
314            } else {
315                Rlog.d(TAG, "old format (cdma) ==> new format (gsm)");
316                pdu = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(
317                            scAddr, destAddr, destPort.intValue(), data,
318                            (tracker.mDeliveryIntent != null));
319            }
320        }
321
322        // replace old smsc and pdu with newly encoded ones
323        map.put("smsc", pdu.encodedScAddress);
324        map.put("pdu", pdu.encodedMessage);
325
326        SMSDispatcher dispatcher = (isCdmaFormat(newFormat)) ? mCdmaDispatcher : mGsmDispatcher;
327
328        tracker.mFormat = dispatcher.getFormat();
329        dispatcher.sendSms(tracker);
330    }
331
332    public boolean isIms() {
333        return mIms;
334    }
335
336    public String getImsSmsFormat() {
337        return mImsSmsFormat;
338    }
339
340    /**
341     * Determines whether or not to use CDMA format for MO SMS.
342     * If SMS over IMS is supported, then format is based on IMS SMS format,
343     * otherwise format is based on current phone type.
344     *
345     * @return true if Cdma format should be used for MO SMS, false otherwise.
346     */
347    protected boolean isCdmaMo() {
348        if (!isIms()) {
349            // IMS is not registered, use Voice technology to determine SMS format.
350            return (PhoneConstants.PHONE_TYPE_CDMA == mPhone.getPhoneType());
351        }
352        // IMS is registered with SMS support
353        return isCdmaFormat(mImsSmsFormat);
354    }
355
356    /**
357     * Determines whether or not format given is CDMA format.
358     *
359     * @param format
360     * @return true if format given is CDMA format, false otherwise.
361     */
362    public boolean isCdmaFormat(String format) {
363        return (mCdmaDispatcher.getFormat().equals(format));
364    }
365
366    /**
367     * Send a data based SMS to a specific application port.
368     *
369     * @param destAddr the address to send the message to
370     * @param scAddr is the service center address or null to use
371     *  the current default SMSC
372     * @param destPort the port to deliver the message to
373     * @param data the body of the message to send
374     * @param sentIntent if not NULL this <code>PendingIntent</code> is
375     *  broadcast when the message is successfully sent, or failed.
376     *  The result code will be <code>Activity.RESULT_OK<code> for success,
377     *  or one of these errors:<br>
378     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
379     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
380     *  <code>RESULT_ERROR_NULL_PDU</code><br>
381     *  <code>RESULT_ERROR_NO_SERVICE</code><br>.
382     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
383     *  the extra "errorCode" containing a radio technology specific value,
384     *  generally only useful for troubleshooting.<br>
385     *  The per-application based SMS control checks sentIntent. If sentIntent
386     *  is NULL the caller will be checked against all unknown applications,
387     *  which cause smaller number of SMS to be sent in checking period.
388     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
389     *  broadcast when the message is delivered to the recipient.  The
390     *  raw pdu of the status report is in the extended data ("pdu").
391     */
392    protected void sendData(String destAddr, String scAddr, int destPort,
393                            byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
394        if (mImsSmsDispatcher.isAvailable()) {
395            mImsSmsDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent,
396                    deliveryIntent);
397        } else if (isCdmaMo()) {
398            mCdmaDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
399        } else {
400            mGsmDispatcher.sendData(destAddr, scAddr, destPort, data, sentIntent, deliveryIntent);
401        }
402    }
403
404    /**
405     * Send a text based SMS.
406     *  @param destAddr the address to send the message to
407     * @param scAddr is the service center address or null to use
408     *  the current default SMSC
409     * @param text the body of the message to send
410     * @param sentIntent if not NULL this <code>PendingIntent</code> is
411     *  broadcast when the message is successfully sent, or failed.
412     *  The result code will be <code>Activity.RESULT_OK<code> for success,
413     *  or one of these errors:<br>
414     *  <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
415     *  <code>RESULT_ERROR_RADIO_OFF</code><br>
416     *  <code>RESULT_ERROR_NULL_PDU</code><br>
417     *  <code>RESULT_ERROR_NO_SERVICE</code><br>.
418     *  For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
419     *  the extra "errorCode" containing a radio technology specific value,
420     *  generally only useful for troubleshooting.<br>
421     *  The per-application based SMS control checks sentIntent. If sentIntent
422     *  is NULL the caller will be checked against all unknown applications,
423     *  which cause smaller number of SMS to be sent in checking period.
424     * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
425     *  broadcast when the message is delivered to the recipient.  The
426     * @param messageUri optional URI of the message if it is already stored in the system
427     * @param callingPkg the calling package name
428     * @param persistMessage whether to save the sent message into SMS DB for a
429     *   non-default SMS app.
430     * @param priority Priority level of the message
431     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
432     *  ---------------------------------
433     *  PRIORITY      | Level of Priority
434     *  ---------------------------------
435     *      '00'      |     Normal
436     *      '01'      |     Interactive
437     *      '10'      |     Urgent
438     *      '11'      |     Emergency
439     *  ----------------------------------
440     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
441     * @param expectMore is a boolean to indicate the sending messages through same link or not.
442     * @param validityPeriod Validity Period of the message in mins.
443     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
444     *  Validity Period(Minimum) -> 5 mins
445     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
446     *  Any Other values included Negative considered as Invalid Validity Period of the message.
447     */
448    public void sendText(String destAddr, String scAddr, String text,
449                            PendingIntent sentIntent, PendingIntent deliveryIntent, Uri messageUri,
450                            String callingPkg, boolean persistMessage, int priority,
451                            boolean expectMore, int validityPeriod) {
452        if (mImsSmsDispatcher.isAvailable()) {
453            mImsSmsDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
454                    messageUri, callingPkg, persistMessage, SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
455                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
456        } else {
457            if (isCdmaMo()) {
458                mCdmaDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
459                        messageUri, callingPkg, persistMessage, priority, expectMore,
460                        validityPeriod);
461            } else {
462                mGsmDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent,
463                        messageUri, callingPkg, persistMessage, priority, expectMore,
464                        validityPeriod);
465            }
466        }
467    }
468
469    /**
470     * Send a multi-part text based SMS.
471     *  @param destAddr the address to send the message to
472     * @param scAddr is the service center address or null to use
473     *   the current default SMSC
474     * @param parts an <code>ArrayList</code> of strings that, in order,
475     *   comprise the original message
476     * @param sentIntents if not null, an <code>ArrayList</code> of
477     *   <code>PendingIntent</code>s (one for each message part) that is
478     *   broadcast when the corresponding message part has been sent.
479     *   The result code will be <code>Activity.RESULT_OK<code> for success,
480     *   or one of these errors:
481     *   <code>RESULT_ERROR_GENERIC_FAILURE</code>
482     *   <code>RESULT_ERROR_RADIO_OFF</code>
483     *   <code>RESULT_ERROR_NULL_PDU</code>
484     *   <code>RESULT_ERROR_NO_SERVICE</code>.
485     *  The per-application based SMS control checks sentIntent. If sentIntent
486     *  is NULL the caller will be checked against all unknown applications,
487     *  which cause smaller number of SMS to be sent in checking period.
488     * @param deliveryIntents if not null, an <code>ArrayList</code> of
489     *   <code>PendingIntent</code>s (one for each message part) that is
490     *   broadcast when the corresponding message part has been delivered
491     *   to the recipient.  The raw pdu of the status report is in the
492     * @param messageUri optional URI of the message if it is already stored in the system
493     * @param callingPkg the calling package name
494     * @param persistMessage whether to save the sent message into SMS DB for a
495     *   non-default SMS app.
496     * @param priority Priority level of the message
497     *  Refer specification See 3GPP2 C.S0015-B, v2.0, table 4.5.9-1
498     *  ---------------------------------
499     *  PRIORITY      | Level of Priority
500     *  ---------------------------------
501     *      '00'      |     Normal
502     *      '01'      |     Interactive
503     *      '10'      |     Urgent
504     *      '11'      |     Emergency
505     *  ----------------------------------
506     *  Any Other values included Negative considered as Invalid Priority Indicator of the message.
507     * @param expectMore is a boolean to indicate the sending messages through same link or not.
508     * @param validityPeriod Validity Period of the message in mins.
509     *  Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1.
510     *  Validity Period(Minimum) -> 5 mins
511     *  Validity Period(Maximum) -> 635040 mins(i.e.63 weeks).
512     *  Any Other values included Negative considered as Invalid Validity Period of the message.
513
514     */
515    protected void sendMultipartText(String destAddr, String scAddr,
516            ArrayList<String> parts, ArrayList<PendingIntent> sentIntents,
517            ArrayList<PendingIntent> deliveryIntents, Uri messageUri, String callingPkg,
518            boolean persistMessage, int priority, boolean expectMore, int validityPeriod) {
519        if (mImsSmsDispatcher.isAvailable()) {
520            mImsSmsDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
521                    deliveryIntents, messageUri, callingPkg, persistMessage,
522                    SMS_MESSAGE_PRIORITY_NOT_SPECIFIED,
523                    false /*expectMore*/, SMS_MESSAGE_PERIOD_NOT_SPECIFIED);
524        } else {
525            if (isCdmaMo()) {
526                mCdmaDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
527                        deliveryIntents, messageUri, callingPkg, persistMessage, priority,
528                        expectMore, validityPeriod);
529            } else {
530                mGsmDispatcher.sendMultipartText(destAddr, scAddr, parts, sentIntents,
531                        deliveryIntents, messageUri, callingPkg, persistMessage, priority,
532                        expectMore, validityPeriod);
533            }
534        }
535    }
536
537    /**
538     * Returns the premium SMS permission for the specified package. If the package has never
539     * been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}
540     * will be returned.
541     * @param packageName the name of the package to query permission
542     * @return one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN},
543     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER},
544     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or
545     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW}
546     */
547    public int getPremiumSmsPermission(String packageName) {
548        return mUsageMonitor.getPremiumSmsPermission(packageName);
549    }
550
551    /**
552     * Sets the premium SMS permission for the specified package and save the value asynchronously
553     * to persistent storage.
554     * @param packageName the name of the package to set permission
555     * @param permission one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER},
556     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or
557     *  {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW}
558     */
559    public void setPremiumSmsPermission(String packageName, int permission) {
560        mUsageMonitor.setPremiumSmsPermission(packageName, permission);
561    }
562
563    public SmsUsageMonitor getUsageMonitor() {
564        return mUsageMonitor;
565    }
566
567    /**
568     * Triggers the correct method for handling the sms status report based on the format.
569     *
570     * @param tracker the sms tracker.
571     * @param format the format.
572     * @param pdu the pdu of the report.
573     * @return a Pair in which the first boolean is whether the report was handled successfully
574     *          or not and the second boolean is whether processing the sms is complete and the
575     *          tracker no longer need to be kept track of, false if we should expect more callbacks
576     *          and the tracker should be kept.
577     */
578    public Pair<Boolean, Boolean> handleSmsStatusReport(SMSDispatcher.SmsTracker tracker,
579            String format, byte[] pdu) {
580        if (isCdmaFormat(format)) {
581            return handleCdmaStatusReport(tracker, format, pdu);
582        } else {
583            return handleGsmStatusReport(tracker, format, pdu);
584        }
585    }
586
587    private Pair<Boolean, Boolean> handleCdmaStatusReport(SMSDispatcher.SmsTracker tracker,
588            String format, byte[] pdu) {
589        tracker.updateSentMessageStatus(mContext, Sms.STATUS_COMPLETE);
590        boolean success = triggerDeliveryIntent(tracker, format, pdu);
591        return new Pair(success, true /* complete */);
592    }
593
594    private Pair<Boolean, Boolean> handleGsmStatusReport(SMSDispatcher.SmsTracker tracker,
595            String format, byte[] pdu) {
596        com.android.internal.telephony.gsm.SmsMessage sms =
597                com.android.internal.telephony.gsm.SmsMessage.newFromCDS(pdu);
598        boolean complete = false;
599        boolean success = false;
600        if (sms != null) {
601            int tpStatus = sms.getStatus();
602            if(tpStatus >= Sms.STATUS_FAILED || tpStatus < Sms.STATUS_PENDING ) {
603                // Update the message status (COMPLETE or FAILED)
604                tracker.updateSentMessageStatus(mContext, tpStatus);
605                complete = true;
606            }
607            success = triggerDeliveryIntent(tracker, format, pdu);
608        }
609        return new Pair(success, complete);
610    }
611
612    private boolean triggerDeliveryIntent(SMSDispatcher.SmsTracker tracker, String format,
613                                          byte[] pdu) {
614        PendingIntent intent = tracker.mDeliveryIntent;
615        Intent fillIn = new Intent();
616        fillIn.putExtra("pdu", pdu);
617        fillIn.putExtra("format", format);
618        try {
619            intent.send(mContext, Activity.RESULT_OK, fillIn);
620            return true;
621        } catch (CanceledException ex) {
622            return false;
623        }
624    }
625
626
627    public interface SmsInjectionCallback {
628        void onSmsInjectedResult(int result);
629    }
630}
631