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