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

InboundSmsHandler

public abstract class InboundSmsHandler extends com.android.internal.util.StateMachine
This class broadcasts incoming SMS messages to interested apps after storing them in the SmsProvider "raw" table and ACKing them to the SMSC. After each message has been broadcast, its parts are removed from the raw table. If the device crashes after ACKing but before the broadcast completes, the pending messages will be rebroadcast on the next boot.

The state machine starts in {@link IdleState} state. When the {@link SMSDispatcher} receives a new SMS from the radio, it calls {@link #dispatchNormalMessage}, which sends a message to the state machine, causing the wakelock to be acquired in {@link #haltedProcessMessage}, which transitions to {@link DeliveringState} state, where the message is saved to the raw table, then acknowledged via the {@link SMSDispatcher} which called us.

After saving the SMS, if the message is complete (either single-part or the final segment of a multi-part SMS), we broadcast the completed PDUs as an ordered broadcast, then transition to {@link WaitingState} state to wait for the broadcast to complete. When the local {@link BroadcastReceiver} is called with the result, it sends {@link #EVENT_BROADCAST_COMPLETE} to the state machine, causing us to either broadcast the next pending message (if one has arrived while waiting for the broadcast to complete), or to transition back to the halted state after all messages are processed. Then the wakelock is released and we wait for the next SMS.

Fields Summary
protected static final boolean
DBG
private static final boolean
VDBG
private static final String[]
PDU_PROJECTION
Query projection for checking for duplicate message segments.
private static final String[]
PDU_SEQUENCE_PORT_PROJECTION
Query projection for combining concatenated message segments.
static final int
PDU_COLUMN
static final int
SEQUENCE_COLUMN
static final int
DESTINATION_PORT_COLUMN
static final int
DATE_COLUMN
static final int
REFERENCE_NUMBER_COLUMN
static final int
COUNT_COLUMN
static final int
ADDRESS_COLUMN
static final int
ID_COLUMN
static final String
SELECT_BY_ID
static final String
SELECT_BY_REFERENCE
public static final int
EVENT_NEW_SMS
New SMS received as an AsyncResult.
static final int
EVENT_BROADCAST_SMS
Message type containing a {@link InboundSmsTracker} ready to broadcast to listeners.
static final int
EVENT_BROADCAST_COMPLETE
Message from resultReceiver notifying {@link WaitingState} of a completed broadcast.
static final int
EVENT_RETURN_TO_IDLE
Sent on exit from {@link WaitingState} to return to idle after sending all broadcasts.
static final int
EVENT_RELEASE_WAKELOCK
Release wakelock after a short timeout when returning to idle state.
static final int
EVENT_START_ACCEPTING_SMS
Sent by {@link SmsBroadcastUndelivered} after cleaning the raw table.
static final int
EVENT_UPDATE_PHONE_OBJECT
Update phone object
public static final int
EVENT_INJECT_SMS
New SMS received as an AsyncResult.
private static final int
WAKELOCK_TIMEOUT
Wakelock release delay when returning to idle state.
private static final android.net.Uri
sRawUri
URI for raw table of SMS provider.
protected final android.content.Context
mContext
private final android.content.ContentResolver
mResolver
private final WapPushOverSms
mWapPush
Special handler for WAP push messages.
final PowerManager.WakeLock
mWakeLock
Wake lock to ensure device stays awake while dispatching the SMS intents.
final DefaultState
mDefaultState
DefaultState throws an exception or logs an error for unhandled message types.
final StartupState
mStartupState
Startup state. Waiting for {@link SmsBroadcastUndelivered} to complete.
final IdleState
mIdleState
Idle state. Waiting for messages to process.
final DeliveringState
mDeliveringState
Delivering state. Saves the PDU in the raw table and acknowledges to SMSC.
final WaitingState
mWaitingState
Broadcasting state. Waits for current broadcast to complete before delivering next.
protected SmsStorageMonitor
mStorageMonitor
Helper class to check whether storage is available for incoming messages.
private final boolean
mSmsReceiveDisabled
protected PhoneBase
mPhone
protected CellBroadcastHandler
mCellBroadcastHandler
private android.os.UserManager
mUserManager
Constructors Summary
protected InboundSmsHandler(String name, android.content.Context context, SmsStorageMonitor storageMonitor, PhoneBase phone, CellBroadcastHandler cellBroadcastHandler)
Create a new SMS broadcast helper.

param
name the class name for logging
param
context the context of the phone app
param
storageMonitor the SmsStorageMonitor to check for storage availability


                                       
          
                
        super(name);

        mContext = context;
        mStorageMonitor = storageMonitor;
        mPhone = phone;
        mCellBroadcastHandler = cellBroadcastHandler;
        mResolver = context.getContentResolver();
        mWapPush = new WapPushOverSms(context);

        boolean smsCapable = mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_sms_capable);
        mSmsReceiveDisabled = !TelephonyManager.from(mContext).getSmsReceiveCapableForPhone(
                mPhone.getPhoneId(), smsCapable);

        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
        mWakeLock.acquire();    // wake lock released after we enter idle state
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);

        addState(mDefaultState);
        addState(mStartupState, mDefaultState);
        addState(mIdleState, mDefaultState);
        addState(mDeliveringState, mDefaultState);
            addState(mWaitingState, mDeliveringState);

        setInitialState(mStartupState);
        if (DBG) log("created InboundSmsHandler");
    
Methods Summary
protected abstract voidacknowledgeLastIncomingSms(boolean success, int result, android.os.Message response)
Send an acknowledge message to the SMSC.

param
success indicates that last message was successfully received.
param
result result code indicating any error
param
response callback message sent when operation completes.

private intaddTrackerToRawTable(InboundSmsTracker tracker)
Insert a message PDU into the raw table so we can acknowledge it immediately. If the device crashes before the broadcast to listeners completes, it will be delivered from the raw table on the next device boot. For single-part messages, the deleteWhere and deleteWhereArgs fields of the tracker will be set to delete the correct row after the ordered broadcast completes.

param
tracker the tracker to add to the raw table
return
true on success; false on failure to write to database

        if (tracker.getMessageCount() != 1) {
            // check for duplicate message segments
            Cursor cursor = null;
            try {
                // sequence numbers are 1-based except for CDMA WAP, which is 0-based
                int sequence = tracker.getSequenceNumber();

                // convert to strings for query
                String address = tracker.getAddress();
                String refNumber = Integer.toString(tracker.getReferenceNumber());
                String count = Integer.toString(tracker.getMessageCount());

                String seqNumber = Integer.toString(sequence);

                // set the delete selection args for multi-part message
                String[] deleteWhereArgs = {address, refNumber, count};
                tracker.setDeleteWhere(SELECT_BY_REFERENCE, deleteWhereArgs);

                // Check for duplicate message segments
                cursor = mResolver.query(sRawUri, PDU_PROJECTION,
                        "address=? AND reference_number=? AND count=? AND sequence=?",
                        new String[] {address, refNumber, count, seqNumber}, null);

                // moveToNext() returns false if no duplicates were found
                if (cursor.moveToNext()) {
                    loge("Discarding duplicate message segment, refNumber=" + refNumber
                            + " seqNumber=" + seqNumber);
                    String oldPduString = cursor.getString(PDU_COLUMN);
                    byte[] pdu = tracker.getPdu();
                    byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString);
                    if (!Arrays.equals(oldPdu, tracker.getPdu())) {
                        loge("Warning: dup message segment PDU of length " + pdu.length
                                + " is different from existing PDU of length " + oldPdu.length);
                    }
                    return Intents.RESULT_SMS_DUPLICATED;   // reject message
                }
                cursor.close();
            } catch (SQLException e) {
                loge("Can't access multipart SMS database", e);
                return Intents.RESULT_SMS_GENERIC_ERROR;    // reject message
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }

        ContentValues values = tracker.getContentValues();

        if (VDBG) log("adding content values to raw table: " + values.toString());
        Uri newUri = mResolver.insert(sRawUri, values);
        if (DBG) log("URI of new row -> " + newUri);

        try {
            long rowId = ContentUris.parseId(newUri);
            if (tracker.getMessageCount() == 1) {
                // set the delete selection args for single-part message
                tracker.setDeleteWhere(SELECT_BY_ID, new String[]{Long.toString(rowId)});
            }
            return Intents.RESULT_SMS_HANDLED;
        } catch (Exception e) {
            loge("error parsing URI for new row: " + newUri, e);
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }
    
protected intaddTrackerToRawTableAndSendMessage(InboundSmsTracker tracker)
Helper to add the tracker to the raw table and then send a message to broadcast it, if successful. Returns the SMS intent status to return to the SMSC.

param
tracker the tracker to save to the raw table and then deliver
return
{@link Intents#RESULT_SMS_HANDLED} or {@link Intents#RESULT_SMS_GENERIC_ERROR} or {@link Intents#RESULT_SMS_DUPLICATED}

        switch(addTrackerToRawTable(tracker)) {
        case Intents.RESULT_SMS_HANDLED:
            sendMessage(EVENT_BROADCAST_SMS, tracker);
            return Intents.RESULT_SMS_HANDLED;

        case Intents.RESULT_SMS_DUPLICATED:
            return Intents.RESULT_SMS_HANDLED;

        case Intents.RESULT_SMS_GENERIC_ERROR:
        default:
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }
    
private static java.lang.StringbuildMessageBodyFromPdus(android.telephony.SmsMessage[] msgs)
Build up the SMS message body from the SmsMessage array of received SMS

param
msgs The SmsMessage array of the received SMS
return
The text message body

        if (msgs.length == 1) {
            // There is only one part, so grab the body directly.
            return replaceFormFeeds(msgs[0].getDisplayMessageBody());
        } else {
            // Build up the body from the parts.
            StringBuilder body = new StringBuilder();
            for (SmsMessage msg: msgs) {
                // getDisplayMessageBody() can NPE if mWrappedMessage inside is null.
                body.append(msg.getDisplayMessageBody());
            }
            return replaceFormFeeds(body.toString());
        }
    
voiddeleteFromRawTable(java.lang.String deleteWhere, java.lang.String[] deleteWhereArgs)
Helper for {@link SmsBroadcastUndelivered} to delete an old message in the raw table.

        int rows = mResolver.delete(sRawUri, deleteWhere, deleteWhereArgs);
        if (rows == 0) {
            loge("No rows were deleted from raw table!");
        } else if (DBG) {
            log("Deleted " + rows + " rows from raw table.");
        }
    
protected voiddispatchIntent(android.content.Intent intent, java.lang.String permission, int appOp, android.content.BroadcastReceiver resultReceiver, android.os.UserHandle user)
Dispatch the intent with the specified permission, appOp, and result receiver, using this state machine's handler thread to run the result receiver.

param
intent the intent to broadcast
param
permission receivers are required to have this permission
param
appOp app op that is being performed when dispatching to a receiver
param
user user to deliver the intent to

        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
        if (user.equals(UserHandle.ALL)) {
            // Get a list of currently started users.
            int[] users = null;
            try {
                users = ActivityManagerNative.getDefault().getRunningUserIds();
            } catch (RemoteException re) {
            }
            if (users == null) {
                users = new int[] {user.getIdentifier()};
            }
            // Deliver the broadcast only to those running users that are permitted
            // by user policy.
            for (int i = users.length - 1; i >= 0; i--) {
                UserHandle targetUser = new UserHandle(users[i]);
                if (users[i] != UserHandle.USER_OWNER) {
                    // Is the user not allowed to use SMS?
                    if (mUserManager.hasUserRestriction(UserManager.DISALLOW_SMS, targetUser)) {
                        continue;
                    }
                    // Skip unknown users and managed profiles as well
                    UserInfo info = mUserManager.getUserInfo(users[i]);
                    if (info == null || info.isManagedProfile()) {
                        continue;
                    }
                }
                // Only pass in the resultReceiver when the USER_OWNER is processed.
                mContext.sendOrderedBroadcastAsUser(intent, targetUser, permission, appOp,
                        users[i] == UserHandle.USER_OWNER ? resultReceiver : null,
                        getHandler(), Activity.RESULT_OK, null, null);
            }
        } else {
            mContext.sendOrderedBroadcastAsUser(intent, user, permission, appOp,
                    resultReceiver,
                    getHandler(), Activity.RESULT_OK, null, null);
        }
    
public intdispatchMessage(SmsMessageBase smsb)
Process an SMS message from the RIL, calling subclass methods to handle 3GPP and 3GPP2-specific message types.

param
smsb the SmsMessageBase object from the RIL
return
a result code from {@link android.provider.Telephony.Sms.Intents}, or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC

        // If sms is null, there was a parsing error.
        if (smsb == null) {
            loge("dispatchSmsMessage: message is null");
            return Intents.RESULT_SMS_GENERIC_ERROR;
        }

        if (mSmsReceiveDisabled) {
            // Device doesn't support receiving SMS,
            log("Received short message on device which doesn't support "
                    + "receiving SMS. Ignored.");
            return Intents.RESULT_SMS_HANDLED;
        }

        return dispatchMessageRadioSpecific(smsb);
    
protected abstract intdispatchMessageRadioSpecific(SmsMessageBase smsb)
Process voicemail notification, SMS-PP data download, CDMA CMAS, CDMA WAP push, and other 3GPP/3GPP2-specific messages. Regular SMS messages are handled by calling the shared {@link #dispatchNormalMessage} from this class.

param
smsb the SmsMessageBase object from the RIL
return
a result code from {@link android.provider.Telephony.Sms.Intents}, or {@link Activity#RESULT_OK} for delayed acknowledgment to SMSC

protected intdispatchNormalMessage(SmsMessageBase sms)
Dispatch a normal incoming SMS. This is called from {@link #dispatchMessageRadioSpecific} if no format-specific handling was required. Saves the PDU to the SMS provider raw table, creates an {@link InboundSmsTracker}, then sends it to the state machine as an {@link #EVENT_BROADCAST_SMS}. Returns {@link Intents#RESULT_SMS_HANDLED} or an error value.

param
sms the message to dispatch
return
{@link Intents#RESULT_SMS_HANDLED} if the message was accepted, or an error status

        SmsHeader smsHeader = sms.getUserDataHeader();
        InboundSmsTracker tracker;

        if ((smsHeader == null) || (smsHeader.concatRef == null)) {
            // Message is not concatenated.
            int destPort = -1;
            if (smsHeader != null && smsHeader.portAddrs != null) {
                // The message was sent to a port.
                destPort = smsHeader.portAddrs.destPort;
                if (DBG) log("destination port: " + destPort);
            }

            tracker = new InboundSmsTracker(sms.getPdu(), sms.getTimestampMillis(), destPort,
                    is3gpp2(), false);
        } else {
            // Create a tracker for this message segment.
            SmsHeader.ConcatRef concatRef = smsHeader.concatRef;
            SmsHeader.PortAddrs portAddrs = smsHeader.portAddrs;
            int destPort = (portAddrs != null ? portAddrs.destPort : -1);

            tracker = new InboundSmsTracker(sms.getPdu(), sms.getTimestampMillis(), destPort,
                    is3gpp2(), sms.getOriginatingAddress(), concatRef.refNumber,
                    concatRef.seqNumber, concatRef.msgCount, false);
        }

        if (VDBG) log("created tracker: " + tracker);
        return addTrackerToRawTableAndSendMessage(tracker);
    
voiddispatchSmsDeliveryIntent(byte[][] pdus, java.lang.String format, int destPort, android.content.BroadcastReceiver resultReceiver)
Creates and dispatches the intent to the default SMS app or the appropriate port.

param
pdus message pdus
param
format the message format, typically "3gpp" or "3gpp2"
param
destPort the destination port
param
resultReceiver the receiver handling the delivery result

        Intent intent = new Intent();
        intent.putExtra("pdus", pdus);
        intent.putExtra("format", format);

        if (destPort == -1) {
            intent.setAction(Intents.SMS_DELIVER_ACTION);
            // Direct the intent to only the default SMS app. If we can't find a default SMS app
            // then sent it to all broadcast receivers.
            // We are deliberately delivering to the primary user's default SMS App.
            ComponentName componentName = SmsApplication.getDefaultSmsApplication(mContext, true);
            if (componentName != null) {
                // Deliver SMS message only to this receiver.
                intent.setComponent(componentName);
                log("Delivering SMS to: " + componentName.getPackageName() +
                    " " + componentName.getClassName());
            } else {
                intent.setComponent(null);
            }

            // TODO: Validate that this is the right place to store the SMS.
            if (SmsManager.getDefault().getAutoPersisting()) {
                final Uri uri = writeInboxMessage(intent);
                if (uri != null) {
                    // Pass this to SMS apps so that they know where it is stored
                    intent.putExtra("uri", uri.toString());
                }
            }
        } else {
            intent.setAction(Intents.DATA_SMS_RECEIVED_ACTION);
            Uri uri = Uri.parse("sms://localhost:" + destPort);
            intent.setData(uri);
            intent.setComponent(null);
        }

        dispatchIntent(intent, android.Manifest.permission.RECEIVE_SMS,
                AppOpsManager.OP_RECEIVE_SMS, resultReceiver, UserHandle.OWNER);
    
public voiddispose()
Tell the state machine to quit after processing all messages.

        quit();
    
public PhoneBasegetPhone()

        return mPhone;
    
private java.util.ListgetSystemAppForIntent(android.content.Intent intent)

        List<String> packages = new ArrayList<String>();
        PackageManager packageManager = mContext.getPackageManager();
        List<ResolveInfo> receivers = packageManager.queryIntentServices(intent, 0);
        String carrierFilterSmsPerm = "android.permission.CARRIER_FILTER_SMS";

        for (ResolveInfo info : receivers) {
            if (info.serviceInfo == null) {
                loge("Can't get service information from " + info);
                continue;
            }
            String packageName = info.serviceInfo.packageName;
                if (packageManager.checkPermission(carrierFilterSmsPerm, packageName) ==
                        packageManager.PERMISSION_GRANTED) {
                    packages.add(packageName);
                    if (DBG) log("getSystemAppForIntent: added package "+ packageName);
                }
        }
        return packages;
    
voidhandleInjectSms(android.os.AsyncResult ar)
This method is called when a new SMS PDU is injected into application framework.

param
ar is the AsyncResult that has the SMS PDU to be injected.

        int result;
        PendingIntent receivedIntent = null;
        try {
            receivedIntent = (PendingIntent) ar.userObj;
            SmsMessage sms = (SmsMessage) ar.result;
            if (sms == null) {
              result = Intents.RESULT_SMS_GENERIC_ERROR;
            } else {
              result = dispatchMessage(sms.mWrappedSmsMessage);
            }
        } catch (RuntimeException ex) {
            loge("Exception dispatching message", ex);
            result = Intents.RESULT_SMS_GENERIC_ERROR;
        }

        if (receivedIntent != null) {
            try {
                receivedIntent.send(result);
            } catch (CanceledException e) { }
        }
    
voidhandleNewSms(android.os.AsyncResult ar)

        if (ar.exception != null) {
            loge("Exception processing incoming SMS: " + ar.exception);
            return;
        }

        int result;
        try {
            SmsMessage sms = (SmsMessage) ar.result;
            result = dispatchMessage(sms.mWrappedSmsMessage);
        } catch (RuntimeException ex) {
            loge("Exception dispatching message", ex);
            result = Intents.RESULT_SMS_GENERIC_ERROR;
        }

        // RESULT_OK means that the SMS will be acknowledged by special handling,
        // e.g. for SMS-PP data download. Any other result, we should ack here.
        if (result != Activity.RESULT_OK) {
            boolean handled = (result == Intents.RESULT_SMS_HANDLED);
            notifyAndAcknowledgeLastIncomingSms(handled, result, null);
        }
    
protected abstract booleanis3gpp2()
Return true if this handler is for 3GPP2 messages; false for 3GPP format.

return
true for the 3GPP2 handler; false for the 3GPP handler

static booleanisCurrentFormat3gpp2()
Returns whether the default message format for the current radio technology is 3GPP2.

return
true if the radio technology uses 3GPP2 format by default, false for 3GPP format

        int activePhone = TelephonyManager.getDefault().getCurrentPhoneType();
        return (PHONE_TYPE_CDMA == activePhone);
    
protected voidlog(java.lang.String s)
Log with debug level.

param
s the string to log

        Rlog.d(getName(), s);
    
protected voidloge(java.lang.String s)
Log with error level.

param
s the string to log

        Rlog.e(getName(), s);
    
protected voidloge(java.lang.String s, java.lang.Throwable e)
Log with error level.

param
s the string to log
param
e is a Throwable which logs additional information.

        Rlog.e(getName(), s, e);
    
voidnotifyAndAcknowledgeLastIncomingSms(boolean success, int result, android.os.Message response)
Notify interested apps if the framework has rejected an incoming SMS, and send an acknowledge message to the network.

param
success indicates that last message was successfully received.
param
result result code indicating any error
param
response callback message sent when operation completes.

        if (!success) {
            // broadcast SMS_REJECTED_ACTION intent
            Intent intent = new Intent(Intents.SMS_REJECTED_ACTION);
            intent.putExtra("result", result);
            mContext.sendBroadcast(intent, android.Manifest.permission.RECEIVE_SMS);
        }
        acknowledgeLastIncomingSms(success, result, response);
    
protected voidonQuitting()
Dispose of the WAP push object and release the wakelock.

        mWapPush.dispose();

        while (mWakeLock.isHeld()) {
            mWakeLock.release();
        }
    
protected voidonUpdatePhoneObject(PhoneBase phone)
Called when the phone changes the default method updates mPhone mStorageMonitor and mCellBroadcastHandler.updatePhoneObject. Override if different or other behavior is desired.

param
phone

        mPhone = phone;
        mStorageMonitor = mPhone.mSmsStorageMonitor;
        log("onUpdatePhoneObject: phone=" + mPhone.getClass().getSimpleName());
    
private static android.content.ContentValuesparseSmsMessage(android.telephony.SmsMessage[] msgs)
Convert SmsMessage[] into SMS database schema columns

param
msgs The SmsMessage array of the received SMS
return
ContentValues representing the columns of parsed SMS

        final SmsMessage sms = msgs[0];
        final ContentValues values = new ContentValues();
        values.put(Telephony.Sms.Inbox.ADDRESS, sms.getDisplayOriginatingAddress());
        values.put(Telephony.Sms.Inbox.BODY, buildMessageBodyFromPdus(msgs));
        values.put(Telephony.Sms.Inbox.DATE_SENT, sms.getTimestampMillis());
        values.put(Telephony.Sms.Inbox.DATE, System.currentTimeMillis());
        values.put(Telephony.Sms.Inbox.PROTOCOL, sms.getProtocolIdentifier());
        values.put(Telephony.Sms.Inbox.SEEN, 0);
        values.put(Telephony.Sms.Inbox.READ, 0);
        final String subject = sms.getPseudoSubject();
        if (!TextUtils.isEmpty(subject)) {
            values.put(Telephony.Sms.Inbox.SUBJECT, subject);
        }
        values.put(Telephony.Sms.Inbox.REPLY_PATH_PRESENT, sms.isReplyPathPresent() ? 1 : 0);
        values.put(Telephony.Sms.Inbox.SERVICE_CENTER, sms.getServiceCenterAddress());
        return values;
    
booleanprocessMessagePart(InboundSmsTracker tracker)
Process the inbound SMS segment. If the message is complete, send it as an ordered broadcast to interested receivers and return true. If the message is a segment of an incomplete multi-part SMS, return false.

param
tracker the tracker containing the message segment to process
return
true if an ordered broadcast was sent; false if waiting for more message segments

        int messageCount = tracker.getMessageCount();
        byte[][] pdus;
        int destPort = tracker.getDestPort();

        if (messageCount == 1) {
            // single-part message
            pdus = new byte[][]{tracker.getPdu()};
        } else {
            // multi-part message
            Cursor cursor = null;
            try {
                // used by several query selection arguments
                String address = tracker.getAddress();
                String refNumber = Integer.toString(tracker.getReferenceNumber());
                String count = Integer.toString(tracker.getMessageCount());

                // query for all segments and broadcast message if we have all the parts
                String[] whereArgs = {address, refNumber, count};
                cursor = mResolver.query(sRawUri, PDU_SEQUENCE_PORT_PROJECTION,
                        SELECT_BY_REFERENCE, whereArgs, null);

                int cursorCount = cursor.getCount();
                if (cursorCount < messageCount) {
                    // Wait for the other message parts to arrive. It's also possible for the last
                    // segment to arrive before processing the EVENT_BROADCAST_SMS for one of the
                    // earlier segments. In that case, the broadcast will be sent as soon as all
                    // segments are in the table, and any later EVENT_BROADCAST_SMS messages will
                    // get a row count of 0 and return.
                    return false;
                }

                // All the parts are in place, deal with them
                pdus = new byte[messageCount][];
                while (cursor.moveToNext()) {
                    // subtract offset to convert sequence to 0-based array index
                    int index = cursor.getInt(SEQUENCE_COLUMN) - tracker.getIndexOffset();

                    pdus[index] = HexDump.hexStringToByteArray(cursor.getString(PDU_COLUMN));

                    // Read the destination port from the first segment (needed for CDMA WAP PDU).
                    // It's not a bad idea to prefer the port from the first segment in other cases.
                    if (index == 0 && !cursor.isNull(DESTINATION_PORT_COLUMN)) {
                        int port = cursor.getInt(DESTINATION_PORT_COLUMN);
                        // strip format flags and convert to real port number, or -1
                        port = InboundSmsTracker.getRealDestPort(port);
                        if (port != -1) {
                            destPort = port;
                        }
                    }
                }
            } catch (SQLException e) {
                loge("Can't access multipart SMS database", e);
                return false;
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }

        SmsBroadcastReceiver resultReceiver = new SmsBroadcastReceiver(tracker);

        if (destPort == SmsHeader.PORT_WAP_PUSH) {
            // Build up the data stream
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            for (byte[] pdu : pdus) {
                // 3GPP needs to extract the User Data from the PDU; 3GPP2 has already done this
                if (!tracker.is3gpp2()) {
                    SmsMessage msg = SmsMessage.createFromPdu(pdu, SmsConstants.FORMAT_3GPP);
                    pdu = msg.getUserData();
                }
                output.write(pdu, 0, pdu.length);
            }
            int result = mWapPush.dispatchWapPdu(output.toByteArray(), resultReceiver, this);
            if (DBG) log("dispatchWapPdu() returned " + result);
            // result is Activity.RESULT_OK if an ordered broadcast was sent
            return (result == Activity.RESULT_OK);
        }

        List<String> carrierPackages = null;
        UiccCard card = UiccController.getInstance().getUiccCard(mPhone.getPhoneId());
        if (card != null) {
            carrierPackages = card.getCarrierPackageNamesForIntent(
                    mContext.getPackageManager(),
                    new Intent(CarrierMessagingService.SERVICE_INTERFACE));
        } else {
            loge("UiccCard not initialized.");
        }

        List<String> systemPackages =
                getSystemAppForIntent(new Intent(CarrierMessagingService.SERVICE_INTERFACE));

        if (carrierPackages != null && carrierPackages.size() == 1) {
            log("Found carrier package.");
            CarrierSmsFilter smsFilter = new CarrierSmsFilter(pdus, destPort,
                    tracker.getFormat(), resultReceiver);
            CarrierSmsFilterCallback smsFilterCallback = new CarrierSmsFilterCallback(smsFilter);
            smsFilter.filterSms(carrierPackages.get(0), smsFilterCallback);
        } else if (systemPackages != null && systemPackages.size() == 1) {
            log("Found system package.");
            CarrierSmsFilter smsFilter = new CarrierSmsFilter(pdus, destPort,
                    tracker.getFormat(), resultReceiver);
            CarrierSmsFilterCallback smsFilterCallback = new CarrierSmsFilterCallback(smsFilter);
            smsFilter.filterSms(systemPackages.get(0), smsFilterCallback);
        } else {
            logv("Unable to find carrier package: " + carrierPackages
                    + ", nor systemPackages: " + systemPackages);
            dispatchSmsDeliveryIntent(pdus, tracker.getFormat(), destPort, resultReceiver);
        }

        return true;
    
private static java.lang.StringreplaceFormFeeds(java.lang.String s)

        return s == null ? "" : s.replace('\f", '\n");
    
protected voidstoreVoiceMailCount()

        // Store the voice mail count in persistent memory.
        String imsi = mPhone.getSubscriberId();
        int mwi = mPhone.getVoiceMessageCount();

        log("Storing Voice Mail Count = " + mwi
                    + " for mVmCountKey = " + mPhone.VM_COUNT
                    + " vmId = " + mPhone.VM_ID
                    + " in preferences.");

        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
        SharedPreferences.Editor editor = sp.edit();
        editor.putInt(mPhone.VM_COUNT, mwi);
        editor.putString(mPhone.VM_ID, imsi);
        editor.commit();
    
public voidupdatePhoneObject(PhoneBase phone)
Update the phone object when it changes.

        sendMessage(EVENT_UPDATE_PHONE_OBJECT, phone);
    
private android.net.UriwriteInboxMessage(android.content.Intent intent)
Store a received SMS into Telephony provider

param
intent The intent containing the received SMS
return
The URI of written message

        final SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
        if (messages == null || messages.length < 1) {
            loge("Failed to parse SMS pdu");
            return null;
        }
        // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
        // the methods on it although the SmsMessage itself is not null. So do this check
        // before we do anything on the parsed SmsMessages.
        for (final SmsMessage sms : messages) {
            try {
                sms.getDisplayMessageBody();
            } catch (NullPointerException e) {
                loge("NPE inside SmsMessage");
                return null;
            }
        }
        final ContentValues values = parseSmsMessage(messages);
        final long identity = Binder.clearCallingIdentity();
        try {
            return mContext.getContentResolver().insert(Telephony.Sms.Inbox.CONTENT_URI, values);
        } catch (Exception e) {
            loge("Failed to persist inbox message", e);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        return null;