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