FileDocCategorySizeDatePackage
PduPersister.javaAPI DocAndroid 1.5 API48366Wed May 06 22:41:56 BST 2009com.google.android.mms.pdu

PduPersister

public class PduPersister extends Object
This class is the high-level manager of PDU storage.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final boolean
LOCAL_LOGV
private static final long
DUMMY_THREAD_ID
public static final String
TEMPORARY_DRM_OBJECT_URI
The uri of temporary drm objects.
public static final int
PROC_STATUS_TRANSIENT_FAILURE
Indicate that we transiently failed to process a MM.
public static final int
PROC_STATUS_PERMANENTLY_FAILURE
Indicate that we permanently failed to process a MM.
public static final int
PROC_STATUS_COMPLETED
Indicate that we have successfully processed a MM.
private static PduPersister
sPersister
private static final com.google.android.mms.util.PduCache
PDU_CACHE_INSTANCE
private static final int[]
ADDRESS_FIELDS
private static final String[]
PDU_PROJECTION
private static final int
PDU_COLUMN_ID
private static final int
PDU_COLUMN_MESSAGE_BOX
private static final int
PDU_COLUMN_THREAD_ID
private static final int
PDU_COLUMN_RETRIEVE_TEXT
private static final int
PDU_COLUMN_SUBJECT
private static final int
PDU_COLUMN_CONTENT_LOCATION
private static final int
PDU_COLUMN_CONTENT_TYPE
private static final int
PDU_COLUMN_MESSAGE_CLASS
private static final int
PDU_COLUMN_MESSAGE_ID
private static final int
PDU_COLUMN_RESPONSE_TEXT
private static final int
PDU_COLUMN_TRANSACTION_ID
private static final int
PDU_COLUMN_CONTENT_CLASS
private static final int
PDU_COLUMN_DELIVERY_REPORT
private static final int
PDU_COLUMN_MESSAGE_TYPE
private static final int
PDU_COLUMN_MMS_VERSION
private static final int
PDU_COLUMN_PRIORITY
private static final int
PDU_COLUMN_READ_REPORT
private static final int
PDU_COLUMN_READ_STATUS
private static final int
PDU_COLUMN_REPORT_ALLOWED
private static final int
PDU_COLUMN_RETRIEVE_STATUS
private static final int
PDU_COLUMN_STATUS
private static final int
PDU_COLUMN_DATE
private static final int
PDU_COLUMN_DELIVERY_TIME
private static final int
PDU_COLUMN_EXPIRY
private static final int
PDU_COLUMN_MESSAGE_SIZE
private static final int
PDU_COLUMN_SUBJECT_CHARSET
private static final int
PDU_COLUMN_RETRIEVE_TEXT_CHARSET
private static final String[]
PART_PROJECTION
private static final int
PART_COLUMN_ID
private static final int
PART_COLUMN_CHARSET
private static final int
PART_COLUMN_CONTENT_DISPOSITION
private static final int
PART_COLUMN_CONTENT_ID
private static final int
PART_COLUMN_CONTENT_LOCATION
private static final int
PART_COLUMN_CONTENT_TYPE
private static final int
PART_COLUMN_FILENAME
private static final int
PART_COLUMN_NAME
private static final HashMap
MESSAGE_BOX_MAP
private static final HashMap
CHARSET_COLUMN_INDEX_MAP
private static final HashMap
ENCODED_STRING_COLUMN_INDEX_MAP
private static final HashMap
TEXT_STRING_COLUMN_INDEX_MAP
private static final HashMap
OCTET_COLUMN_INDEX_MAP
private static final HashMap
LONG_COLUMN_INDEX_MAP
private static final HashMap
CHARSET_COLUMN_NAME_MAP
private static final HashMap
ENCODED_STRING_COLUMN_NAME_MAP
private static final HashMap
TEXT_STRING_COLUMN_NAME_MAP
private static final HashMap
OCTET_COLUMN_NAME_MAP
private static final HashMap
LONG_COLUMN_NAME_MAP
private final android.content.Context
mContext
private final android.content.ContentResolver
mContentResolver
Constructors Summary
private PduPersister(android.content.Context context)

        mContext = context;
        mContentResolver = context.getContentResolver();
     
Methods Summary
private byte[]getByteArrayFromPartColumn(android.database.Cursor c, int columnIndex)

        if (!c.isNull(columnIndex)) {
            return getBytes(c.getString(columnIndex));
        }
        return null;
    
public static byte[]getBytes(java.lang.String data)
Unpack a given String into a byte[].

        try {
            return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
        } catch (UnsupportedEncodingException e) {
            // Impossible to reach here!
            Log.e(TAG, "ISO_8859_1 must be supported!", e);
            return new byte[0];
        }
    
private java.lang.IntegergetIntegerFromPartColumn(android.database.Cursor c, int columnIndex)

        if (!c.isNull(columnIndex)) {
            return c.getInt(columnIndex);
        }
        return null;
    
public static com.google.android.mms.pdu.PduPersistergetPduPersister(android.content.Context context)
Get(or create if not exist) an instance of PduPersister

        if ((sPersister == null) || !context.equals(sPersister.mContext)) {
            sPersister = new PduPersister(context);
        }

        return sPersister;
    
public android.database.CursorgetPendingMessages(long dueTime)
Find all messages to be sent or downloaded before certain time.

        Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
        uriBuilder.appendQueryParameter("protocol", "mms");

        String selection = PendingMessages.ERROR_TYPE + " < ?"
                + " AND " + PendingMessages.DUE_TIME + " <= ?";

        String[] selectionArgs = new String[] {
                String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
                String.valueOf(dueTime)
        };

        return SqliteWrapper.query(mContext, mContentResolver,
                uriBuilder.build(), null, selection, selectionArgs,
                PendingMessages.DUE_TIME);
    
public GenericPduload(android.net.Uri uri)
Load a PDU from storage by given Uri.

param
uri The Uri of the PDU to be loaded.
return
A generic PDU object, it may be cast to dedicated PDU.
throws
MmsException Failed to load some fields of a PDU.

        PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri);
        if (cacheEntry != null) {
            return cacheEntry.getPdu();
        }

        Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
                        PDU_PROJECTION, null, null, null);
        PduHeaders headers = new PduHeaders();
        Set<Entry<Integer, Integer>> set;
        long msgId = ContentUris.parseId(uri);
        int msgBox;
        long threadId;

        try {
            if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
                throw new MmsException("Bad uri: " + uri);
            }

            msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
            threadId = c.getLong(PDU_COLUMN_THREAD_ID);

            set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet();
            for (Entry<Integer, Integer> e : set) {
                setEncodedStringValueToHeaders(
                        c, e.getValue(), headers, e.getKey());
            }

            set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet();
            for (Entry<Integer, Integer> e : set) {
                setTextStringToHeaders(
                        c, e.getValue(), headers, e.getKey());
            }

            set = OCTET_COLUMN_INDEX_MAP.entrySet();
            for (Entry<Integer, Integer> e : set) {
                setOctetToHeaders(
                        c, e.getValue(), headers, e.getKey());
            }

            set = LONG_COLUMN_INDEX_MAP.entrySet();
            for (Entry<Integer, Integer> e : set) {
                setLongToHeaders(
                        c, e.getValue(), headers, e.getKey());
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }

        // Check whether 'msgId' has been assigned a valid value.
        if (msgId == -1L) {
            throw new MmsException("Error! ID of the message: -1.");
        }

        // Load address information of the MM.
        loadAddress(msgId, headers);

        int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
        PduBody body = new PduBody();

        // For PDU which type is M_retrieve.conf or Send.req, we should
        // load multiparts and put them into the body of the PDU.
        if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
            PduPart[] parts = loadParts(msgId);
            if (parts != null) {
                int partsNum = parts.length;
                for (int i = 0; i < partsNum; i++) {
                    body.addPart(parts[i]);
                }
            }
        }

        GenericPdu pdu = null;
        switch (msgType) {
            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
                pdu = new NotificationInd(headers);
                break;
            case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
                pdu = new DeliveryInd(headers);
                break;
            case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
                pdu = new ReadOrigInd(headers);
                break;
            case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
                pdu = new RetrieveConf(headers, body);
                break;
            case PduHeaders.MESSAGE_TYPE_SEND_REQ:
                pdu = new SendReq(headers, body);
                break;
            case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
                pdu = new AcknowledgeInd(headers);
                break;
            case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
                pdu = new NotifyRespInd(headers);
                break;
            case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
                pdu = new ReadRecInd(headers);
                break;
            case PduHeaders.MESSAGE_TYPE_SEND_CONF:
            case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
            case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
            case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
            case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
            case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
            case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
            case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
            case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
            case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
            case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
            case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
                throw new MmsException(
                        "Unsupported PDU type: " + Integer.toHexString(msgType));

            default:
                throw new MmsException(
                        "Unrecognized PDU type: " + Integer.toHexString(msgType));
        }

        cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
        PDU_CACHE_INSTANCE.put(uri, cacheEntry);
        return pdu;
    
private voidloadAddress(long msgId, PduHeaders headers)

        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
                Uri.parse("content://mms/" + msgId + "/addr"),
                new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE },
                null, null, null);

        if (c != null) {
            try {
                while (c.moveToNext()) {
                    String addr = c.getString(0);
                    if (!TextUtils.isEmpty(addr)) {
                        int addrType = c.getInt(2);
                        switch (addrType) {
                            case PduHeaders.FROM:
                                headers.setEncodedStringValue(
                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
                                        addrType);
                                break;
                            case PduHeaders.TO:
                            case PduHeaders.CC:
                            case PduHeaders.BCC:
                                headers.appendEncodedStringValue(
                                        new EncodedStringValue(c.getInt(1), getBytes(addr)),
                                        addrType);
                                break;
                            default:
                                Log.e(TAG, "Unknown address type: " + addrType);
                                break;
                        }
                    }
                }
            } finally {
                c.close();
            }
        }
    
private PduPart[]loadParts(long msgId)

        Cursor c = SqliteWrapper.query(mContext, mContentResolver,
                Uri.parse("content://mms/" + msgId + "/part"),
                PART_PROJECTION, null, null, null);

        PduPart[] parts = null;

        try {
            if ((c == null) || (c.getCount() == 0)) {
                if (LOCAL_LOGV) {
                    Log.v(TAG, "loadParts(" + msgId + "): no part to load.");
                }
                return null;
            }

            int partCount = c.getCount();
            int partIdx = 0;
            parts = new PduPart[partCount];
            while (c.moveToNext()) {
                PduPart part = new PduPart();
                Integer charset = getIntegerFromPartColumn(
                        c, PART_COLUMN_CHARSET);
                if (charset != null) {
                    part.setCharset(charset);
                }

                byte[] contentDisposition = getByteArrayFromPartColumn(
                        c, PART_COLUMN_CONTENT_DISPOSITION);
                if (contentDisposition != null) {
                    part.setContentDisposition(contentDisposition);
                }

                byte[] contentId = getByteArrayFromPartColumn(
                        c, PART_COLUMN_CONTENT_ID);
                if (contentId != null) {
                    part.setContentId(contentId);
                }

                byte[] contentLocation = getByteArrayFromPartColumn(
                        c, PART_COLUMN_CONTENT_LOCATION);
                if (contentLocation != null) {
                    part.setContentLocation(contentLocation);
                }

                byte[] contentType = getByteArrayFromPartColumn(
                        c, PART_COLUMN_CONTENT_TYPE);
                if (contentType != null) {
                    part.setContentType(contentType);
                } else {
                    throw new MmsException("Content-Type must be set.");
                }

                byte[] fileName = getByteArrayFromPartColumn(
                        c, PART_COLUMN_FILENAME);
                if (fileName != null) {
                    part.setFilename(fileName);
                }

                byte[] name = getByteArrayFromPartColumn(
                        c, PART_COLUMN_NAME);
                if (name != null) {
                    part.setName(name);
                }

                // Construct a Uri for this part.
                long partId = c.getLong(PART_COLUMN_ID);
                Uri partURI = Uri.parse("content://mms/part/" + partId);
                part.setDataUri(partURI);

                // For images/audio/video, we won't keep their data in Part
                // because their renderer accept Uri as source.
                String type = toIsoString(contentType);
                if (!ContentType.isImageType(type)
                        && !ContentType.isAudioType(type)
                        && !ContentType.isVideoType(type)) {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    InputStream is = null;

                    try {
                        is = mContentResolver.openInputStream(partURI);

                        byte[] buffer = new byte[256];
                        int len = is.read(buffer);
                        while (len >= 0) {
                            baos.write(buffer, 0, len);
                            len = is.read(buffer);
                        }
                    } catch (IOException e) {
                        Log.e(TAG, "Failed to load part data", e);
                        c.close();
                        throw new MmsException(e);
                    } finally {
                        if (is != null) {
                            try {
                                is.close();
                            } catch (IOException e) {
                                Log.e(TAG, "Failed to close stream", e);
                            } // Ignore
                        }
                    }
                    part.setData(baos.toByteArray());
                }
                parts[partIdx++] = part;
            }
        } finally {
            if (c != null) {
                c.close();
            }
        }

        return parts;
    
public android.net.Urimove(android.net.Uri from, android.net.Uri to)
Move a PDU object from one location to another.

param
from Specify the PDU object to be moved.
param
to The destination location, should be one of the following: "content://mms/inbox", "content://mms/sent", "content://mms/drafts", "content://mms/outbox", "content://mms/trash".
return
New Uri of the moved PDU.
throws
MmsException Error occurred while moving the message.

        // Check whether the 'msgId' has been assigned a valid value.
        long msgId = ContentUris.parseId(from);
        if (msgId == -1L) {
            throw new MmsException("Error! ID of the message: -1.");
        }

        // Get corresponding int value of destination box.
        Integer msgBox = MESSAGE_BOX_MAP.get(to);
        if (msgBox == null) {
            throw new MmsException(
                    "Bad destination, must be one of "
                    + "content://mms/inbox, content://mms/sent, "
                    + "content://mms/drafts, content://mms/outbox, "
                    + "content://mms/temp.");
        }

        ContentValues values = new ContentValues(1);
        values.put(Mms.MESSAGE_BOX, msgBox);
        SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
        return ContentUris.withAppendedId(to, msgId);
    
public android.net.Uripersist(GenericPdu pdu, android.net.Uri uri)
Persist a PDU object to specific location in the storage.

param
pdu The PDU object to be stored.
param
uri Where to store the given PDU object.
return
A Uri which can be used to access the stored PDU.

        if (uri == null) {
            throw new MmsException("Uri may not be null.");
        }

        Integer msgBox = MESSAGE_BOX_MAP.get(uri);
        if (msgBox == null) {
            throw new MmsException(
                    "Bad destination, must be one of "
                    + "content://mms/inbox, content://mms/sent, "
                    + "content://mms/drafts, content://mms/outbox, "
                    + "content://mms/temp.");
        }

        PduHeaders header = pdu.getPduHeaders();
        PduBody body = null;
        ContentValues values = new ContentValues();
        Set<Entry<Integer, String>> set;

        set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet();
        for (Entry<Integer, String> e : set) {
            int field = e.getKey();
            EncodedStringValue encodedString = header.getEncodedStringValue(field);
            if (encodedString != null) {
                String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
                values.put(e.getValue(), toIsoString(encodedString.getTextString()));
                values.put(charsetColumn, encodedString.getCharacterSet());
            }
        }

        set = TEXT_STRING_COLUMN_NAME_MAP.entrySet();
        for (Entry<Integer, String> e : set){
            byte[] text = header.getTextString(e.getKey());
            if (text != null) {
                values.put(e.getValue(), toIsoString(text));
            }
        }

        set = OCTET_COLUMN_NAME_MAP.entrySet();
        for (Entry<Integer, String> e : set){
            int b = header.getOctet(e.getKey());
            if (b != 0) {
                values.put(e.getValue(), b);
            }
        }

        set = LONG_COLUMN_NAME_MAP.entrySet();
        for (Entry<Integer, String> e : set){
            long l = header.getLongInteger(e.getKey());
            if (l != -1L) {
                values.put(e.getValue(), l);
            }
        }

        HashMap<Integer, EncodedStringValue[]> addressMap =
                new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length);
        // Save address information.
        for (int addrType : ADDRESS_FIELDS) {
            EncodedStringValue[] array = null;
            if (addrType == PduHeaders.FROM) {
                EncodedStringValue v = header.getEncodedStringValue(addrType);
                if (v != null) {
                    array = new EncodedStringValue[1];
                    array[0] = v;
                }
            } else {
                array = header.getEncodedStringValues(addrType);
            }
            addressMap.put(addrType, array);
        }

        HashSet<String> recipients = new HashSet<String>();
        long threadId = DUMMY_THREAD_ID;
        int msgType = pdu.getMessageType();
        // Here we only allocate thread ID for M-Notification.ind,
        // M-Retrieve.conf and M-Send.req.
        // Some of other PDU types may be allocated a thread ID outside
        // this scope.
        if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
                || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
                || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
            EncodedStringValue[] array = null;
            switch (msgType) {
                case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
                case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
                    array = addressMap.get(PduHeaders.FROM);
                    break;
                case PduHeaders.MESSAGE_TYPE_SEND_REQ:
                    array = addressMap.get(PduHeaders.TO);
                    break;
            }

            if (array != null) {
                for (EncodedStringValue v : array) {
                    if (v != null) {
                        recipients.add(v.getString());
                    }
                }
            }
            threadId = Threads.getOrCreateThreadId(mContext, recipients);
        }
        values.put(Mms.THREAD_ID, threadId);

        // Save parts first to avoid inconsistent message is loaded
        // while saving the parts.
        long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
        // Get body if the PDU is a RetrieveConf or SendReq.
        if (pdu instanceof MultimediaMessagePdu) {
            body = ((MultimediaMessagePdu) pdu).getBody();
            // Start saving parts if necessary.
            if (body != null) {
                int partsNum = body.getPartsNum();
                for (int i = 0; i < partsNum; i++) {
                    PduPart part = body.getPart(i);
                    persistPart(part, dummyId);
                }
            }
        }

        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
        if (res == null) {
            throw new MmsException("persist() failed: return null.");
        }

        // Get the real ID of the PDU and update all parts which were
        // saved with the dummy ID.
        long msgId = ContentUris.parseId(res);
        values = new ContentValues(1);
        values.put(Part.MSG_ID, msgId);
        SqliteWrapper.update(mContext, mContentResolver,
                             Uri.parse("content://mms/" + dummyId + "/part"),
                             values, null, null);
        // We should return the longest URI of the persisted PDU, for
        // example, if input URI is "content://mms/inbox" and the _ID of
        // persisted PDU is '8', we should return "content://mms/inbox/8"
        // instead of "content://mms/8".
        // FIXME: Should the MmsProvider be responsible for this???
        res = Uri.parse(uri + "/" + msgId);

        // Save address information.
        for (int addrType : ADDRESS_FIELDS) {
            EncodedStringValue[] array = addressMap.get(addrType);
            if (array != null) {
                persistAddress(msgId, addrType, array);
            }
        }

        return res;
    
private voidpersistAddress(long msgId, int type, EncodedStringValue[] array)

        ContentValues values = new ContentValues(3);

        for (EncodedStringValue addr : array) {
            values.clear(); // Clear all values first.
            values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
            values.put(Addr.CHARSET, addr.getCharacterSet());
            values.put(Addr.TYPE, type);

            Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
            SqliteWrapper.insert(mContext, mContentResolver, uri, values);
        }
    
private voidpersistData(PduPart part, android.net.Uri uri, java.lang.String contentType)
Save data of the part into storage. The source data may be given by a byte[] or a Uri. If it's a byte[], directly save it into storage, otherwise load source data from the dataUri and then save it. If the data is an image, we may scale down it according to user preference.

param
part The PDU part which contains data to be saved.
param
uri The URI of the part.
param
contentType The MIME type of the part.
throws
MmsException Cannot find source data or error occurred while saving the data.

        OutputStream os = null;
        InputStream is = null;

        try {
            os = mContentResolver.openOutputStream(uri);
            byte[] data = part.getData();
            if (data == null) {
                Uri dataUri = part.getDataUri();
                if ((dataUri == null) || (dataUri == uri)) {
                    Log.w(TAG, "Can't find data for this part.");
                    return;
                }
                is = mContentResolver.openInputStream(dataUri);
                
                if (LOCAL_LOGV) {
                    Log.v(TAG, "Saving data to: " + uri);
                }

                byte[] buffer = new byte[256];
                for (int len = 0; (len = is.read(buffer)) != -1; ) {
                    os.write(buffer, 0, len);
                }
            } else {
                if (LOCAL_LOGV) {
                    Log.v(TAG, "Saving data to: " + uri);
                }
                os.write(data);
            }
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Failed to open Input/Output stream.", e);
            throw new MmsException(e);
        } catch (IOException e) {
            Log.e(TAG, "Failed to read/write data.", e);
            throw new MmsException(e);
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException while closing: " + os, e);
                } // Ignore
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException while closing: " + is, e);
                } // Ignore
            }
        }
    
public android.net.UripersistPart(PduPart part, long msgId)

        Uri uri = Uri.parse("content://mms/" + msgId + "/part");
        ContentValues values = new ContentValues(8);

        int charset = part.getCharset();
        if (charset != 0 ) {
            values.put(Part.CHARSET, charset);
        }

        String contentType = null;
        if (part.getContentType() != null) {
            contentType = toIsoString(part.getContentType());
            values.put(Part.CONTENT_TYPE, contentType);
            // To ensure the SMIL part is always the first part.
            if (ContentType.APP_SMIL.equals(contentType)) {
                values.put(Part.SEQ, -1);
            }
        } else {
            throw new MmsException("MIME type of the part must be set.");
        }

        if (part.getFilename() != null) {
            String fileName = new String(part.getFilename());
            values.put(Part.FILENAME, fileName);
        }

        if (part.getName() != null) {
            String name = new String(part.getName());
            values.put(Part.NAME, name);
        }

        Object value = null;
        if (part.getContentDisposition() != null) {
            value = toIsoString(part.getContentDisposition());
            values.put(Part.CONTENT_DISPOSITION, (String) value);
        }

        if (part.getContentId() != null) {
            value = toIsoString(part.getContentId());
            values.put(Part.CONTENT_ID, (String) value);
        }

        if (part.getContentLocation() != null) {
            value = toIsoString(part.getContentLocation());
            values.put(Part.CONTENT_LOCATION, (String) value);
        }

        Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
        if (res == null) {
            throw new MmsException("Failed to persist part, return null.");
        }

        persistData(part, res, contentType);
        // After successfully store the data, we should update
        // the dataUri of the part.
        part.setDataUri(res);

        return res;
    
public voidrelease()
Remove all objects in the temporary path.

        Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
        SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
    
private voidsetEncodedStringValueToHeaders(android.database.Cursor c, int columnIndex, PduHeaders headers, int mapColumn)

        String s = c.getString(columnIndex);
        if ((s != null) && (s.length() > 0)) {
            int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
            int charset = c.getInt(charsetColumnIndex);
            EncodedStringValue value = new EncodedStringValue(
                    charset, getBytes(s));
            headers.setEncodedStringValue(value, mapColumn);
        }
    
private voidsetLongToHeaders(android.database.Cursor c, int columnIndex, PduHeaders headers, int mapColumn)

        if (!c.isNull(columnIndex)) {
            long l = c.getLong(columnIndex);
            headers.setLongInteger(l, mapColumn);
        }
    
private voidsetOctetToHeaders(android.database.Cursor c, int columnIndex, PduHeaders headers, int mapColumn)

        if (!c.isNull(columnIndex)) {
            int b = c.getInt(columnIndex);
            headers.setOctet(b, mapColumn);
        }
    
private voidsetTextStringToHeaders(android.database.Cursor c, int columnIndex, PduHeaders headers, int mapColumn)

        String s = c.getString(columnIndex);
        if (s != null) {
            headers.setTextString(getBytes(s), mapColumn);
        }
    
public static java.lang.StringtoIsoString(byte[] bytes)
Wrap a byte[] into a String.

        try {
            return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
        } catch (UnsupportedEncodingException e) {
            // Impossible to reach here!
            Log.e(TAG, "ISO_8859_1 must be supported!", e);
            return "";
        }
    
private voidupdateAddress(long msgId, int type, EncodedStringValue[] array)

        // Delete old address information and then insert new ones.
        SqliteWrapper.delete(mContext, mContentResolver,
                Uri.parse("content://mms/" + msgId + "/addr"),
                Addr.TYPE + "=" + type, null);

        persistAddress(msgId, type, array);
    
public voidupdateHeaders(android.net.Uri uri, SendReq sendReq)
Update headers of a SendReq.

param
uri The PDU which need to be updated.
param
pdu New headers.
throws
MmsException Bad URI or updating failed.

        PDU_CACHE_INSTANCE.purge(uri);

        ContentValues values = new ContentValues(9);
        byte[] contentType = sendReq.getContentType();
        if (contentType != null) {
            values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
        }

        long date = sendReq.getDate();
        if (date != -1) {
            values.put(Mms.DATE, date);
        }

        int deliveryReport = sendReq.getDeliveryReport();
        if (deliveryReport != 0) {
            values.put(Mms.DELIVERY_REPORT, deliveryReport);
        }

        long expiry = sendReq.getExpiry();
        if (expiry != -1) {
            values.put(Mms.EXPIRY, expiry);
        }

        byte[] msgClass = sendReq.getMessageClass();
        if (msgClass != null) {
            values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
        }

        int priority = sendReq.getPriority();
        if (priority != 0) {
            values.put(Mms.PRIORITY, priority);
        }

        int readReport = sendReq.getReadReport();
        if (readReport != 0) {
            values.put(Mms.READ_REPORT, readReport);
        }

        byte[] transId = sendReq.getTransactionId();
        if (transId != null) {
            values.put(Mms.TRANSACTION_ID, toIsoString(transId));
        }

        EncodedStringValue subject = sendReq.getSubject();
        if (subject != null) {
            values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
            values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
        }

        PduHeaders headers = sendReq.getPduHeaders();
        HashSet<String> recipients = new HashSet<String>();
        for (int addrType : ADDRESS_FIELDS) {
            EncodedStringValue[] array = null;
            if (addrType == PduHeaders.FROM) {
                EncodedStringValue v = headers.getEncodedStringValue(addrType);
                if (v != null) {
                    array = new EncodedStringValue[1];
                    array[0] = v;
                }
            } else {
                array = headers.getEncodedStringValues(addrType);
            }

            if (array != null) {
                long msgId = ContentUris.parseId(uri);
                updateAddress(msgId, addrType, array);
                if (addrType == PduHeaders.TO) {
                    for (EncodedStringValue v : array) {
                        if (v != null) {
                            recipients.add(v.getString());
                        }
                    }
                }
            }
        }

        long threadId = Threads.getOrCreateThreadId(mContext, recipients);
        values.put(Mms.THREAD_ID, threadId);

        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
    
private voidupdatePart(android.net.Uri uri, PduPart part)

        ContentValues values = new ContentValues(7);

        int charset = part.getCharset();
        if (charset != 0 ) {
            values.put(Part.CHARSET, charset);
        }

        String contentType = null;
        if (part.getContentType() != null) {
            contentType = toIsoString(part.getContentType());
            values.put(Part.CONTENT_TYPE, contentType);
        } else {
            throw new MmsException("MIME type of the part must be set.");
        }

        if (part.getFilename() != null) {
            String fileName = new String(part.getFilename());
            values.put(Part.FILENAME, fileName);
        }

        if (part.getName() != null) {
            String name = new String(part.getName());
            values.put(Part.NAME, name);
        }

        Object value = null;
        if (part.getContentDisposition() != null) {
            value = toIsoString(part.getContentDisposition());
            values.put(Part.CONTENT_DISPOSITION, (String) value);
        }

        if (part.getContentId() != null) {
            value = toIsoString(part.getContentId());
            values.put(Part.CONTENT_ID, (String) value);
        }

        if (part.getContentLocation() != null) {
            value = toIsoString(part.getContentLocation());
            values.put(Part.CONTENT_LOCATION, (String) value);
        }

        SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);

        // Only update the data when:
        // 1. New binary data supplied or
        // 2. The Uri of the part is different from the current one.
        if ((part.getData() != null)
                || (uri != part.getDataUri())) {
            persistData(part, uri, contentType);
        }
    
public voidupdateParts(android.net.Uri uri, PduBody body)
Update all parts of a PDU.

param
uri The PDU which need to be updated.
param
body New message body of the PDU.
throws
MmsException Bad URI or updating failed.

        PduCacheEntry cacheEntry = PDU_CACHE_INSTANCE.get(uri);
        if (cacheEntry != null) {
            ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
        }

        ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
        HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>();

        int partsNum = body.getPartsNum();
        StringBuilder filter = new StringBuilder().append('(");
        for (int i = 0; i < partsNum; i++) {
            PduPart part = body.getPart(i);
            Uri partUri = part.getDataUri();
            if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) {
                toBeCreated.add(part);
            } else {
                toBeUpdated.put(partUri, part);

                // Don't use 'i > 0' to determine whether we should append
                // 'AND' since 'i = 0' may be skipped in another branch.
                if (filter.length() > 1) {
                    filter.append(" AND ");
                }

                filter.append(Part._ID);
                filter.append("!=");
                DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
            }
        }
        filter.append(')");

        long msgId = ContentUris.parseId(uri);

        // Remove the parts which doesn't exist anymore.
        SqliteWrapper.delete(mContext, mContentResolver,
                Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
                filter.length() > 2 ? filter.toString() : null, null);

        // Create new parts which didn't exist before.
        for (PduPart part : toBeCreated) {
            persistPart(part, msgId);
        }

        // Update the modified parts.
        for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
            updatePart(e.getKey(), e.getValue());
        }