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