FileDocCategorySizeDatePackage
SMSDispatcher.javaAPI DocAndroid 5.1 API75006Thu Mar 12 22:22:54 GMT 2015com.android.internal.telephony

SMSDispatcher

public abstract class SMSDispatcher extends android.os.Handler

Fields Summary
static final String
TAG
static final boolean
DBG
private static final String
SEND_NEXT_MSG_EXTRA
private static final String
SEND_SMS_NO_CONFIRMATION_PERMISSION
Permission required to send SMS to short codes without user confirmation.
private static final int
PREMIUM_RULE_USE_SIM
private static final int
PREMIUM_RULE_USE_NETWORK
private static final int
PREMIUM_RULE_USE_BOTH
private final AtomicInteger
mPremiumSmsRule
private final SettingsObserver
mSettingsObserver
protected static final int
EVENT_SEND_SMS_COMPLETE
SMS send complete.
private static final int
EVENT_SEND_RETRY
Retry sending a previously failed SMS message
private static final int
EVENT_SEND_LIMIT_REACHED_CONFIRMATION
Confirmation required for sending a large number of messages.
static final int
EVENT_SEND_CONFIRMED_SMS
Send the user confirmed SMS
static final int
EVENT_STOP_SENDING
Don't send SMS (user did not confirm).
private static final int
EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE
Confirmation required for third-party apps sending to an SMS short code.
private static final int
EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE
Confirmation required for third-party apps sending to an SMS short code.
protected static final int
EVENT_HANDLE_STATUS_REPORT
Handle status report from {@code CdmaInboundSmsHandler}.
protected static final int
EVENT_RADIO_ON
Radio is ON
protected static final int
EVENT_IMS_STATE_CHANGED
IMS registration/SMS format changed
protected static final int
EVENT_IMS_STATE_DONE
Callback from RIL_REQUEST_IMS_REGISTRATION_STATE
protected static final int
EVENT_NEW_ICC_SMS
protected static final int
EVENT_ICC_CHANGED
protected PhoneBase
mPhone
protected final android.content.Context
mContext
protected final android.content.ContentResolver
mResolver
protected final CommandsInterface
mCi
protected final android.telephony.TelephonyManager
mTelephonyManager
private static final int
MAX_SEND_RETRIES
Maximum number of times to retry sending a failed SMS.
private static final int
SEND_RETRY_DELAY
Delay before next send attempt on a failed SMS, in milliseconds.
private static final int
SINGLE_PART_SMS
single part SMS
private static final int
MO_MSG_QUEUE_LIMIT
Message sending queue limit
private static int
sConcatenatedRef
Message reference for a CONCATENATED_8_BIT_REFERENCE or CONCATENATED_16_BIT_REFERENCE message set. Should be incremented for each set of concatenated messages. Static field shared by all dispatcher objects.
private SmsUsageMonitor
mUsageMonitor
Outgoing message counter. Shared by all dispatchers.
private ImsSMSDispatcher
mImsSMSDispatcher
private int
mPendingTrackerCount
Number of outgoing SmsTrackers waiting for user confirmation.
protected boolean
mSmsCapable
protected boolean
mSmsSendDisabled
protected final ArrayList
deliveryPendingList
Sent messages awaiting a delivery status report.
Constructors Summary
protected SMSDispatcher(PhoneBase phone, SmsUsageMonitor usageMonitor, ImsSMSDispatcher imsSMSDispatcher)
Create a new SMS dispatcher.

param
phone the Phone to use
param
usageMonitor the SmsUsageMonitor to use

        mPhone = phone;
        mImsSMSDispatcher = imsSMSDispatcher;
        mContext = phone.getContext();
        mResolver = mContext.getContentResolver();
        mCi = phone.mCi;
        mUsageMonitor = usageMonitor;
        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
        mSettingsObserver = new SettingsObserver(this, mPremiumSmsRule, mContext);
        mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
                Settings.Global.SMS_SHORT_CODE_RULE), false, mSettingsObserver);

        mSmsCapable = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_sms_capable);
        mSmsSendDisabled = !mTelephonyManager.getSmsSendCapableForPhone(
                mPhone.getPhoneId(), mSmsCapable);
        Rlog.d(TAG, "SMSDispatcher: ctor mSmsCapable=" + mSmsCapable + " format=" + getFormat()
                + " mSmsSendDisabled=" + mSmsSendDisabled);
    
Methods Summary
protected abstract com.android.internal.telephony.GsmAlphabet.TextEncodingDetailscalculateLength(java.lang.CharSequence messageBody, boolean use7bitOnly)
Calculate the number of septets needed to encode the message. This function should only be called for individual segments of multipart message.

param
messageBody the message to encode
param
use7bitOnly ignore (but still count) illegal characters if true
return
TextEncodingDetails

private voidcheckCallerIsPhoneOrCarrierApp()

        int uid = Binder.getCallingUid();
        int appId = UserHandle.getAppId(uid);
        if (appId == Process.PHONE_UID || uid == 0) {
            return;
        }
        try {
            PackageManager pm = mContext.getPackageManager();
            ApplicationInfo ai = pm.getApplicationInfo(getCarrierAppPackageName(), 0);
            if (!UserHandle.isSameApp(ai.uid, Binder.getCallingUid())) {
                throw new SecurityException("Caller is not phone or carrier app!");
            }
        } catch (PackageManager.NameNotFoundException re) {
            throw new SecurityException("Caller is not phone or carrier app!");
        }
    
booleancheckDestination(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Check if destination is a potential premium short code and sender is not pre-approved to send to short codes.

param
tracker the tracker for the SMS to send
return
true if the destination is approved; false if user confirmation event was sent

        if (mContext.checkCallingOrSelfPermission(SEND_SMS_NO_CONFIRMATION_PERMISSION)
                == PackageManager.PERMISSION_GRANTED) {
            return true;            // app is pre-approved to send to short codes
        } else {
            int rule = mPremiumSmsRule.get();
            int smsCategory = SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE;
            if (rule == PREMIUM_RULE_USE_SIM || rule == PREMIUM_RULE_USE_BOTH) {
                String simCountryIso = mTelephonyManager.getSimCountryIso();
                if (simCountryIso == null || simCountryIso.length() != 2) {
                    Rlog.e(TAG, "Can't get SIM country Iso: trying network country Iso");
                    simCountryIso = mTelephonyManager.getNetworkCountryIso();
                }

                smsCategory = mUsageMonitor.checkDestination(tracker.mDestAddress, simCountryIso);
            }
            if (rule == PREMIUM_RULE_USE_NETWORK || rule == PREMIUM_RULE_USE_BOTH) {
                String networkCountryIso = mTelephonyManager.getNetworkCountryIso();
                if (networkCountryIso == null || networkCountryIso.length() != 2) {
                    Rlog.e(TAG, "Can't get Network country Iso: trying SIM country Iso");
                    networkCountryIso = mTelephonyManager.getSimCountryIso();
                }

                smsCategory = SmsUsageMonitor.mergeShortCodeCategories(smsCategory,
                        mUsageMonitor.checkDestination(tracker.mDestAddress, networkCountryIso));
            }

            if (smsCategory == SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE
                    || smsCategory == SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE
                    || smsCategory == SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE) {
                return true;    // not a premium short code
            }

            // Wait for user confirmation unless the user has set permission to always allow/deny
            int premiumSmsPermission = mUsageMonitor.getPremiumSmsPermission(
                    tracker.mAppInfo.packageName);
            if (premiumSmsPermission == SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN) {
                // First time trying to send to premium SMS.
                premiumSmsPermission = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER;
            }

            switch (premiumSmsPermission) {
                case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW:
                    Rlog.d(TAG, "User approved this app to send to premium SMS");
                    return true;

                case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW:
                    Rlog.w(TAG, "User denied this app from sending to premium SMS");
                    sendMessage(obtainMessage(EVENT_STOP_SENDING, tracker));
                    return false;   // reject this message

                case SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER:
                default:
                    int event;
                    if (smsCategory == SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE) {
                        event = EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE;
                    } else {
                        event = EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE;
                    }
                    sendMessage(obtainMessage(event, tracker));
                    return false;   // wait for user confirmation
            }
        }
    
private booleandenyIfQueueLimitReached(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Deny sending an SMS if the outgoing queue limit is reached. Used when the message must be confirmed by the user due to excessive usage or potential premium SMS detected.

param
tracker the SmsTracker for the message to send
return
true if the message was denied; false to continue with send confirmation

        if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) {
            // Deny sending message when the queue limit is reached.
            Rlog.e(TAG, "Denied because queue limit reached");
            tracker.onFailed(mContext, RESULT_ERROR_LIMIT_EXCEEDED, 0/*errorCode*/);
            return true;
        }
        mPendingTrackerCount++;
        return false;
    
public voiddispose()
Unregister for incoming SMS events.

        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
    
private java.lang.CharSequencegetAppLabel(java.lang.String appPackage)
Returns the label for the specified app package name.

param
appPackage the package name of the app requesting to send an SMS
return
the label for the specified app, or the package name if getApplicationInfo() fails

        PackageManager pm = mContext.getPackageManager();
        try {
            ApplicationInfo appInfo = pm.getApplicationInfo(appPackage, 0);
            return appInfo.loadLabel(pm);
        } catch (PackageManager.NameNotFoundException e) {
            Rlog.e(TAG, "PackageManager Name Not Found for package " + appPackage);
            return appPackage;  // fall back to package name if we can't get app label
        }
    
protected java.lang.StringgetCarrierAppPackageName()

        UiccCard card = UiccController.getInstance().getUiccCard(mPhone.getPhoneId());
        if (card == null) {
            return null;
        }

        List<String> carrierPackages = card.getCarrierPackageNamesForIntent(
            mContext.getPackageManager(), new Intent(CarrierMessagingService.SERVICE_INTERFACE));
        return (carrierPackages != null && carrierPackages.size() == 1) ?
                carrierPackages.get(0) : null;
    
protected abstract java.lang.StringgetFormat()
The format of the message PDU in the associated broadcast intent. This will be either "3gpp" for GSM/UMTS/LTE messages in 3GPP format or "3gpp2" for CDMA/LTE messages in 3GPP2 format. Note: All applications which handle incoming SMS messages by processing the SMS_RECEIVED_ACTION broadcast intent MUST pass the "format" extra from the intent into the new methods in {@link android.telephony.SmsMessage} which take an extra format parameter. This is required in order to correctly decode the PDU on devices which require support for both 3GPP and 3GPP2 formats at the same time, such as CDMA/LTE devices and GSM/CDMA world phones.

return
the format of the message PDU

public java.lang.StringgetImsSmsFormat()

        if (mImsSMSDispatcher != null) {
            return mImsSMSDispatcher.getImsSmsFormat();
        } else {
            Rlog.e(TAG, mImsSMSDispatcher + " is null");
            return null;
        }
    
private java.lang.StringgetMultipartMessageText(java.util.ArrayList parts)

        final StringBuilder sb = new StringBuilder();
        for (String part : parts) {
            if (part != null) {
                sb.append(part);
            }
        }
        return sb.toString();
    
protected abstract com.android.internal.telephony.SMSDispatcher$SmsTrackergetNewSubmitPduTracker(java.lang.String destinationAddress, java.lang.String scAddress, java.lang.String message, SmsHeader smsHeader, int encoding, android.app.PendingIntent sentIntent, android.app.PendingIntent deliveryIntent, boolean lastPart, java.util.concurrent.atomic.AtomicInteger unsentPartCount, java.util.concurrent.atomic.AtomicBoolean anyPartFailed, android.net.Uri messageUri, java.lang.String fullMessageText)
Create a new SubmitPdu and return the SMS tracker.

protected static intgetNextConcatenatedRef()


        
        sConcatenatedRef += 1;
        return sConcatenatedRef;
    
protected static intgetNotInServiceError(int ss)

param
ss service state
return
The result error based on input service state for not in service error

        if (ss == ServiceState.STATE_POWER_OFF) {
            return RESULT_ERROR_RADIO_OFF;
        }
        return RESULT_ERROR_NO_SERVICE;
    
public intgetPremiumSmsPermission(java.lang.String packageName)
Returns the premium SMS permission for the specified package. If the package has never been seen before, the default {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER} will be returned.

param
packageName the name of the package to query permission
return
one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_UNKNOWN}, {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW}

        return mUsageMonitor.getPremiumSmsPermission(packageName);
    
protected com.android.internal.telephony.SMSDispatcher$SmsTrackergetSmsTracker(java.util.HashMap data, android.app.PendingIntent sentIntent, android.app.PendingIntent deliveryIntent, java.lang.String format, java.util.concurrent.atomic.AtomicInteger unsentPartCount, java.util.concurrent.atomic.AtomicBoolean anyPartFailed, android.net.Uri messageUri, SmsHeader smsHeader, boolean isExpectMore, java.lang.String fullMessageText, boolean isText)

        // Get calling app package name via UID from Binder call
        PackageManager pm = mContext.getPackageManager();
        String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());

        // Get package info via packagemanager
        PackageInfo appInfo = null;
        if (packageNames != null && packageNames.length > 0) {
            try {
                // XXX this is lossy- apps can share a UID
                appInfo = pm.getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES);
            } catch (PackageManager.NameNotFoundException e) {
                // error will be logged in sendRawPdu
            }
        }
        // Strip non-digits from destination phone number before checking for short codes
        // and before displaying the number to the user if confirmation is required.
        String destAddr = PhoneNumberUtils.extractNetworkPortion((String) data.get("destAddr"));
        return new SmsTracker(data, sentIntent, deliveryIntent, appInfo, destAddr, format,
                unsentPartCount, anyPartFailed, messageUri, smsHeader, isExpectMore,
                fullMessageText, getSubId(), isText);
    
protected com.android.internal.telephony.SMSDispatcher$SmsTrackergetSmsTracker(java.util.HashMap data, android.app.PendingIntent sentIntent, android.app.PendingIntent deliveryIntent, java.lang.String format, android.net.Uri messageUri, boolean isExpectMore, java.lang.String fullMessageText, boolean isText)

        return getSmsTracker(data, sentIntent, deliveryIntent, format, null/*unsentPartCount*/,
                null/*anyPartFailed*/, messageUri, null/*smsHeader*/, isExpectMore,
                fullMessageText, isText);
    
protected java.util.HashMapgetSmsTrackerMap(java.lang.String destAddr, java.lang.String scAddr, java.lang.String text, SmsMessageBase.SubmitPduBase pdu)

        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("destAddr", destAddr);
        map.put("scAddr", scAddr);
        map.put("text", text);
        map.put("smsc", pdu.encodedScAddress);
        map.put("pdu", pdu.encodedMessage);
        return map;
    
protected java.util.HashMapgetSmsTrackerMap(java.lang.String destAddr, java.lang.String scAddr, int destPort, byte[] data, SmsMessageBase.SubmitPduBase pdu)

        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("destAddr", destAddr);
        map.put("scAddr", scAddr);
        map.put("destPort", destPort);
        map.put("data", data);
        map.put("smsc", pdu.encodedScAddress);
        map.put("pdu", pdu.encodedMessage);
        return map;
    
protected intgetSubId()

        return SubscriptionController.getInstance().getSubIdUsingPhoneId(mPhone.mPhoneId);
    
protected voidhandleConfirmShortCode(boolean isPremium, com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Post an alert for user confirmation when sending to a potential short code.

param
isPremium true if the destination is known to be a premium short code
param
tracker the SmsTracker for the current message.

        if (denyIfQueueLimitReached(tracker)) {
            return;     // queue limit reached; error was returned to caller
        }

        int detailsId;
        if (isPremium) {
            detailsId = R.string.sms_premium_short_code_details;
        } else {
            detailsId = R.string.sms_short_code_details;
        }

        CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName);
        Resources r = Resources.getSystem();
        Spanned messageText = Html.fromHtml(r.getString(R.string.sms_short_code_confirm_message,
                appLabel, tracker.mDestAddress));

        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        View layout = inflater.inflate(R.layout.sms_short_code_confirmation_dialog, null);

        ConfirmDialogListener listener = new ConfirmDialogListener(tracker,
                (TextView)layout.findViewById(R.id.sms_short_code_remember_undo_instruction));


        TextView messageView = (TextView) layout.findViewById(R.id.sms_short_code_confirm_message);
        messageView.setText(messageText);

        ViewGroup detailsLayout = (ViewGroup) layout.findViewById(
                R.id.sms_short_code_detail_layout);
        TextView detailsView = (TextView) detailsLayout.findViewById(
                R.id.sms_short_code_detail_message);
        detailsView.setText(detailsId);

        CheckBox rememberChoice = (CheckBox) layout.findViewById(
                R.id.sms_short_code_remember_choice_checkbox);
        rememberChoice.setOnCheckedChangeListener(listener);

        AlertDialog d = new AlertDialog.Builder(mContext)
                .setView(layout)
                .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener)
                .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener)
                .setOnCancelListener(listener)
                .create();

        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        d.show();

        listener.setPositiveButton(d.getButton(DialogInterface.BUTTON_POSITIVE));
        listener.setNegativeButton(d.getButton(DialogInterface.BUTTON_NEGATIVE));
    
public voidhandleMessage(android.os.Message msg)
Handles events coming from the phone stack. Overridden from handler.

param
msg the message to handle


                         
    
        
        switch (msg.what) {
        case EVENT_SEND_SMS_COMPLETE:
            // An outbound SMS has been successfully transferred, or failed.
            handleSendComplete((AsyncResult) msg.obj);
            break;

        case EVENT_SEND_RETRY:
            Rlog.d(TAG, "SMS retry..");
            sendRetrySms((SmsTracker) msg.obj);
            break;

        case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
            handleReachSentLimit((SmsTracker)(msg.obj));
            break;

        case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
            handleConfirmShortCode(false, (SmsTracker)(msg.obj));
            break;

        case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
            handleConfirmShortCode(true, (SmsTracker)(msg.obj));
            break;

        case EVENT_SEND_CONFIRMED_SMS:
        {
            SmsTracker tracker = (SmsTracker) msg.obj;
            if (tracker.isMultipart()) {
                sendMultipartSms(tracker);
            } else {
                if (mPendingTrackerCount > 1) {
                    tracker.mExpectMore = true;
                } else {
                    tracker.mExpectMore = false;
                }
                sendSms(tracker);
            }
            mPendingTrackerCount--;
            break;
        }

        case EVENT_STOP_SENDING:
        {
            SmsTracker tracker = (SmsTracker) msg.obj;
            tracker.onFailed(mContext, RESULT_ERROR_LIMIT_EXCEEDED, 0/*errorCode*/);
            mPendingTrackerCount--;
            break;
        }

        case EVENT_HANDLE_STATUS_REPORT:
            handleStatusReport(msg.obj);
            break;

        default:
            Rlog.e(TAG, "handleMessage() ignoring message of unexpected type " + msg.what);
        }
    
protected static voidhandleNotInService(int ss, android.app.PendingIntent sentIntent)
Handles outbound message when the phone is not in service.

param
ss Current service state. Valid values are: OUT_OF_SERVICE EMERGENCY_ONLY POWER_OFF
param
sentIntent the PendingIntent to send the error to

        if (sentIntent != null) {
            try {
                if (ss == ServiceState.STATE_POWER_OFF) {
                    sentIntent.send(RESULT_ERROR_RADIO_OFF);
                } else {
                    sentIntent.send(RESULT_ERROR_NO_SERVICE);
                }
            } catch (CanceledException ex) {}
        }
    
protected voidhandleReachSentLimit(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Post an alert when SMS needs confirmation due to excessive usage.

param
tracker an SmsTracker for the current message.

        if (denyIfQueueLimitReached(tracker)) {
            return;     // queue limit reached; error was returned to caller
        }

        CharSequence appLabel = getAppLabel(tracker.mAppInfo.packageName);
        Resources r = Resources.getSystem();
        Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));

        ConfirmDialogListener listener = new ConfirmDialogListener(tracker, null);

        AlertDialog d = new AlertDialog.Builder(mContext)
                .setTitle(R.string.sms_control_title)
                .setIcon(R.drawable.stat_sys_warning)
                .setMessage(messageText)
                .setPositiveButton(r.getString(R.string.sms_control_yes), listener)
                .setNegativeButton(r.getString(R.string.sms_control_no), listener)
                .setOnCancelListener(listener)
                .create();

        d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        d.show();
    
protected voidhandleSendComplete(android.os.AsyncResult ar)
Called when SMS send completes. Broadcasts a sentIntent on success. On failure, either sets up retries or broadcasts a sentIntent with the failure in the result code.

param
ar AsyncResult passed into the message handler. ar.result should an SmsResponse instance if send was successful. ar.userObj should be an SmsTracker instance.

        SmsTracker tracker = (SmsTracker) ar.userObj;
        PendingIntent sentIntent = tracker.mSentIntent;

        if (ar.result != null) {
            tracker.mMessageRef = ((SmsResponse)ar.result).mMessageRef;
        } else {
            Rlog.d(TAG, "SmsResponse was null");
        }

        if (ar.exception == null) {
            if (DBG) Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent);

            if (tracker.mDeliveryIntent != null) {
                // Expecting a status report.  Add it to the list.
                deliveryPendingList.add(tracker);
            }
            tracker.onSent(mContext);
        } else {
            if (DBG) Rlog.d(TAG, "SMS send failed");

            int ss = mPhone.getServiceState().getState();

            if ( tracker.mImsRetry > 0 && ss != ServiceState.STATE_IN_SERVICE) {
                // This is retry after failure over IMS but voice is not available.
                // Set retry to max allowed, so no retry is sent and
                //   cause RESULT_ERROR_GENERIC_FAILURE to be returned to app.
                tracker.mRetryCount = MAX_SEND_RETRIES;

                Rlog.d(TAG, "handleSendComplete: Skipping retry: "
                +" isIms()="+isIms()
                +" mRetryCount="+tracker.mRetryCount
                +" mImsRetry="+tracker.mImsRetry
                +" mMessageRef="+tracker.mMessageRef
                +" SS= "+mPhone.getServiceState().getState());
            }

            // if sms over IMS is not supported on data and voice is not available...
            if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
                tracker.onFailed(mContext, getNotInServiceError(ss), 0/*errorCode*/);
            } else if ((((CommandException)(ar.exception)).getCommandError()
                    == CommandException.Error.SMS_FAIL_RETRY) &&
                   tracker.mRetryCount < MAX_SEND_RETRIES) {
                // Retry after a delay if needed.
                // TODO: According to TS 23.040, 9.2.3.6, we should resend
                //       with the same TP-MR as the failed message, and
                //       TP-RD set to 1.  However, we don't have a means of
                //       knowing the MR for the failed message (EF_SMSstatus
                //       may or may not have the MR corresponding to this
                //       message, depending on the failure).  Also, in some
                //       implementations this retry is handled by the baseband.
                tracker.mRetryCount++;
                Message retryMsg = obtainMessage(EVENT_SEND_RETRY, tracker);
                sendMessageDelayed(retryMsg, SEND_RETRY_DELAY);
            } else {
                int errorCode = 0;
                if (ar.result != null) {
                    errorCode = ((SmsResponse)ar.result).mErrorCode;
                }
                int error = RESULT_ERROR_GENERIC_FAILURE;
                if (((CommandException)(ar.exception)).getCommandError()
                        == CommandException.Error.FDN_CHECK_FAILURE) {
                    error = RESULT_ERROR_FDN_CHECK_FAILURE;
                }
                tracker.onFailed(mContext, error, errorCode);
            }
        }
    
protected voidhandleStatusReport(java.lang.Object o)
Pass the Message object to subclass to handle. Currently used to pass CDMA status reports from {@link com.android.internal.telephony.cdma.CdmaInboundSmsHandler}.

param
o the SmsMessage containing the status report

        Rlog.d(TAG, "handleStatusReport() called with no subclass.");
    
protected abstract voidinjectSmsPdu(byte[] pdu, java.lang.String format, android.app.PendingIntent receivedIntent)
Inject an SMS PDU into the android platform.

param
pdu is the byte array of pdu to be injected into android telephony layer
param
format is the format of SMS pdu (3gpp or 3gpp2)
param
receivedIntent if not NULL this PendingIntent is broadcast when the message is successfully received by the android telephony layer. This intent is broadcasted at the same time an SMS received from radio is responded back.

public booleanisIms()

        if (mImsSMSDispatcher != null) {
            return mImsSMSDispatcher.isIms();
        } else {
            Rlog.e(TAG, mImsSMSDispatcher + " is null");
            return false;
        }
    
private voidprocessSendSmsResponse(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker, int result, int messageRef)

        if (tracker == null) {
            Rlog.e(TAG, "processSendSmsResponse: null tracker");
            return;
        }

        SmsResponse smsResponse = new SmsResponse(
                messageRef, null /* ackPdu */, -1 /* unknown error code */);

        switch (result) {
        case CarrierMessagingService.SEND_STATUS_OK:
            Rlog.d(TAG, "Sending SMS by IP succeeded.");
            sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
                                      new AsyncResult(tracker,
                                                      smsResponse,
                                                      null /* exception*/ )));
            break;
        case CarrierMessagingService.SEND_STATUS_ERROR:
            Rlog.d(TAG, "Sending SMS by IP failed.");
            sendMessage(obtainMessage(EVENT_SEND_SMS_COMPLETE,
                    new AsyncResult(tracker, smsResponse,
                            new CommandException(CommandException.Error.GENERIC_FAILURE))));
            break;
        case CarrierMessagingService.SEND_STATUS_RETRY_ON_CARRIER_NETWORK:
            Rlog.d(TAG, "Sending SMS by IP failed. Retry on carrier network.");
            sendSubmitPdu(tracker);
            break;
        default:
            Rlog.d(TAG, "Unknown result " + result + " Retry on carrier network.");
            sendSubmitPdu(tracker);
        }
    
protected abstract voidsendData(java.lang.String destAddr, java.lang.String scAddr, int destPort, byte[] data, android.app.PendingIntent sentIntent, android.app.PendingIntent deliveryIntent)
Send a data based SMS to a specific application port.

param
destAddr the address to send the message to
param
scAddr is the service center address or null to use the current default SMSC
param
destPort the port to deliver the message to
param
data the body of the message to send
param
sentIntent if not NULL this PendingIntent is broadcast when the message is successfully sent, or failed. The result code will be Activity.RESULT_OK for success, or one of these errors:
RESULT_ERROR_GENERIC_FAILURE
RESULT_ERROR_RADIO_OFF
RESULT_ERROR_NULL_PDU
RESULT_ERROR_NO_SERVICE
. For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include the extra "errorCode" containing a radio technology specific value, generally only useful for troubleshooting.
The per-application based SMS control checks sentIntent. If sentIntent is NULL the caller will be checked against all unknown applications, which cause smaller number of SMS to be sent in checking period.
param
deliveryIntent if not NULL this PendingIntent is broadcast when the message is delivered to the recipient. The raw pdu of the status report is in the extended data ("pdu").

private voidsendMultipartSms(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Send the multi-part SMS based on multipart Sms tracker

param
tracker holds the multipart Sms tracker ready to be sent

        ArrayList<String> parts;
        ArrayList<PendingIntent> sentIntents;
        ArrayList<PendingIntent> deliveryIntents;

        HashMap<String, Object> map = tracker.mData;

        String destinationAddress = (String) map.get("destination");
        String scAddress = (String) map.get("scaddress");

        parts = (ArrayList<String>) map.get("parts");
        sentIntents = (ArrayList<PendingIntent>) map.get("sentIntents");
        deliveryIntents = (ArrayList<PendingIntent>) map.get("deliveryIntents");

        // check if in service
        int ss = mPhone.getServiceState().getState();
        // if sms over IMS is not supported on data and voice is not available...
        if (!isIms() && ss != ServiceState.STATE_IN_SERVICE) {
            for (int i = 0, count = parts.size(); i < count; i++) {
                PendingIntent sentIntent = null;
                if (sentIntents != null && sentIntents.size() > i) {
                    sentIntent = sentIntents.get(i);
                }
                handleNotInService(ss, sentIntent);
            }
            return;
        }

        sendMultipartText(destinationAddress, scAddress, parts, sentIntents, deliveryIntents,
                null/*messageUri*/, null/*callingPkg*/);
    
protected voidsendMultipartText(java.lang.String destAddr, java.lang.String scAddr, java.util.ArrayList parts, java.util.ArrayList sentIntents, java.util.ArrayList deliveryIntents, android.net.Uri messageUri, java.lang.String callingPkg)
Send a multi-part text based SMS.

param
destAddr the address to send the message to
param
scAddr is the service center address or null to use the current default SMSC
param
parts an ArrayList of strings that, in order, comprise the original message
param
sentIntents if not null, an ArrayList of PendingIntents (one for each message part) that is broadcast when the corresponding message part has been sent. The result code will be Activity.RESULT_OK for success, or one of these errors: RESULT_ERROR_GENERIC_FAILURE RESULT_ERROR_RADIO_OFF RESULT_ERROR_NULL_PDU RESULT_ERROR_NO_SERVICE. The per-application based SMS control checks sentIntent. If sentIntent is NULL the caller will be checked against all unknown applications, which cause smaller number of SMS to be sent in checking period.
param
deliveryIntents if not null, an ArrayList of PendingIntents (one for each message part) that is broadcast when the corresponding message part has been delivered to the recipient. The raw pdu of the status report is in the
param
messageUri optional URI of the message if it is already stored in the system
param
callingPkg the calling package name

        final String fullMessageText = getMultipartMessageText(parts);
        int refNumber = getNextConcatenatedRef() & 0x00FF;
        int msgCount = parts.size();
        int encoding = SmsConstants.ENCODING_UNKNOWN;

        TextEncodingDetails[] encodingForParts = new TextEncodingDetails[msgCount];
        for (int i = 0; i < msgCount; i++) {
            TextEncodingDetails details = calculateLength(parts.get(i), false);
            if (encoding != details.codeUnitSize
                    && (encoding == SmsConstants.ENCODING_UNKNOWN
                            || encoding == SmsConstants.ENCODING_7BIT)) {
                encoding = details.codeUnitSize;
            }
            encodingForParts[i] = details;
        }

        SmsTracker[] trackers = new SmsTracker[msgCount];

        // States to track at the message level (for all parts)
        final AtomicInteger unsentPartCount = new AtomicInteger(msgCount);
        final AtomicBoolean anyPartFailed = new AtomicBoolean(false);

        for (int i = 0; i < msgCount; i++) {
            SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
            concatRef.refNumber = refNumber;
            concatRef.seqNumber = i + 1;  // 1-based sequence
            concatRef.msgCount = msgCount;
            // TODO: We currently set this to true since our messaging app will never
            // send more than 255 parts (it converts the message to MMS well before that).
            // However, we should support 3rd party messaging apps that might need 16-bit
            // references
            // Note:  It's not sufficient to just flip this bit to true; it will have
            // ripple effects (several calculations assume 8-bit ref).
            concatRef.isEightBits = true;
            SmsHeader smsHeader = new SmsHeader();
            smsHeader.concatRef = concatRef;

            // Set the national language tables for 3GPP 7-bit encoding, if enabled.
            if (encoding == SmsConstants.ENCODING_7BIT) {
                smsHeader.languageTable = encodingForParts[i].languageTable;
                smsHeader.languageShiftTable = encodingForParts[i].languageShiftTable;
            }

            PendingIntent sentIntent = null;
            if (sentIntents != null && sentIntents.size() > i) {
                sentIntent = sentIntents.get(i);
            }

            PendingIntent deliveryIntent = null;
            if (deliveryIntents != null && deliveryIntents.size() > i) {
                deliveryIntent = deliveryIntents.get(i);
            }

            trackers[i] =
                getNewSubmitPduTracker(destAddr, scAddr, parts.get(i), smsHeader, encoding,
                        sentIntent, deliveryIntent, (i == (msgCount - 1)),
                        unsentPartCount, anyPartFailed, messageUri, fullMessageText);
        }

        if (parts == null || trackers == null || trackers.length == 0
                || trackers[0] == null) {
            Rlog.e(TAG, "Cannot send multipart text. parts=" + parts + " trackers=" + trackers);
            return;
        }

        String carrierPackage = getCarrierAppPackageName();
        if (carrierPackage != null) {
            Rlog.d(TAG, "Found carrier package.");
            MultipartSmsSender smsSender = new MultipartSmsSender(parts, trackers);
            smsSender.sendSmsByCarrierApp(carrierPackage, new MultipartSmsSenderCallback(smsSender));
        } else {
            Rlog.v(TAG, "No carrier package.");
            for (SmsTracker tracker : trackers) {
                if (tracker != null) {
                    sendSubmitPdu(tracker);
                } else {
                    Rlog.e(TAG, "Null tracker.");
                }
            }
        }
    
protected voidsendRawPdu(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Send an SMS

param
tracker will contain: -smsc the SMSC to send the message through, or NULL for the default SMSC -pdu the raw PDU to send -sentIntent if not NULL this Intent is broadcast when the message is successfully sent, or failed. The result code will be Activity.RESULT_OK for success, or one of these errors: RESULT_ERROR_GENERIC_FAILURE RESULT_ERROR_RADIO_OFF RESULT_ERROR_NULL_PDU RESULT_ERROR_NO_SERVICE. The per-application based SMS control checks sentIntent. If sentIntent is NULL the caller will be checked against all unknown applications, which cause smaller number of SMS to be sent in checking period. -deliveryIntent if not NULL this Intent is broadcast when the message is delivered to the recipient. The raw pdu of the status report is in the extended data ("pdu"). -param destAddr the destination phone number (for short code confirmation)

        HashMap map = tracker.mData;
        byte pdu[] = (byte[]) map.get("pdu");

        if (mSmsSendDisabled) {
            Rlog.e(TAG, "Device does not support sending sms.");
            tracker.onFailed(mContext, RESULT_ERROR_NO_SERVICE, 0/*errorCode*/);
            return;
        }

        if (pdu == null) {
            Rlog.e(TAG, "Empty PDU");
            tracker.onFailed(mContext, RESULT_ERROR_NULL_PDU, 0/*errorCode*/);
            return;
        }

        // Get calling app package name via UID from Binder call
        PackageManager pm = mContext.getPackageManager();
        String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());

        if (packageNames == null || packageNames.length == 0) {
            // Refuse to send SMS if we can't get the calling package name.
            Rlog.e(TAG, "Can't get calling app package name: refusing to send SMS");
            tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/);
            return;
        }

        // Get package info via packagemanager
        PackageInfo appInfo;
        try {
            // XXX this is lossy- apps can share a UID
            appInfo = pm.getPackageInfo(packageNames[0], PackageManager.GET_SIGNATURES);
        } catch (PackageManager.NameNotFoundException e) {
            Rlog.e(TAG, "Can't get calling app package info: refusing to send SMS");
            tracker.onFailed(mContext, RESULT_ERROR_GENERIC_FAILURE, 0/*errorCode*/);
            return;
        }

        // checkDestination() returns true if the destination is not a premium short code or the
        // sending app is approved to send to short codes. Otherwise, a message is sent to our
        // handler with the SmsTracker to request user confirmation before sending.
        if (checkDestination(tracker)) {
            // check for excessive outgoing SMS usage by this app
            if (!mUsageMonitor.check(appInfo.packageName, SINGLE_PART_SMS)) {
                sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
                return;
            }

            sendSms(tracker);
        }
    
public voidsendRetrySms(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Retry the message along to the radio.

param
tracker holds the SMS message to send

        // re-routing to ImsSMSDispatcher
        if (mImsSMSDispatcher != null) {
            mImsSMSDispatcher.sendRetrySms(tracker);
        } else {
            Rlog.e(TAG, mImsSMSDispatcher + " is null. Retry failed");
        }
    
protected abstract voidsendSms(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Send the message along to the radio.

param
tracker holds the SMS message to send

protected abstract voidsendSmsByPstn(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Send the SMS via the PSTN network.

param
tracker holds the Sms tracker ready to be sent

protected abstract voidsendSubmitPdu(com.android.internal.telephony.SMSDispatcher$SmsTracker tracker)
Send an SMS PDU. Usually just calls {@link sendRawPdu}.

protected abstract voidsendText(java.lang.String destAddr, java.lang.String scAddr, java.lang.String text, android.app.PendingIntent sentIntent, android.app.PendingIntent deliveryIntent, android.net.Uri messageUri, java.lang.String callingPkg)
Send a text based SMS.

param
destAddr the address to send the message to
param
scAddr is the service center address or null to use the current default SMSC
param
text the body of the message to send
param
sentIntent if not NULL this PendingIntent is broadcast when the message is successfully sent, or failed. The result code will be Activity.RESULT_OK for success, or one of these errors:
RESULT_ERROR_GENERIC_FAILURE
RESULT_ERROR_RADIO_OFF
RESULT_ERROR_NULL_PDU
RESULT_ERROR_NO_SERVICE
. For RESULT_ERROR_GENERIC_FAILURE the sentIntent may include the extra "errorCode" containing a radio technology specific value, generally only useful for troubleshooting.
The per-application based SMS control checks sentIntent. If sentIntent is NULL the caller will be checked against all unknown applications, which cause smaller number of SMS to be sent in checking period.
param
deliveryIntent if not NULL this PendingIntent is broadcast when the message is delivered to the recipient. The
param
messageUri optional URI of the message if it is already stored in the system
param
callingPkg the calling package name

public voidsetPremiumSmsPermission(java.lang.String packageName, int permission)
Sets the premium SMS permission for the specified package and save the value asynchronously to persistent storage.

param
packageName the name of the package to set permission
param
permission one of {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ASK_USER}, {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_NEVER_ALLOW}, or {@link SmsUsageMonitor#PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW}

        mUsageMonitor.setPremiumSmsPermission(packageName, permission);
    
protected voidupdatePhoneObject(PhoneBase phone)

        mPhone = phone;
        mUsageMonitor = phone.mSmsUsageMonitor;
        Rlog.d(TAG, "Active phone changed to " + mPhone.getPhoneName() );