Home | History | Annotate | Download | only in pdu
      1 /*
      2  * Copyright (C) 2007-2008 Esmertec AG.
      3  * Copyright (C) 2007-2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.messaging.mmslib.pdu;
     19 
     20 import android.content.ContentResolver;
     21 import android.content.ContentUris;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.database.Cursor;
     25 import android.database.DatabaseUtils;
     26 import android.database.sqlite.SQLiteException;
     27 import android.net.Uri;
     28 import android.provider.MediaStore;
     29 import android.provider.Telephony.Mms;
     30 import android.provider.Telephony.Mms.Addr;
     31 import android.provider.Telephony.Mms.Part;
     32 import android.provider.Telephony.MmsSms;
     33 import android.provider.Telephony.MmsSms.PendingMessages;
     34 import android.support.v4.util.ArrayMap;
     35 import android.support.v4.util.SimpleArrayMap;
     36 import android.telephony.PhoneNumberUtils;
     37 import android.text.TextUtils;
     38 import android.util.Log;
     39 import android.util.SparseArray;
     40 import android.util.SparseIntArray;
     41 
     42 import com.android.messaging.datamodel.data.ParticipantData;
     43 import com.android.messaging.mmslib.InvalidHeaderValueException;
     44 import com.android.messaging.mmslib.MmsException;
     45 import com.android.messaging.mmslib.SqliteWrapper;
     46 import com.android.messaging.mmslib.util.DownloadDrmHelper;
     47 import com.android.messaging.mmslib.util.DrmConvertSession;
     48 import com.android.messaging.mmslib.util.PduCache;
     49 import com.android.messaging.mmslib.util.PduCacheEntry;
     50 import com.android.messaging.sms.MmsSmsUtils;
     51 import com.android.messaging.util.Assert;
     52 import com.android.messaging.util.ContentType;
     53 import com.android.messaging.util.LogUtil;
     54 import com.android.messaging.util.OsUtil;
     55 
     56 import java.io.ByteArrayOutputStream;
     57 import java.io.File;
     58 import java.io.FileNotFoundException;
     59 import java.io.IOException;
     60 import java.io.InputStream;
     61 import java.io.OutputStream;
     62 import java.io.UnsupportedEncodingException;
     63 import java.util.ArrayList;
     64 import java.util.HashSet;
     65 import java.util.Map;
     66 
     67 /**
     68  * This class is the high-level manager of PDU storage.
     69  */
     70 public class PduPersister {
     71     private static final String TAG = "PduPersister";
     72     private static final boolean LOCAL_LOGV = false;
     73 
     74     /**
     75      * The uri of temporary drm objects.
     76      */
     77     public static final String TEMPORARY_DRM_OBJECT_URI =
     78             "content://mms/" + Long.MAX_VALUE + "/part";
     79 
     80     /**
     81      * Indicate that we transiently failed to process a MM.
     82      */
     83     public static final int PROC_STATUS_TRANSIENT_FAILURE = 1;
     84 
     85     /**
     86      * Indicate that we permanently failed to process a MM.
     87      */
     88     public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2;
     89 
     90     /**
     91      * Indicate that we have successfully processed a MM.
     92      */
     93     public static final int PROC_STATUS_COMPLETED = 3;
     94 
     95     public static final String BEGIN_VCARD = "BEGIN:VCARD";
     96 
     97     private static PduPersister sPersister;
     98 
     99     private static final PduCache PDU_CACHE_INSTANCE;
    100 
    101     private static final int[] ADDRESS_FIELDS = new int[]{
    102             PduHeaders.BCC,
    103             PduHeaders.CC,
    104             PduHeaders.FROM,
    105             PduHeaders.TO
    106     };
    107 
    108     public static final String[] PDU_PROJECTION = new String[]{
    109             Mms._ID,
    110             Mms.MESSAGE_BOX,
    111             Mms.THREAD_ID,
    112             Mms.RETRIEVE_TEXT,
    113             Mms.SUBJECT,
    114             Mms.CONTENT_LOCATION,
    115             Mms.CONTENT_TYPE,
    116             Mms.MESSAGE_CLASS,
    117             Mms.MESSAGE_ID,
    118             Mms.RESPONSE_TEXT,
    119             Mms.TRANSACTION_ID,
    120             Mms.CONTENT_CLASS,
    121             Mms.DELIVERY_REPORT,
    122             Mms.MESSAGE_TYPE,
    123             Mms.MMS_VERSION,
    124             Mms.PRIORITY,
    125             Mms.READ_REPORT,
    126             Mms.READ_STATUS,
    127             Mms.REPORT_ALLOWED,
    128             Mms.RETRIEVE_STATUS,
    129             Mms.STATUS,
    130             Mms.DATE,
    131             Mms.DELIVERY_TIME,
    132             Mms.EXPIRY,
    133             Mms.MESSAGE_SIZE,
    134             Mms.SUBJECT_CHARSET,
    135             Mms.RETRIEVE_TEXT_CHARSET,
    136             Mms.READ,
    137             Mms.SEEN,
    138     };
    139 
    140     public static final int PDU_COLUMN_ID                    = 0;
    141     public static final int PDU_COLUMN_MESSAGE_BOX           = 1;
    142     public static final int PDU_COLUMN_THREAD_ID             = 2;
    143     public static final int PDU_COLUMN_RETRIEVE_TEXT         = 3;
    144     public static final int PDU_COLUMN_SUBJECT               = 4;
    145     public static final int PDU_COLUMN_CONTENT_LOCATION      = 5;
    146     public static final int PDU_COLUMN_CONTENT_TYPE          = 6;
    147     public static final int PDU_COLUMN_MESSAGE_CLASS         = 7;
    148     public static final int PDU_COLUMN_MESSAGE_ID            = 8;
    149     public static final int PDU_COLUMN_RESPONSE_TEXT         = 9;
    150     public static final int PDU_COLUMN_TRANSACTION_ID        = 10;
    151     public static final int PDU_COLUMN_CONTENT_CLASS         = 11;
    152     public static final int PDU_COLUMN_DELIVERY_REPORT       = 12;
    153     public static final int PDU_COLUMN_MESSAGE_TYPE          = 13;
    154     public static final int PDU_COLUMN_MMS_VERSION           = 14;
    155     public static final int PDU_COLUMN_PRIORITY              = 15;
    156     public static final int PDU_COLUMN_READ_REPORT           = 16;
    157     public static final int PDU_COLUMN_READ_STATUS           = 17;
    158     public static final int PDU_COLUMN_REPORT_ALLOWED        = 18;
    159     public static final int PDU_COLUMN_RETRIEVE_STATUS       = 19;
    160     public static final int PDU_COLUMN_STATUS                = 20;
    161     public static final int PDU_COLUMN_DATE                  = 21;
    162     public static final int PDU_COLUMN_DELIVERY_TIME         = 22;
    163     public static final int PDU_COLUMN_EXPIRY                = 23;
    164     public static final int PDU_COLUMN_MESSAGE_SIZE          = 24;
    165     public static final int PDU_COLUMN_SUBJECT_CHARSET       = 25;
    166     public static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26;
    167     public static final int PDU_COLUMN_READ                  = 27;
    168     public static final int PDU_COLUMN_SEEN                  = 28;
    169 
    170     private static final String[] PART_PROJECTION = new String[] {
    171             Part._ID,
    172             Part.CHARSET,
    173             Part.CONTENT_DISPOSITION,
    174             Part.CONTENT_ID,
    175             Part.CONTENT_LOCATION,
    176             Part.CONTENT_TYPE,
    177             Part.FILENAME,
    178             Part.NAME,
    179             Part.TEXT
    180     };
    181 
    182     private static final int PART_COLUMN_ID                  = 0;
    183     private static final int PART_COLUMN_CHARSET             = 1;
    184     private static final int PART_COLUMN_CONTENT_DISPOSITION = 2;
    185     private static final int PART_COLUMN_CONTENT_ID          = 3;
    186     private static final int PART_COLUMN_CONTENT_LOCATION    = 4;
    187     private static final int PART_COLUMN_CONTENT_TYPE        = 5;
    188     private static final int PART_COLUMN_FILENAME            = 6;
    189     private static final int PART_COLUMN_NAME                = 7;
    190     private static final int PART_COLUMN_TEXT                = 8;
    191 
    192     private static final SimpleArrayMap<Uri, Integer> MESSAGE_BOX_MAP;
    193 
    194     // These map are used for convenience in persist() and load().
    195     private static final SparseIntArray CHARSET_COLUMN_INDEX_MAP;
    196 
    197     private static final SparseIntArray ENCODED_STRING_COLUMN_INDEX_MAP;
    198 
    199     private static final SparseIntArray TEXT_STRING_COLUMN_INDEX_MAP;
    200 
    201     private static final SparseIntArray OCTET_COLUMN_INDEX_MAP;
    202 
    203     private static final SparseIntArray LONG_COLUMN_INDEX_MAP;
    204 
    205     private static final SparseArray<String> CHARSET_COLUMN_NAME_MAP;
    206 
    207     private static final SparseArray<String> ENCODED_STRING_COLUMN_NAME_MAP;
    208 
    209     private static final SparseArray<String> TEXT_STRING_COLUMN_NAME_MAP;
    210 
    211     private static final SparseArray<String> OCTET_COLUMN_NAME_MAP;
    212 
    213     private static final SparseArray<String> LONG_COLUMN_NAME_MAP;
    214 
    215     static {
    216         MESSAGE_BOX_MAP = new SimpleArrayMap<Uri, Integer>();
    217         MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX);
    218         MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT);
    219         MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS);
    220         MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
    221 
    222         CHARSET_COLUMN_INDEX_MAP = new SparseIntArray();
    223         CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
    224         CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET);
    225 
    226         CHARSET_COLUMN_NAME_MAP = new SparseArray<String>();
    227         CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
    228         CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET);
    229 
    230         // Encoded string field code -> column index/name map.
    231         ENCODED_STRING_COLUMN_INDEX_MAP = new SparseIntArray();
    232         ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
    233         ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
    234 
    235         ENCODED_STRING_COLUMN_NAME_MAP = new SparseArray<String>();
    236         ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
    237         ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT);
    238 
    239         // Text string field code -> column index/name map.
    240         TEXT_STRING_COLUMN_INDEX_MAP = new SparseIntArray();
    241         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
    242         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
    243         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
    244         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
    245         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
    246         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID);
    247 
    248         TEXT_STRING_COLUMN_NAME_MAP = new SparseArray<String>();
    249         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
    250         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
    251         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
    252         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
    253         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
    254         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID);
    255 
    256         // Octet field code -> column index/name map.
    257         OCTET_COLUMN_INDEX_MAP = new SparseIntArray();
    258         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
    259         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
    260         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
    261         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
    262         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
    263         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
    264         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
    265         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
    266         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
    267         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
    268 
    269         OCTET_COLUMN_NAME_MAP = new SparseArray<String>();
    270         OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
    271         OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
    272         OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
    273         OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
    274         OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
    275         OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
    276         OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
    277         OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
    278         OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
    279         OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS);
    280 
    281         // Long field code -> column index/name map.
    282         LONG_COLUMN_INDEX_MAP = new SparseIntArray();
    283         LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
    284         LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
    285         LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
    286         LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
    287 
    288         LONG_COLUMN_NAME_MAP = new SparseArray<String>();
    289         LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
    290         LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
    291         LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
    292         LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE);
    293 
    294         PDU_CACHE_INSTANCE = PduCache.getInstance();
    295     }
    296 
    297     private final Context mContext;
    298 
    299     private final ContentResolver mContentResolver;
    300 
    301     private PduPersister(final Context context) {
    302         mContext = context;
    303         mContentResolver = context.getContentResolver();
    304     }
    305 
    306     /** Get(or create if not exist) an instance of PduPersister */
    307     public static PduPersister getPduPersister(final Context context) {
    308         if ((sPersister == null) || !context.equals(sPersister.mContext)) {
    309             sPersister = new PduPersister(context);
    310         }
    311         if (LOCAL_LOGV) {
    312             LogUtil.v(TAG, "PduPersister getPduPersister");
    313         }
    314 
    315         return sPersister;
    316     }
    317 
    318     private void setEncodedStringValueToHeaders(
    319             final Cursor c, final int columnIndex,
    320             final PduHeaders headers, final int mapColumn) {
    321         final String s = c.getString(columnIndex);
    322         if ((s != null) && (s.length() > 0)) {
    323             final int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn);
    324             final int charset = c.getInt(charsetColumnIndex);
    325             final EncodedStringValue value = new EncodedStringValue(
    326                     charset, getBytes(s));
    327             headers.setEncodedStringValue(value, mapColumn);
    328         }
    329     }
    330 
    331     private void setTextStringToHeaders(
    332             final Cursor c, final int columnIndex,
    333             final PduHeaders headers, final int mapColumn) {
    334         final String s = c.getString(columnIndex);
    335         if (s != null) {
    336             headers.setTextString(getBytes(s), mapColumn);
    337         }
    338     }
    339 
    340     private void setOctetToHeaders(
    341             final Cursor c, final int columnIndex,
    342             final PduHeaders headers, final int mapColumn) throws InvalidHeaderValueException {
    343         if (!c.isNull(columnIndex)) {
    344             final int b = c.getInt(columnIndex);
    345             headers.setOctet(b, mapColumn);
    346         }
    347     }
    348 
    349     private void setLongToHeaders(
    350             final Cursor c, final int columnIndex,
    351             final PduHeaders headers, final int mapColumn) {
    352         if (!c.isNull(columnIndex)) {
    353             final long l = c.getLong(columnIndex);
    354             headers.setLongInteger(l, mapColumn);
    355         }
    356     }
    357 
    358     private Integer getIntegerFromPartColumn(final Cursor c, final int columnIndex) {
    359         if (!c.isNull(columnIndex)) {
    360             return c.getInt(columnIndex);
    361         }
    362         return null;
    363     }
    364 
    365     private byte[] getByteArrayFromPartColumn(final Cursor c, final int columnIndex) {
    366         if (!c.isNull(columnIndex)) {
    367             return getBytes(c.getString(columnIndex));
    368         }
    369         return null;
    370     }
    371 
    372     private PduPart[] loadParts(final long msgId) throws MmsException {
    373         final Cursor c = SqliteWrapper.query(mContext, mContentResolver,
    374                 Uri.parse("content://mms/" + msgId + "/part"),
    375                 PART_PROJECTION, null, null, null);
    376 
    377         PduPart[] parts = null;
    378 
    379         try {
    380             if ((c == null) || (c.getCount() == 0)) {
    381                 if (LOCAL_LOGV) {
    382                     LogUtil.v(TAG, "loadParts(" + msgId + "): no part to load.");
    383                 }
    384                 return null;
    385             }
    386 
    387             final int partCount = c.getCount();
    388             int partIdx = 0;
    389             parts = new PduPart[partCount];
    390             while (c.moveToNext()) {
    391                 final PduPart part = new PduPart();
    392                 final Integer charset = getIntegerFromPartColumn(
    393                         c, PART_COLUMN_CHARSET);
    394                 if (charset != null) {
    395                     part.setCharset(charset);
    396                 }
    397 
    398                 final byte[] contentDisposition = getByteArrayFromPartColumn(
    399                         c, PART_COLUMN_CONTENT_DISPOSITION);
    400                 if (contentDisposition != null) {
    401                     part.setContentDisposition(contentDisposition);
    402                 }
    403 
    404                 final byte[] contentId = getByteArrayFromPartColumn(
    405                         c, PART_COLUMN_CONTENT_ID);
    406                 if (contentId != null) {
    407                     part.setContentId(contentId);
    408                 }
    409 
    410                 final byte[] contentLocation = getByteArrayFromPartColumn(
    411                         c, PART_COLUMN_CONTENT_LOCATION);
    412                 if (contentLocation != null) {
    413                     part.setContentLocation(contentLocation);
    414                 }
    415 
    416                 final byte[] contentType = getByteArrayFromPartColumn(
    417                         c, PART_COLUMN_CONTENT_TYPE);
    418                 if (contentType != null) {
    419                     part.setContentType(contentType);
    420                 } else {
    421                     throw new MmsException("Content-Type must be set.");
    422                 }
    423 
    424                 final byte[] fileName = getByteArrayFromPartColumn(
    425                         c, PART_COLUMN_FILENAME);
    426                 if (fileName != null) {
    427                     part.setFilename(fileName);
    428                 }
    429 
    430                 final byte[] name = getByteArrayFromPartColumn(
    431                         c, PART_COLUMN_NAME);
    432                 if (name != null) {
    433                     part.setName(name);
    434                 }
    435 
    436                 // Construct a Uri for this part.
    437                 final long partId = c.getLong(PART_COLUMN_ID);
    438                 final Uri partURI = Uri.parse("content://mms/part/" + partId);
    439                 part.setDataUri(partURI);
    440 
    441                 // For images/audio/video, we won't keep their data in Part
    442                 // because their renderer accept Uri as source.
    443                 final String type = toIsoString(contentType);
    444                 if (!ContentType.isImageType(type)
    445                         && !ContentType.isAudioType(type)
    446                         && !ContentType.isVideoType(type)) {
    447                     final ByteArrayOutputStream baos = new ByteArrayOutputStream();
    448                     InputStream is = null;
    449 
    450                     // Store simple string values directly in the database instead of an
    451                     // external file.  This makes the text searchable and retrieval slightly
    452                     // faster.
    453                     if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type)
    454                             || ContentType.TEXT_HTML.equals(type)) {
    455                         final String text = c.getString(PART_COLUMN_TEXT);
    456                         final byte[] blob = new EncodedStringValue(
    457                                 charset != null ? charset : CharacterSets.DEFAULT_CHARSET,
    458                                 text != null ? text : "")
    459                                 .getTextString();
    460                         baos.write(blob, 0, blob.length);
    461                     } else {
    462 
    463                         try {
    464                             is = mContentResolver.openInputStream(partURI);
    465 
    466                             final byte[] buffer = new byte[256];
    467                             int len = is.read(buffer);
    468                             while (len >= 0) {
    469                                 baos.write(buffer, 0, len);
    470                                 len = is.read(buffer);
    471                             }
    472                         } catch (final IOException e) {
    473                             Log.e(TAG, "Failed to load part data", e);
    474                             c.close();
    475                             throw new MmsException(e);
    476                         } finally {
    477                             if (is != null) {
    478                                 try {
    479                                     is.close();
    480                                 } catch (final IOException e) {
    481                                     Log.e(TAG, "Failed to close stream", e);
    482                                 } // Ignore
    483                             }
    484                         }
    485                     }
    486                     part.setData(baos.toByteArray());
    487                 }
    488                 parts[partIdx++] = part;
    489             }
    490         } finally {
    491             if (c != null) {
    492                 c.close();
    493             }
    494         }
    495 
    496         return parts;
    497     }
    498 
    499     private void loadAddress(final long msgId, final PduHeaders headers) {
    500         final Cursor c = SqliteWrapper.query(mContext, mContentResolver,
    501                 Uri.parse("content://mms/" + msgId + "/addr"),
    502                 new String[]{Addr.ADDRESS, Addr.CHARSET, Addr.TYPE},
    503                 null, null, null);
    504 
    505         if (c != null) {
    506             try {
    507                 while (c.moveToNext()) {
    508                     final String addr = c.getString(0);
    509                     if (!TextUtils.isEmpty(addr)) {
    510                         final int addrType = c.getInt(2);
    511                         switch (addrType) {
    512                             case PduHeaders.FROM:
    513                                 headers.setEncodedStringValue(
    514                                         new EncodedStringValue(c.getInt(1), getBytes(addr)),
    515                                         addrType);
    516                                 break;
    517                             case PduHeaders.TO:
    518                             case PduHeaders.CC:
    519                             case PduHeaders.BCC:
    520                                 headers.appendEncodedStringValue(
    521                                         new EncodedStringValue(c.getInt(1), getBytes(addr)),
    522                                         addrType);
    523                                 break;
    524                             default:
    525                                 Log.e(TAG, "Unknown address type: " + addrType);
    526                                 break;
    527                         }
    528                     }
    529                 }
    530             } finally {
    531                 c.close();
    532             }
    533         }
    534     }
    535 
    536     /**
    537      * Load a PDU from a given cursor
    538      *
    539      * @param c The cursor
    540      * @return A parsed PDU from the database row
    541      */
    542     public GenericPdu load(final Cursor c) throws MmsException {
    543         final PduHeaders headers = new PduHeaders();
    544         final long msgId = c.getLong(PDU_COLUMN_ID);
    545         // Fill in the headers from the PDU columns
    546         loadHeadersFromCursor(c, headers);
    547         // Load address information of the MM.
    548         loadAddress(msgId, headers);
    549         // Load parts for the PDU body
    550         final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
    551         final PduBody body = loadBody(msgId, msgType);
    552         return createPdu(msgType, headers, body);
    553     }
    554 
    555     /**
    556      * Load a PDU from storage by given Uri.
    557      *
    558      * @param uri            The Uri of the PDU to be loaded.
    559      * @return A generic PDU object, it may be cast to dedicated PDU.
    560      * @throws MmsException Failed to load some fields of a PDU.
    561      */
    562     public GenericPdu load(final Uri uri) throws MmsException {
    563         GenericPdu pdu = null;
    564         PduCacheEntry cacheEntry = null;
    565         int msgBox = 0;
    566         final long threadId = -1;
    567         try {
    568             synchronized (PDU_CACHE_INSTANCE) {
    569                 if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
    570                     if (LOCAL_LOGV) {
    571                         LogUtil.v(TAG, "load: " + uri + " blocked by isUpdating()");
    572                     }
    573                     try {
    574                         PDU_CACHE_INSTANCE.wait();
    575                     } catch (final InterruptedException e) {
    576                         Log.e(TAG, "load: ", e);
    577                     }
    578                 }
    579 
    580                 // Check if the pdu is already loaded
    581                 cacheEntry = PDU_CACHE_INSTANCE.get(uri);
    582                 if (cacheEntry != null) {
    583                     return cacheEntry.getPdu();
    584                 }
    585 
    586                 // Tell the cache to indicate to other callers that this item
    587                 // is currently being updated.
    588                 PDU_CACHE_INSTANCE.setUpdating(uri, true);
    589             }
    590 
    591             final Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri,
    592                     PDU_PROJECTION, null, null, null);
    593             final PduHeaders headers = new PduHeaders();
    594             final long msgId = ContentUris.parseId(uri);
    595 
    596             try {
    597                 if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) {
    598                     return null;  // MMS not found
    599                 }
    600 
    601                 msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX);
    602                 //threadId = c.getLong(PDU_COLUMN_THREAD_ID);
    603                 loadHeadersFromCursor(c, headers);
    604             } finally {
    605                 if (c != null) {
    606                     c.close();
    607                 }
    608             }
    609 
    610             // Check whether 'msgId' has been assigned a valid value.
    611             if (msgId == -1L) {
    612                 throw new MmsException("Error! ID of the message: -1.");
    613             }
    614 
    615             // Load address information of the MM.
    616             loadAddress(msgId, headers);
    617 
    618             final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE);
    619             final PduBody body = loadBody(msgId, msgType);
    620             pdu = createPdu(msgType, headers, body);
    621         } finally {
    622             synchronized (PDU_CACHE_INSTANCE) {
    623                 if (pdu != null) {
    624                     Assert.isNull(PDU_CACHE_INSTANCE.get(uri), "Pdu exists for " + uri);
    625                     // Update the cache entry with the real info
    626                     cacheEntry = new PduCacheEntry(pdu, msgBox, threadId);
    627                     PDU_CACHE_INSTANCE.put(uri, cacheEntry);
    628                 }
    629                 PDU_CACHE_INSTANCE.setUpdating(uri, false);
    630                 PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead
    631             }
    632         }
    633         return pdu;
    634     }
    635 
    636     private void loadHeadersFromCursor(final Cursor c, final PduHeaders headers)
    637             throws InvalidHeaderValueException {
    638         for (int i = ENCODED_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
    639             setEncodedStringValueToHeaders(
    640                     c, ENCODED_STRING_COLUMN_INDEX_MAP.valueAt(i), headers,
    641                     ENCODED_STRING_COLUMN_INDEX_MAP.keyAt(i));
    642         }
    643         for (int i = TEXT_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
    644             setTextStringToHeaders(
    645                     c, TEXT_STRING_COLUMN_INDEX_MAP.valueAt(i), headers,
    646                     TEXT_STRING_COLUMN_INDEX_MAP.keyAt(i));
    647         }
    648         for (int i = OCTET_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
    649             setOctetToHeaders(
    650                     c, OCTET_COLUMN_INDEX_MAP.valueAt(i), headers,
    651                     OCTET_COLUMN_INDEX_MAP.keyAt(i));
    652         }
    653         for (int i = LONG_COLUMN_INDEX_MAP.size(); --i >= 0; ) {
    654             setLongToHeaders(
    655                     c, LONG_COLUMN_INDEX_MAP.valueAt(i), headers,
    656                     LONG_COLUMN_INDEX_MAP.keyAt(i));
    657         }
    658     }
    659 
    660     private GenericPdu createPdu(final int msgType, final PduHeaders headers, final PduBody body)
    661             throws MmsException {
    662         switch (msgType) {
    663             case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
    664                 return new NotificationInd(headers);
    665             case PduHeaders.MESSAGE_TYPE_DELIVERY_IND:
    666                 return new DeliveryInd(headers);
    667             case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND:
    668                 return new ReadOrigInd(headers);
    669             case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
    670                 return new RetrieveConf(headers, body);
    671             case PduHeaders.MESSAGE_TYPE_SEND_REQ:
    672                 return new SendReq(headers, body);
    673             case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND:
    674                 return new AcknowledgeInd(headers);
    675             case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND:
    676                 return new NotifyRespInd(headers);
    677             case PduHeaders.MESSAGE_TYPE_READ_REC_IND:
    678                 return new ReadRecInd(headers);
    679             case PduHeaders.MESSAGE_TYPE_SEND_CONF:
    680             case PduHeaders.MESSAGE_TYPE_FORWARD_REQ:
    681             case PduHeaders.MESSAGE_TYPE_FORWARD_CONF:
    682             case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ:
    683             case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF:
    684             case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ:
    685             case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF:
    686             case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ:
    687             case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF:
    688             case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ:
    689             case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF:
    690             case PduHeaders.MESSAGE_TYPE_MBOX_DESCR:
    691             case PduHeaders.MESSAGE_TYPE_DELETE_REQ:
    692             case PduHeaders.MESSAGE_TYPE_DELETE_CONF:
    693             case PduHeaders.MESSAGE_TYPE_CANCEL_REQ:
    694             case PduHeaders.MESSAGE_TYPE_CANCEL_CONF:
    695                 throw new MmsException(
    696                         "Unsupported PDU type: " + Integer.toHexString(msgType));
    697 
    698             default:
    699                 throw new MmsException(
    700                         "Unrecognized PDU type: " + Integer.toHexString(msgType));
    701         }
    702     }
    703 
    704     private PduBody loadBody(final long msgId, final int msgType) throws MmsException {
    705         final PduBody body = new PduBody();
    706 
    707         // For PDU which type is M_retrieve.conf or Send.req, we should
    708         // load multiparts and put them into the body of the PDU.
    709         if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
    710                 || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
    711             final PduPart[] parts = loadParts(msgId);
    712             if (parts != null) {
    713                 final int partsNum = parts.length;
    714                 for (int i = 0; i < partsNum; i++) {
    715                     body.addPart(parts[i]);
    716                 }
    717             }
    718         }
    719 
    720         return body;
    721     }
    722 
    723     private void persistAddress(
    724             final long msgId, final int type, final EncodedStringValue[] array) {
    725         final ContentValues values = new ContentValues(3);
    726 
    727         for (final EncodedStringValue addr : array) {
    728             values.clear(); // Clear all values first.
    729             values.put(Addr.ADDRESS, toIsoString(addr.getTextString()));
    730             values.put(Addr.CHARSET, addr.getCharacterSet());
    731             values.put(Addr.TYPE, type);
    732 
    733             final Uri uri = Uri.parse("content://mms/" + msgId + "/addr");
    734             SqliteWrapper.insert(mContext, mContentResolver, uri, values);
    735         }
    736     }
    737 
    738     private static String getPartContentType(final PduPart part) {
    739         return part.getContentType() == null ? null : toIsoString(part.getContentType());
    740     }
    741 
    742     private static void getValues(final PduPart part, final ContentValues values) {
    743         byte[] bytes = part.getFilename();
    744         if (bytes != null) {
    745             values.put(Part.FILENAME, new String(bytes));
    746         }
    747 
    748         bytes = part.getName();
    749         if (bytes != null) {
    750             values.put(Part.NAME, new String(bytes));
    751         }
    752 
    753         bytes = part.getContentDisposition();
    754         if (bytes != null) {
    755             values.put(Part.CONTENT_DISPOSITION, toIsoString(bytes));
    756         }
    757 
    758         bytes = part.getContentId();
    759         if (bytes != null) {
    760             values.put(Part.CONTENT_ID, toIsoString(bytes));
    761         }
    762 
    763         bytes = part.getContentLocation();
    764         if (bytes != null) {
    765             values.put(Part.CONTENT_LOCATION, toIsoString(bytes));
    766         }
    767     }
    768 
    769     public Uri persistPart(final PduPart part, final long msgId,
    770             final Map<Uri, InputStream> preOpenedFiles) throws MmsException {
    771         final Uri uri = Uri.parse("content://mms/" + msgId + "/part");
    772         final ContentValues values = new ContentValues(8);
    773 
    774         final int charset = part.getCharset();
    775         if (charset != 0) {
    776             values.put(Part.CHARSET, charset);
    777         }
    778 
    779         String contentType = getPartContentType(part);
    780         final byte[] data = part.getData();
    781 
    782         if (LOCAL_LOGV) {
    783             LogUtil.v(TAG, "PduPersister.persistPart part: " + uri + " contentType: " +
    784                     contentType);
    785         }
    786 
    787         if (contentType != null) {
    788             // There is no "image/jpg" in Android (and it's an invalid mimetype).
    789             // Change it to "image/jpeg"
    790             if (ContentType.IMAGE_JPG.equals(contentType)) {
    791                 contentType = ContentType.IMAGE_JPEG;
    792             }
    793 
    794             // On somes phones, a vcard comes in as text/plain instead of text/v-card.
    795             // Fix it if necessary.
    796             if (ContentType.TEXT_PLAIN.equals(contentType) && data != null) {
    797                 // There might be a more efficient way to just check the beginning of the string
    798                 // without encoding the whole thing, but we're concerned that with various
    799                 // characters sets, just comparing the byte data to BEGIN_VCARD would not be
    800                 // reliable.
    801                 final String encodedDataString = new EncodedStringValue(charset, data).getString();
    802                 if (encodedDataString != null && encodedDataString.startsWith(BEGIN_VCARD)) {
    803                     contentType = ContentType.TEXT_VCARD;
    804                     part.setContentType(contentType.getBytes());
    805                     if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) {
    806                         LogUtil.d(TAG, "PduPersister.persistPart part: " + uri + " contentType: " +
    807                                 contentType + " changing to vcard");
    808                     }
    809                 }
    810             }
    811 
    812             values.put(Part.CONTENT_TYPE, contentType);
    813             // To ensure the SMIL part is always the first part.
    814             if (ContentType.APP_SMIL.equals(contentType)) {
    815                 values.put(Part.SEQ, -1);
    816             }
    817         } else {
    818             throw new MmsException("MIME type of the part must be set.");
    819         }
    820 
    821         getValues(part, values);
    822 
    823         Uri res = null;
    824 
    825         try {
    826             res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
    827         } catch (IllegalStateException e) {
    828             // Currently the MMS provider throws an IllegalStateException when it's out of space
    829             LogUtil.e(TAG, "SqliteWrapper.insert threw: ", e);
    830         }
    831 
    832         if (res == null) {
    833             throw new MmsException("Failed to persist part, return null.");
    834         }
    835 
    836         persistData(part, res, contentType, preOpenedFiles);
    837         // After successfully store the data, we should update
    838         // the dataUri of the part.
    839         part.setDataUri(res);
    840 
    841         return res;
    842     }
    843 
    844     /**
    845      * Save data of the part into storage. The source data may be given
    846      * by a byte[] or a Uri. If it's a byte[], directly save it
    847      * into storage, otherwise load source data from the dataUri and then
    848      * save it. If the data is an image, we may scale down it according
    849      * to user preference.
    850      *
    851      * @param part           The PDU part which contains data to be saved.
    852      * @param uri            The URI of the part.
    853      * @param contentType    The MIME type of the part.
    854      * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
    855      * @throws MmsException Cannot find source data or error occurred
    856      *                      while saving the data.
    857      */
    858     private void persistData(final PduPart part, final Uri uri,
    859             final String contentType, final Map<Uri, InputStream> preOpenedFiles)
    860             throws MmsException {
    861         OutputStream os = null;
    862         InputStream is = null;
    863         DrmConvertSession drmConvertSession = null;
    864         Uri dataUri = null;
    865         String path = null;
    866 
    867         try {
    868             final byte[] data = part.getData();
    869             final int charset = part.getCharset();
    870             if (ContentType.TEXT_PLAIN.equals(contentType)
    871                     || ContentType.APP_SMIL.equals(contentType)
    872                     || ContentType.TEXT_HTML.equals(contentType)) {
    873                 // Some phone could send MMS with a text part having empty data
    874                 // Let's just skip those parts.
    875                 // EncodedStringValue() throws NPE if data is empty
    876                 if (data != null) {
    877                     final ContentValues cv = new ContentValues();
    878                     cv.put(Mms.Part.TEXT, new EncodedStringValue(charset, data).getString());
    879                     if (mContentResolver.update(uri, cv, null, null) != 1) {
    880                         throw new MmsException("unable to update " + uri.toString());
    881                     }
    882                 }
    883             } else {
    884                 final boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType);
    885                 if (isDrm) {
    886                     if (uri != null) {
    887                         try {
    888                             path = convertUriToPath(mContext, uri);
    889                             if (LOCAL_LOGV) {
    890                                 LogUtil.v(TAG, "drm uri: " + uri + " path: " + path);
    891                             }
    892                             final File f = new File(path);
    893                             final long len = f.length();
    894                             if (LOCAL_LOGV) {
    895                                 LogUtil.v(TAG, "drm path: " + path + " len: " + len);
    896                             }
    897                             if (len > 0) {
    898                                 // we're not going to re-persist and re-encrypt an already
    899                                 // converted drm file
    900                                 return;
    901                             }
    902                         } catch (final Exception e) {
    903                             Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e);
    904                         }
    905                     }
    906                     // We haven't converted the file yet, start the conversion
    907                     drmConvertSession = DrmConvertSession.open(mContext, contentType);
    908                     if (drmConvertSession == null) {
    909                         throw new MmsException("Mimetype " + contentType +
    910                                 " can not be converted.");
    911                     }
    912                 }
    913                 // uri can look like:
    914                 // content://mms/part/98
    915                 os = mContentResolver.openOutputStream(uri);
    916                 if (os == null) {
    917                     throw new MmsException("Failed to create output stream on " + uri);
    918                 }
    919                 if (data == null) {
    920                     dataUri = part.getDataUri();
    921                     if ((dataUri == null) || (dataUri == uri)) {
    922                         Log.w(TAG, "Can't find data for this part.");
    923                         return;
    924                     }
    925                     // dataUri can look like:
    926                     // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715
    927                     if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) {
    928                         is = preOpenedFiles.get(dataUri);
    929                     }
    930                     if (is == null) {
    931                         is = mContentResolver.openInputStream(dataUri);
    932                     }
    933                     if (is == null) {
    934                         throw new MmsException("Failed to create input stream on " + dataUri);
    935                     }
    936                     if (LOCAL_LOGV) {
    937                         LogUtil.v(TAG, "Saving data to: " + uri);
    938                     }
    939 
    940                     final byte[] buffer = new byte[8192];
    941                     for (int len = 0; (len = is.read(buffer)) != -1; ) {
    942                         if (!isDrm) {
    943                             os.write(buffer, 0, len);
    944                         } else {
    945                             final byte[] convertedData = drmConvertSession.convert(buffer, len);
    946                             if (convertedData != null) {
    947                                 os.write(convertedData, 0, convertedData.length);
    948                             } else {
    949                                 throw new MmsException("Error converting drm data.");
    950                             }
    951                         }
    952                     }
    953                 } else {
    954                     if (LOCAL_LOGV) {
    955                         LogUtil.v(TAG, "Saving data to: " + uri);
    956                     }
    957                     if (!isDrm) {
    958                         os.write(data);
    959                     } else {
    960                         dataUri = uri;
    961                         final byte[] convertedData = drmConvertSession.convert(data, data.length);
    962                         if (convertedData != null) {
    963                             os.write(convertedData, 0, convertedData.length);
    964                         } else {
    965                             throw new MmsException("Error converting drm data.");
    966                         }
    967                     }
    968                 }
    969             }
    970         } catch (final SQLiteException e) {
    971             Log.e(TAG, "Failed with SQLiteException.", e);
    972             throw new MmsException(e);
    973         } catch (final FileNotFoundException e) {
    974             Log.e(TAG, "Failed to open Input/Output stream.", e);
    975             throw new MmsException(e);
    976         } catch (final IOException e) {
    977             Log.e(TAG, "Failed to read/write data.", e);
    978             throw new MmsException(e);
    979         } finally {
    980             if (os != null) {
    981                 try {
    982                     os.close();
    983                 } catch (final IOException e) {
    984                     Log.e(TAG, "IOException while closing: " + os, e);
    985                 } // Ignore
    986             }
    987             if (is != null) {
    988                 try {
    989                     is.close();
    990                 } catch (final IOException e) {
    991                     Log.e(TAG, "IOException while closing: " + is, e);
    992                 } // Ignore
    993             }
    994             if (drmConvertSession != null) {
    995                 drmConvertSession.close(path);
    996 
    997                 // Reset the permissions on the encrypted part file so everyone has only read
    998                 // permission.
    999                 final File f = new File(path);
   1000                 final ContentValues values = new ContentValues(0);
   1001                 SqliteWrapper.update(mContext, mContentResolver,
   1002                         Uri.parse("content://mms/resetFilePerm/" + f.getName()),
   1003                         values, null, null);
   1004             }
   1005         }
   1006     }
   1007 
   1008     /**
   1009      * This method expects uri in the following format
   1010      *     content://media/<table_name>/<row_index> (or)
   1011      *     file://sdcard/test.mp4
   1012      *     http://test.com/test.mp4
   1013      *
   1014      * Here <table_name> shall be "video" or "audio" or "images"
   1015      * <row_index> the index of the content in given table
   1016      */
   1017     public static String convertUriToPath(final Context context, final Uri uri) {
   1018         String path = null;
   1019         if (null != uri) {
   1020             final String scheme = uri.getScheme();
   1021             if (null == scheme || scheme.equals("") ||
   1022                     scheme.equals(ContentResolver.SCHEME_FILE)) {
   1023                 path = uri.getPath();
   1024 
   1025             } else if (scheme.equals("http")) {
   1026                 path = uri.toString();
   1027 
   1028             } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
   1029                 final String[] projection = new String[] {MediaStore.MediaColumns.DATA};
   1030                 Cursor cursor = null;
   1031                 try {
   1032                     cursor = context.getContentResolver().query(uri, projection, null,
   1033                             null, null);
   1034                     if (null == cursor || 0 == cursor.getCount() || !cursor.moveToFirst()) {
   1035                         throw new IllegalArgumentException("Given Uri could not be found" +
   1036                                 " in media store");
   1037                     }
   1038                     final int pathIndex =
   1039                             cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
   1040                     path = cursor.getString(pathIndex);
   1041                 } catch (final SQLiteException e) {
   1042                     throw new IllegalArgumentException("Given Uri is not formatted in a way " +
   1043                             "so that it can be found in media store.");
   1044                 } finally {
   1045                     if (null != cursor) {
   1046                         cursor.close();
   1047                     }
   1048                 }
   1049             } else {
   1050                 throw new IllegalArgumentException("Given Uri scheme is not supported");
   1051             }
   1052         }
   1053         return path;
   1054     }
   1055 
   1056     private void updateAddress(
   1057             final long msgId, final int type, final EncodedStringValue[] array) {
   1058         // Delete old address information and then insert new ones.
   1059         SqliteWrapper.delete(mContext, mContentResolver,
   1060                 Uri.parse("content://mms/" + msgId + "/addr"),
   1061                 Addr.TYPE + "=" + type, null);
   1062 
   1063         persistAddress(msgId, type, array);
   1064     }
   1065 
   1066     /**
   1067      * Update headers of a SendReq.
   1068      *
   1069      * @param uri The PDU which need to be updated.
   1070      * @param pdu New headers.
   1071      * @throws MmsException Bad URI or updating failed.
   1072      */
   1073     public void updateHeaders(final Uri uri, final SendReq sendReq) {
   1074         synchronized (PDU_CACHE_INSTANCE) {
   1075             // If the cache item is getting updated, wait until it's done updating before
   1076             // purging it.
   1077             if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
   1078                 if (LOCAL_LOGV) {
   1079                     LogUtil.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()");
   1080                 }
   1081                 try {
   1082                     PDU_CACHE_INSTANCE.wait();
   1083                 } catch (final InterruptedException e) {
   1084                     Log.e(TAG, "updateHeaders: ", e);
   1085                 }
   1086             }
   1087         }
   1088         PDU_CACHE_INSTANCE.purge(uri);
   1089 
   1090         final ContentValues values = new ContentValues(10);
   1091         final byte[] contentType = sendReq.getContentType();
   1092         if (contentType != null) {
   1093             values.put(Mms.CONTENT_TYPE, toIsoString(contentType));
   1094         }
   1095 
   1096         final long date = sendReq.getDate();
   1097         if (date != -1) {
   1098             values.put(Mms.DATE, date);
   1099         }
   1100 
   1101         final int deliveryReport = sendReq.getDeliveryReport();
   1102         if (deliveryReport != 0) {
   1103             values.put(Mms.DELIVERY_REPORT, deliveryReport);
   1104         }
   1105 
   1106         final long expiry = sendReq.getExpiry();
   1107         if (expiry != -1) {
   1108             values.put(Mms.EXPIRY, expiry);
   1109         }
   1110 
   1111         final byte[] msgClass = sendReq.getMessageClass();
   1112         if (msgClass != null) {
   1113             values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass));
   1114         }
   1115 
   1116         final int priority = sendReq.getPriority();
   1117         if (priority != 0) {
   1118             values.put(Mms.PRIORITY, priority);
   1119         }
   1120 
   1121         final int readReport = sendReq.getReadReport();
   1122         if (readReport != 0) {
   1123             values.put(Mms.READ_REPORT, readReport);
   1124         }
   1125 
   1126         final byte[] transId = sendReq.getTransactionId();
   1127         if (transId != null) {
   1128             values.put(Mms.TRANSACTION_ID, toIsoString(transId));
   1129         }
   1130 
   1131         final EncodedStringValue subject = sendReq.getSubject();
   1132         if (subject != null) {
   1133             values.put(Mms.SUBJECT, toIsoString(subject.getTextString()));
   1134             values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet());
   1135         } else {
   1136             values.put(Mms.SUBJECT, "");
   1137         }
   1138 
   1139         final long messageSize = sendReq.getMessageSize();
   1140         if (messageSize > 0) {
   1141             values.put(Mms.MESSAGE_SIZE, messageSize);
   1142         }
   1143 
   1144         final PduHeaders headers = sendReq.getPduHeaders();
   1145         final HashSet<String> recipients = new HashSet<String>();
   1146         for (final int addrType : ADDRESS_FIELDS) {
   1147             EncodedStringValue[] array = null;
   1148             if (addrType == PduHeaders.FROM) {
   1149                 final EncodedStringValue v = headers.getEncodedStringValue(addrType);
   1150                 if (v != null) {
   1151                     array = new EncodedStringValue[1];
   1152                     array[0] = v;
   1153                 }
   1154             } else {
   1155                 array = headers.getEncodedStringValues(addrType);
   1156             }
   1157 
   1158             if (array != null) {
   1159                 final long msgId = ContentUris.parseId(uri);
   1160                 updateAddress(msgId, addrType, array);
   1161                 if (addrType == PduHeaders.TO) {
   1162                     for (final EncodedStringValue v : array) {
   1163                         if (v != null) {
   1164                             recipients.add(v.getString());
   1165                         }
   1166                     }
   1167                 }
   1168             }
   1169         }
   1170         if (!recipients.isEmpty()) {
   1171             final long threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
   1172             values.put(Mms.THREAD_ID, threadId);
   1173         }
   1174 
   1175         SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
   1176     }
   1177 
   1178 
   1179     private void updatePart(final Uri uri, final PduPart part,
   1180             final Map<Uri, InputStream> preOpenedFiles)
   1181             throws MmsException {
   1182         final ContentValues values = new ContentValues(7);
   1183 
   1184         final int charset = part.getCharset();
   1185         if (charset != 0) {
   1186             values.put(Part.CHARSET, charset);
   1187         }
   1188 
   1189         String contentType = null;
   1190         if (part.getContentType() != null) {
   1191             contentType = toIsoString(part.getContentType());
   1192             values.put(Part.CONTENT_TYPE, contentType);
   1193         } else {
   1194             throw new MmsException("MIME type of the part must be set.");
   1195         }
   1196 
   1197         getValues(part, values);
   1198 
   1199         SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null);
   1200 
   1201         // Only update the data when:
   1202         // 1. New binary data supplied or
   1203         // 2. The Uri of the part is different from the current one.
   1204         if ((part.getData() != null)
   1205                 || (uri != part.getDataUri())) {
   1206             persistData(part, uri, contentType, preOpenedFiles);
   1207         }
   1208     }
   1209 
   1210     /**
   1211      * Update all parts of a PDU.
   1212      *
   1213      * @param uri            The PDU which need to be updated.
   1214      * @param body           New message body of the PDU.
   1215      * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts.
   1216      * @throws MmsException Bad URI or updating failed.
   1217      */
   1218     public void updateParts(final Uri uri, final PduBody body,
   1219             final Map<Uri, InputStream> preOpenedFiles)
   1220             throws MmsException {
   1221         try {
   1222             PduCacheEntry cacheEntry;
   1223             synchronized (PDU_CACHE_INSTANCE) {
   1224                 if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
   1225                     if (LOCAL_LOGV) {
   1226                         LogUtil.v(TAG, "updateParts: " + uri + " blocked by isUpdating()");
   1227                     }
   1228                     try {
   1229                         PDU_CACHE_INSTANCE.wait();
   1230                     } catch (final InterruptedException e) {
   1231                         Log.e(TAG, "updateParts: ", e);
   1232                     }
   1233                     cacheEntry = PDU_CACHE_INSTANCE.get(uri);
   1234                     if (cacheEntry != null) {
   1235                         ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body);
   1236                     }
   1237                 }
   1238                 // Tell the cache to indicate to other callers that this item
   1239                 // is currently being updated.
   1240                 PDU_CACHE_INSTANCE.setUpdating(uri, true);
   1241             }
   1242 
   1243             final ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>();
   1244             final ArrayMap<Uri, PduPart> toBeUpdated = new ArrayMap<Uri, PduPart>();
   1245 
   1246             final int partsNum = body.getPartsNum();
   1247             final StringBuilder filter = new StringBuilder().append('(');
   1248             for (int i = 0; i < partsNum; i++) {
   1249                 final PduPart part = body.getPart(i);
   1250                 final Uri partUri = part.getDataUri();
   1251                 if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) {
   1252                     toBeCreated.add(part);
   1253                 } else {
   1254                     toBeUpdated.put(partUri, part);
   1255 
   1256                     // Don't use 'i > 0' to determine whether we should append
   1257                     // 'AND' since 'i = 0' may be skipped in another branch.
   1258                     if (filter.length() > 1) {
   1259                         filter.append(" AND ");
   1260                     }
   1261 
   1262                     filter.append(Part._ID);
   1263                     filter.append("!=");
   1264                     DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment());
   1265                 }
   1266             }
   1267             filter.append(')');
   1268 
   1269             final long msgId = ContentUris.parseId(uri);
   1270 
   1271             // Remove the parts which doesn't exist anymore.
   1272             SqliteWrapper.delete(mContext, mContentResolver,
   1273                     Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"),
   1274                     filter.length() > 2 ? filter.toString() : null, null);
   1275 
   1276             // Create new parts which didn't exist before.
   1277             for (final PduPart part : toBeCreated) {
   1278                 persistPart(part, msgId, preOpenedFiles);
   1279             }
   1280 
   1281             // Update the modified parts.
   1282             for (final Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) {
   1283                 updatePart(e.getKey(), e.getValue(), preOpenedFiles);
   1284             }
   1285         } finally {
   1286             synchronized (PDU_CACHE_INSTANCE) {
   1287                 PDU_CACHE_INSTANCE.setUpdating(uri, false);
   1288                 PDU_CACHE_INSTANCE.notifyAll();
   1289             }
   1290         }
   1291     }
   1292 
   1293     /**
   1294      * Persist a PDU object to specific location in the storage.
   1295      *
   1296      * @param pdu             The PDU object to be stored.
   1297      * @param uri             Where to store the given PDU object.
   1298      * @param subId           Subscription id associated with this message.
   1299      * @param subPhoneNumber TODO
   1300      * @param preOpenedFiles  if not null, a map of preopened InputStreams for the parts.
   1301      * @return A Uri which can be used to access the stored PDU.
   1302      */
   1303     public Uri persist(final GenericPdu pdu, final Uri uri, final int subId,
   1304             final String subPhoneNumber, final Map<Uri, InputStream> preOpenedFiles)
   1305             throws MmsException {
   1306         if (uri == null) {
   1307             throw new MmsException("Uri may not be null.");
   1308         }
   1309         long msgId = -1;
   1310         try {
   1311             msgId = ContentUris.parseId(uri);
   1312         } catch (final NumberFormatException e) {
   1313             // the uri ends with "inbox" or something else like that
   1314         }
   1315         final boolean existingUri = msgId != -1;
   1316 
   1317         if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) {
   1318             throw new MmsException(
   1319                     "Bad destination, must be one of "
   1320                             + "content://mms/inbox, content://mms/sent, "
   1321                             + "content://mms/drafts, content://mms/outbox, "
   1322                             + "content://mms/temp."
   1323             );
   1324         }
   1325         synchronized (PDU_CACHE_INSTANCE) {
   1326             // If the cache item is getting updated, wait until it's done updating before
   1327             // purging it.
   1328             if (PDU_CACHE_INSTANCE.isUpdating(uri)) {
   1329                 if (LOCAL_LOGV) {
   1330                     LogUtil.v(TAG, "persist: " + uri + " blocked by isUpdating()");
   1331                 }
   1332                 try {
   1333                     PDU_CACHE_INSTANCE.wait();
   1334                 } catch (final InterruptedException e) {
   1335                     Log.e(TAG, "persist1: ", e);
   1336                 }
   1337             }
   1338         }
   1339         PDU_CACHE_INSTANCE.purge(uri);
   1340 
   1341         final PduHeaders header = pdu.getPduHeaders();
   1342         PduBody body = null;
   1343         ContentValues values = new ContentValues();
   1344 
   1345         // Mark new messages as seen in the telephony database so that we don't have to
   1346         // do a global "set all messages as seen" since that occasionally seems to be
   1347         // problematic (i.e. very slow).  See bug 18189471.
   1348         values.put(Mms.SEEN, 1);
   1349 
   1350         //Set<Entry<Integer, String>> set;
   1351 
   1352         for (int i = ENCODED_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) {
   1353             final int field = ENCODED_STRING_COLUMN_NAME_MAP.keyAt(i);
   1354             final EncodedStringValue encodedString = header.getEncodedStringValue(field);
   1355             if (encodedString != null) {
   1356                 final String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field);
   1357                 values.put(ENCODED_STRING_COLUMN_NAME_MAP.valueAt(i),
   1358                         toIsoString(encodedString.getTextString()));
   1359                 values.put(charsetColumn, encodedString.getCharacterSet());
   1360             }
   1361         }
   1362 
   1363         for (int i = TEXT_STRING_COLUMN_NAME_MAP.size(); --i >= 0; ) {
   1364             final byte[] text = header.getTextString(TEXT_STRING_COLUMN_NAME_MAP.keyAt(i));
   1365             if (text != null) {
   1366                 values.put(TEXT_STRING_COLUMN_NAME_MAP.valueAt(i), toIsoString(text));
   1367             }
   1368         }
   1369 
   1370         for (int i = OCTET_COLUMN_NAME_MAP.size(); --i >= 0; ) {
   1371             final int b = header.getOctet(OCTET_COLUMN_NAME_MAP.keyAt(i));
   1372             if (b != 0) {
   1373                 values.put(OCTET_COLUMN_NAME_MAP.valueAt(i), b);
   1374             }
   1375         }
   1376 
   1377         for (int i = LONG_COLUMN_NAME_MAP.size(); --i >= 0; ) {
   1378             final long l = header.getLongInteger(LONG_COLUMN_NAME_MAP.keyAt(i));
   1379             if (l != -1L) {
   1380                 values.put(LONG_COLUMN_NAME_MAP.valueAt(i), l);
   1381             }
   1382         }
   1383 
   1384         final SparseArray<EncodedStringValue[]> addressMap =
   1385                 new SparseArray<EncodedStringValue[]>(ADDRESS_FIELDS.length);
   1386         // Save address information.
   1387         for (final int addrType : ADDRESS_FIELDS) {
   1388             EncodedStringValue[] array = null;
   1389             if (addrType == PduHeaders.FROM) {
   1390                 final EncodedStringValue v = header.getEncodedStringValue(addrType);
   1391                 if (v != null) {
   1392                     array = new EncodedStringValue[1];
   1393                     array[0] = v;
   1394                 }
   1395             } else {
   1396                 array = header.getEncodedStringValues(addrType);
   1397             }
   1398             addressMap.put(addrType, array);
   1399         }
   1400 
   1401         final HashSet<String> recipients = new HashSet<String>();
   1402         final int msgType = pdu.getMessageType();
   1403         // Here we only allocate thread ID for M-Notification.ind,
   1404         // M-Retrieve.conf and M-Send.req.
   1405         // Some of other PDU types may be allocated a thread ID outside
   1406         // this scope.
   1407         if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND)
   1408                 || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)
   1409                 || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) {
   1410             switch (msgType) {
   1411                 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
   1412                 case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF:
   1413                     loadRecipients(PduHeaders.FROM, recipients, addressMap);
   1414 
   1415                     // For received messages (whether group MMS is enabled or not) we want to
   1416                     // associate this message with the thread composed of all the recipients
   1417                     // EXCLUDING our own number. This includes the person who sent the
   1418                     // message (the FROM field above) in addition to the other people the message
   1419                     // was addressed TO (or CC fields to address group messaging compatibility
   1420                     // issues with devices that place numbers in this field). Typically our own
   1421                     // number is in the TO/CC field so we have to remove it in loadRecipients.
   1422                     checkAndLoadToCcRecipients(recipients, addressMap, subPhoneNumber);
   1423                     break;
   1424                 case PduHeaders.MESSAGE_TYPE_SEND_REQ:
   1425                     loadRecipients(PduHeaders.TO, recipients, addressMap);
   1426                     break;
   1427             }
   1428             long threadId = -1L;
   1429             if (!recipients.isEmpty()) {
   1430                 // Given all the recipients associated with this message, find (or create) the
   1431                 // correct thread.
   1432                 threadId = MmsSmsUtils.Threads.getOrCreateThreadId(mContext, recipients);
   1433             } else {
   1434                 LogUtil.w(TAG, "PduPersister.persist No recipients; persisting PDU to thread: "
   1435                         + threadId);
   1436             }
   1437             values.put(Mms.THREAD_ID, threadId);
   1438         }
   1439 
   1440         // Save parts first to avoid inconsistent message is loaded
   1441         // while saving the parts.
   1442         final long dummyId = System.currentTimeMillis(); // Dummy ID of the msg.
   1443 
   1444         // Figure out if this PDU is a text-only message
   1445         boolean textOnly = true;
   1446 
   1447         // Get body if the PDU is a RetrieveConf or SendReq.
   1448         if (pdu instanceof MultimediaMessagePdu) {
   1449             body = ((MultimediaMessagePdu) pdu).getBody();
   1450             // Start saving parts if necessary.
   1451             if (body != null) {
   1452                 final int partsNum = body.getPartsNum();
   1453                 if (LOCAL_LOGV) {
   1454                     LogUtil.v(TAG, "PduPersister.persist partsNum: " + partsNum);
   1455                 }
   1456                 if (partsNum > 2) {
   1457                     // For a text-only message there will be two parts: 1-the SMIL, 2-the text.
   1458                     // Down a few lines below we're checking to make sure we've only got SMIL or
   1459                     // text. We also have to check then we don't have more than two parts.
   1460                     // Otherwise, a slideshow with two text slides would be marked as textOnly.
   1461                     textOnly = false;
   1462                 }
   1463                 for (int i = 0; i < partsNum; i++) {
   1464                     final PduPart part = body.getPart(i);
   1465                     persistPart(part, dummyId, preOpenedFiles);
   1466 
   1467                     // If we've got anything besides text/plain or SMIL part, then we've got
   1468                     // an mms message with some other type of attachment.
   1469                     final String contentType = getPartContentType(part);
   1470                     if (LOCAL_LOGV) {
   1471                         LogUtil.v(TAG, "PduPersister.persist part: " + i + " contentType: " +
   1472                                 contentType);
   1473                     }
   1474                     if (contentType != null && !ContentType.APP_SMIL.equals(contentType)
   1475                             && !ContentType.TEXT_PLAIN.equals(contentType)) {
   1476                         textOnly = false;
   1477                     }
   1478                 }
   1479             }
   1480         }
   1481         // Record whether this mms message is a simple plain text or not. This is a hint for the
   1482         // UI.
   1483         if (OsUtil.isAtLeastJB_MR1()) {
   1484             values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0);
   1485         }
   1486 
   1487         if (OsUtil.isAtLeastL_MR1()) {
   1488             values.put(Mms.SUBSCRIPTION_ID, subId);
   1489         } else {
   1490             Assert.equals(ParticipantData.DEFAULT_SELF_SUB_ID, subId);
   1491         }
   1492 
   1493         Uri res = null;
   1494         if (existingUri) {
   1495             res = uri;
   1496             SqliteWrapper.update(mContext, mContentResolver, res, values, null, null);
   1497         } else {
   1498             res = SqliteWrapper.insert(mContext, mContentResolver, uri, values);
   1499             if (res == null) {
   1500                 throw new MmsException("persist() failed: return null.");
   1501             }
   1502             // Get the real ID of the PDU and update all parts which were
   1503             // saved with the dummy ID.
   1504             msgId = ContentUris.parseId(res);
   1505         }
   1506 
   1507         values = new ContentValues(1);
   1508         values.put(Part.MSG_ID, msgId);
   1509         SqliteWrapper.update(mContext, mContentResolver,
   1510                 Uri.parse("content://mms/" + dummyId + "/part"),
   1511                 values, null, null);
   1512         // We should return the longest URI of the persisted PDU, for
   1513         // example, if input URI is "content://mms/inbox" and the _ID of
   1514         // persisted PDU is '8', we should return "content://mms/inbox/8"
   1515         // instead of "content://mms/8".
   1516         // TODO: Should the MmsProvider be responsible for this???
   1517         if (!existingUri) {
   1518             res = Uri.parse(uri + "/" + msgId);
   1519         }
   1520 
   1521         // Save address information.
   1522         for (final int addrType : ADDRESS_FIELDS) {
   1523             final EncodedStringValue[] array = addressMap.get(addrType);
   1524             if (array != null) {
   1525                 persistAddress(msgId, addrType, array);
   1526             }
   1527         }
   1528 
   1529         return res;
   1530     }
   1531 
   1532     /**
   1533      * For a given address type, extract the recipients from the headers.
   1534      *
   1535      * @param addressType     can be PduHeaders.FROM or PduHeaders.TO
   1536      * @param recipients      a HashSet that is loaded with the recipients from the FROM or TO
   1537      *                        headers
   1538      * @param addressMap      a HashMap of the addresses from the ADDRESS_FIELDS header
   1539      */
   1540     private void loadRecipients(final int addressType, final HashSet<String> recipients,
   1541             final SparseArray<EncodedStringValue[]> addressMap) {
   1542         final EncodedStringValue[] array = addressMap.get(addressType);
   1543         if (array == null) {
   1544             return;
   1545         }
   1546         for (final EncodedStringValue v : array) {
   1547             if (v != null) {
   1548                 final String number = v.getString();
   1549                 if (!recipients.contains(number)) {
   1550                     // Only add numbers which aren't already included.
   1551                     recipients.add(number);
   1552                 }
   1553             }
   1554         }
   1555     }
   1556 
   1557     /**
   1558      * For a given address type, extract the recipients from the headers.
   1559      *
   1560      * @param recipients      a HashSet that is loaded with the recipients from the FROM or TO
   1561      *                        headers
   1562      * @param addressMap      a HashMap of the addresses from the ADDRESS_FIELDS header
   1563      * @param selfNumber      self phone number
   1564      */
   1565     private void checkAndLoadToCcRecipients(final HashSet<String> recipients,
   1566             final SparseArray<EncodedStringValue[]> addressMap, final String selfNumber) {
   1567         final EncodedStringValue[] arrayTo = addressMap.get(PduHeaders.TO);
   1568         final EncodedStringValue[] arrayCc = addressMap.get(PduHeaders.CC);
   1569         final ArrayList<String> numbers = new ArrayList<String>();
   1570         if (arrayTo != null) {
   1571             for (final EncodedStringValue v : arrayTo) {
   1572                 if (v != null) {
   1573                     numbers.add(v.getString());
   1574                 }
   1575             }
   1576         }
   1577         if (arrayCc != null) {
   1578             for (final EncodedStringValue v : arrayCc) {
   1579                 if (v != null) {
   1580                     numbers.add(v.getString());
   1581                 }
   1582             }
   1583         }
   1584         for (final String number : numbers) {
   1585             // Only add numbers which aren't my own number.
   1586             if (TextUtils.isEmpty(selfNumber) || !PhoneNumberUtils.compare(number, selfNumber)) {
   1587                 if (!recipients.contains(number)) {
   1588                     // Only add numbers which aren't already included.
   1589                     recipients.add(number);
   1590                 }
   1591             }
   1592         }
   1593     }
   1594 
   1595     /**
   1596      * Move a PDU object from one location to another.
   1597      *
   1598      * @param from Specify the PDU object to be moved.
   1599      * @param to   The destination location, should be one of the following:
   1600      *             "content://mms/inbox", "content://mms/sent",
   1601      *             "content://mms/drafts", "content://mms/outbox",
   1602      *             "content://mms/trash".
   1603      * @return New Uri of the moved PDU.
   1604      * @throws MmsException Error occurred while moving the message.
   1605      */
   1606     public Uri move(final Uri from, final Uri to) throws MmsException {
   1607         // Check whether the 'msgId' has been assigned a valid value.
   1608         final long msgId = ContentUris.parseId(from);
   1609         if (msgId == -1L) {
   1610             throw new MmsException("Error! ID of the message: -1.");
   1611         }
   1612 
   1613         // Get corresponding int value of destination box.
   1614         final Integer msgBox = MESSAGE_BOX_MAP.get(to);
   1615         if (msgBox == null) {
   1616             throw new MmsException(
   1617                     "Bad destination, must be one of "
   1618                             + "content://mms/inbox, content://mms/sent, "
   1619                             + "content://mms/drafts, content://mms/outbox, "
   1620                             + "content://mms/temp."
   1621             );
   1622         }
   1623 
   1624         final ContentValues values = new ContentValues(1);
   1625         values.put(Mms.MESSAGE_BOX, msgBox);
   1626         SqliteWrapper.update(mContext, mContentResolver, from, values, null, null);
   1627         return ContentUris.withAppendedId(to, msgId);
   1628     }
   1629 
   1630     /**
   1631      * Wrap a byte[] into a String.
   1632      */
   1633     public static String toIsoString(final byte[] bytes) {
   1634         try {
   1635             return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1);
   1636         } catch (final UnsupportedEncodingException e) {
   1637             // Impossible to reach here!
   1638             Log.e(TAG, "ISO_8859_1 must be supported!", e);
   1639             return "";
   1640         }
   1641     }
   1642 
   1643     /**
   1644      * Unpack a given String into a byte[].
   1645      */
   1646     public static byte[] getBytes(final String data) {
   1647         try {
   1648             return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1);
   1649         } catch (final UnsupportedEncodingException e) {
   1650             // Impossible to reach here!
   1651             Log.e(TAG, "ISO_8859_1 must be supported!", e);
   1652             return new byte[0];
   1653         }
   1654     }
   1655 
   1656     /**
   1657      * Remove all objects in the temporary path.
   1658      */
   1659     public void release() {
   1660         final Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI);
   1661         SqliteWrapper.delete(mContext, mContentResolver, uri, null, null);
   1662     }
   1663 
   1664     /**
   1665      * Find all messages to be sent or downloaded before certain time.
   1666      */
   1667     public Cursor getPendingMessages(final long dueTime) {
   1668         final Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon();
   1669         uriBuilder.appendQueryParameter("protocol", "mms");
   1670 
   1671         final String selection = PendingMessages.ERROR_TYPE + " < ?"
   1672                 + " AND " + PendingMessages.DUE_TIME + " <= ?";
   1673 
   1674         final String[] selectionArgs = new String[] {
   1675                 String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT),
   1676                 String.valueOf(dueTime)
   1677         };
   1678 
   1679         return SqliteWrapper.query(mContext, mContentResolver,
   1680                 uriBuilder.build(), null, selection, selectionArgs,
   1681                 PendingMessages.DUE_TIME);
   1682     }
   1683 }
   1684