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