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