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