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