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>();
MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX)200         MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI,  Mms.MESSAGE_BOX_INBOX);
MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT)201         MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI,   Mms.MESSAGE_BOX_SENT);
MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS)202         MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI,  Mms.MESSAGE_BOX_DRAFTS);
MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX)203         MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX);
204 
205         CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>();
CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET)206         CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET);
CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_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>();
CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET)210         CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET);
CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_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>();
ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT)215         ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT);
ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT)216         ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT);
217 
218         ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>();
ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT)219         ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT);
ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT)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>();
TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION)224         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION);
TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE)225         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE);
TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS)226         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS);
TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID)227         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID);
TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT)228         TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT);
TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID)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>();
TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION)232         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION);
TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE)233         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE);
TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS)234         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS);
TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID)235         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID);
TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT)236         TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT);
TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID)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>();
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS)241         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT)242         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE)243         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION)244         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY)245         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT)246         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS)247         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED)248         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS)249         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS);
OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS)250         OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS);
251 
252         OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>();
OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS)253         OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT)254         OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE)255         OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION)256         OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY)257         OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT)258         OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS)259         OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED)260         OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS)261         OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS);
OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.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>();
LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE)266         LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE);
LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME)267         LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME);
LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY)268         LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY);
LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE)269         LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE);
270 
271         LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>();
LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE)272         LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE);
LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME)273         LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME);
LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY)274         LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY);
LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE)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 
PduPersister(Context context)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 */
getPduPersister(Context context)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 
setEncodedStringValueToHeaders( Cursor c, int columnIndex, PduHeaders headers, int mapColumn)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 
setTextStringToHeaders( Cursor c, int columnIndex, PduHeaders headers, int mapColumn)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 
setOctetToHeaders( Cursor c, int columnIndex, PduHeaders headers, int mapColumn)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 
setLongToHeaders( Cursor c, int columnIndex, PduHeaders headers, int mapColumn)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 
getIntegerFromPartColumn(Cursor c, int columnIndex)345     private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) {
346         if (!c.isNull(columnIndex)) {
347             return c.getInt(columnIndex);
348         }
349         return null;
350     }
351 
getByteArrayFromPartColumn(Cursor c, int columnIndex)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 
loadParts(long msgId)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 
loadAddress(long msgId, PduHeaders headers)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      */
load(Uri uri)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 
persistAddress( long msgId, int type, EncodedStringValue[] array)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 
getPartContentType(PduPart part)699     private static String getPartContentType(PduPart part) {
700         return part.getContentType() == null ? null : toIsoString(part.getContentType());
701     }
702 
persistPart(PduPart part, long msgId, HashMap<Uri, InputStream> preOpenedFiles)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      */
persistData(PduPart part, Uri uri, String contentType, HashMap<Uri, InputStream> preOpenedFiles)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      */
convertUriToPath(Context context, Uri uri)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 
updateAddress( long msgId, int type, EncodedStringValue[] array)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      */
updateHeaders(Uri uri, SendReq sendReq)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 
updatePart(Uri uri, PduPart part, HashMap<Uri, InputStream> preOpenedFiles)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      */
updateParts(Uri uri, PduBody body, HashMap<Uri, InputStream> preOpenedFiles)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 
persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled, HashMap<Uri, InputStream> preOpenedFiles)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      */
loadRecipients(int addressType, HashSet<String> recipients, HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber)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      */
move(Uri from, Uri to)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      */
toIsoString(byte[] bytes)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      */
getBytes(String data)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      */
release()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      */
getPendingMessages(long dueTime)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