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 56 import java.io.ByteArrayOutputStream; 57 import java.io.File; 58 import java.io.FileNotFoundException; 59 import java.io.IOException; 60 import java.io.InputStream; 61 import java.io.OutputStream; 62 import java.io.UnsupportedEncodingException; 63 import java.util.ArrayList; 64 import java.util.HashSet; 65 import java.util.Map; 66 67 /** 68 * This class is the high-level manager of PDU storage. 69 */ 70 public class PduPersister { 71 private static final String TAG = "PduPersister"; 72 private static final boolean LOCAL_LOGV = false; 73 74 /** 75 * The uri of temporary drm objects. 76 */ 77 public static final String TEMPORARY_DRM_OBJECT_URI = 78 "content://mms/" + Long.MAX_VALUE + "/part"; 79 80 /** 81 * Indicate that we transiently failed to process a MM. 82 */ 83 public static final int PROC_STATUS_TRANSIENT_FAILURE = 1; 84 85 /** 86 * Indicate that we permanently failed to process a MM. 87 */ 88 public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2; 89 90 /** 91 * Indicate that we have successfully processed a MM. 92 */ 93 public static final int PROC_STATUS_COMPLETED = 3; 94 95 public static final String BEGIN_VCARD = "BEGIN:VCARD"; 96 97 private static PduPersister sPersister; 98 99 private static final PduCache PDU_CACHE_INSTANCE; 100 101 private static final int[] ADDRESS_FIELDS = new int[]{ 102 PduHeaders.BCC, 103 PduHeaders.CC, 104 PduHeaders.FROM, 105 PduHeaders.TO 106 }; 107 108 public static final String[] PDU_PROJECTION = new String[]{ 109 Mms._ID, 110 Mms.MESSAGE_BOX, 111 Mms.THREAD_ID, 112 Mms.RETRIEVE_TEXT, 113 Mms.SUBJECT, 114 Mms.CONTENT_LOCATION, 115 Mms.CONTENT_TYPE, 116 Mms.MESSAGE_CLASS, 117 Mms.MESSAGE_ID, 118 Mms.RESPONSE_TEXT, 119 Mms.TRANSACTION_ID, 120 Mms.CONTENT_CLASS, 121 Mms.DELIVERY_REPORT, 122 Mms.MESSAGE_TYPE, 123 Mms.MMS_VERSION, 124 Mms.PRIORITY, 125 Mms.READ_REPORT, 126 Mms.READ_STATUS, 127 Mms.REPORT_ALLOWED, 128 Mms.RETRIEVE_STATUS, 129 Mms.STATUS, 130 Mms.DATE, 131 Mms.DELIVERY_TIME, 132 Mms.EXPIRY, 133 Mms.MESSAGE_SIZE, 134 Mms.SUBJECT_CHARSET, 135 Mms.RETRIEVE_TEXT_CHARSET, 136 Mms.READ, 137 Mms.SEEN, 138 }; 139 140 public static final int PDU_COLUMN_ID = 0; 141 public static final int PDU_COLUMN_MESSAGE_BOX = 1; 142 public static final int PDU_COLUMN_THREAD_ID = 2; 143 public static final int PDU_COLUMN_RETRIEVE_TEXT = 3; 144 public static final int PDU_COLUMN_SUBJECT = 4; 145 public static final int PDU_COLUMN_CONTENT_LOCATION = 5; 146 public static final int PDU_COLUMN_CONTENT_TYPE = 6; 147 public static final int PDU_COLUMN_MESSAGE_CLASS = 7; 148 public static final int PDU_COLUMN_MESSAGE_ID = 8; 149 public static final int PDU_COLUMN_RESPONSE_TEXT = 9; 150 public static final int PDU_COLUMN_TRANSACTION_ID = 10; 151 public static final int PDU_COLUMN_CONTENT_CLASS = 11; 152 public static final int PDU_COLUMN_DELIVERY_REPORT = 12; 153 public static final int PDU_COLUMN_MESSAGE_TYPE = 13; 154 public static final int PDU_COLUMN_MMS_VERSION = 14; 155 public static final int PDU_COLUMN_PRIORITY = 15; 156 public static final int PDU_COLUMN_READ_REPORT = 16; 157 public static final int PDU_COLUMN_READ_STATUS = 17; 158 public static final int PDU_COLUMN_REPORT_ALLOWED = 18; 159 public static final int PDU_COLUMN_RETRIEVE_STATUS = 19; 160 public static final int PDU_COLUMN_STATUS = 20; 161 public static final int PDU_COLUMN_DATE = 21; 162 public static final int PDU_COLUMN_DELIVERY_TIME = 22; 163 public static final int PDU_COLUMN_EXPIRY = 23; 164 public static final int PDU_COLUMN_MESSAGE_SIZE = 24; 165 public static final int PDU_COLUMN_SUBJECT_CHARSET = 25; 166 public static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26; 167 public static final int PDU_COLUMN_READ = 27; 168 public static final int PDU_COLUMN_SEEN = 28; 169 170 private static final String[] PART_PROJECTION = new String[] { 171 Part._ID, 172 Part.CHARSET, 173 Part.CONTENT_DISPOSITION, 174 Part.CONTENT_ID, 175 Part.CONTENT_LOCATION, 176 Part.CONTENT_TYPE, 177 Part.FILENAME, 178 Part.NAME, 179 Part.TEXT 180 }; 181 182 private static final int PART_COLUMN_ID = 0; 183 private static final int PART_COLUMN_CHARSET = 1; 184 private static final int PART_COLUMN_CONTENT_DISPOSITION = 2; 185 private static final int PART_COLUMN_CONTENT_ID = 3; 186 private static final int PART_COLUMN_CONTENT_LOCATION = 4; 187 private static final int PART_COLUMN_CONTENT_TYPE = 5; 188 private static final int PART_COLUMN_FILENAME = 6; 189 private static final int PART_COLUMN_NAME = 7; 190 private static final int PART_COLUMN_TEXT = 8; 191 192 private static final SimpleArrayMap<Uri, Integer> MESSAGE_BOX_MAP; 193 194 // These map are used for convenience in persist() and load(). 195 private static final SparseIntArray CHARSET_COLUMN_INDEX_MAP; 196 197 private static final SparseIntArray ENCODED_STRING_COLUMN_INDEX_MAP; 198 199 private static final SparseIntArray TEXT_STRING_COLUMN_INDEX_MAP; 200 201 private static final SparseIntArray OCTET_COLUMN_INDEX_MAP; 202 203 private static final SparseIntArray LONG_COLUMN_INDEX_MAP; 204 205 private static final SparseArray<String> CHARSET_COLUMN_NAME_MAP; 206 207 private static final SparseArray<String> ENCODED_STRING_COLUMN_NAME_MAP; 208 209 private static final SparseArray<String> TEXT_STRING_COLUMN_NAME_MAP; 210 211 private static final SparseArray<String> OCTET_COLUMN_NAME_MAP; 212 213 private static final SparseArray<String> LONG_COLUMN_NAME_MAP; 214 215 static { 216 MESSAGE_BOX_MAP = new SimpleArrayMap<Uri, Integer>(); MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX)217 MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX); MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT)218 MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT); MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS)219 MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS); MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX)220 MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX); 221 222 CHARSET_COLUMN_INDEX_MAP = new SparseIntArray(); CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET)223 CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET); CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET)224 CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET); 225 226 CHARSET_COLUMN_NAME_MAP = new SparseArray<String>(); CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET)227 CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET); CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET)228 CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET); 229 230 // Encoded string field code -> column index/name map. 231 ENCODED_STRING_COLUMN_INDEX_MAP = new SparseIntArray(); ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT)232 ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT); ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT)233 ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT); 234 235 ENCODED_STRING_COLUMN_NAME_MAP = new SparseArray<String>(); ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT)236 ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT); ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT)237 ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT); 238 239 // Text string field code -> column index/name map. 240 TEXT_STRING_COLUMN_INDEX_MAP = new SparseIntArray(); TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION)241 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)242 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)243 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)244 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)245 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)246 TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID); 247 248 TEXT_STRING_COLUMN_NAME_MAP = new SparseArray<String>(); TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION)249 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION); TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE)250 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE); TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS)251 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS); TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID)252 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID); TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT)253 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT); TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID)254 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID); 255 256 // Octet field code -> column index/name map. 257 OCTET_COLUMN_INDEX_MAP = new SparseIntArray(); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS)258 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT)259 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE)260 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION)261 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY)262 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT)263 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS)264 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED)265 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS)266 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS); OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS)267 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS); 268 269 OCTET_COLUMN_NAME_MAP = new SparseArray<String>(); OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS)270 OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS); OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT)271 OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT); OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE)272 OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE); OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION)273 OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION); OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY)274 OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY); OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT)275 OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT); OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS)276 OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS); OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED)277 OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED); OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS)278 OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS); OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS)279 OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS); 280 281 // Long field code -> column index/name map. 282 LONG_COLUMN_INDEX_MAP = new SparseIntArray(); LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE)283 LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE); LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME)284 LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME); LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY)285 LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY); LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE)286 LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE); 287 288 LONG_COLUMN_NAME_MAP = new SparseArray<String>(); LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE)289 LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE); LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME)290 LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME); LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY)291 LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY); LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE)292 LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE); 293 294 PDU_CACHE_INSTANCE = PduCache.getInstance(); 295 } 296 297 private final Context mContext; 298 299 private final ContentResolver mContentResolver; 300 PduPersister(final Context context)301 private PduPersister(final Context context) { 302 mContext = context; 303 mContentResolver = context.getContentResolver(); 304 } 305 306 /** Get(or create if not exist) an instance of PduPersister */ getPduPersister(final Context context)307 public static PduPersister getPduPersister(final Context context) { 308 if ((sPersister == null) || !context.equals(sPersister.mContext)) { 309 sPersister = new PduPersister(context); 310 } 311 if (LOCAL_LOGV) { 312 LogUtil.v(TAG, "PduPersister getPduPersister"); 313 } 314 315 return sPersister; 316 } 317 setEncodedStringValueToHeaders( final Cursor c, final int columnIndex, final PduHeaders headers, final int mapColumn)318 private void setEncodedStringValueToHeaders( 319 final Cursor c, final int columnIndex, 320 final PduHeaders headers, final int mapColumn) { 321 final String s = c.getString(columnIndex); 322 if ((s != null) && (s.length() > 0)) { 323 final int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn); 324 final int charset = c.getInt(charsetColumnIndex); 325 final EncodedStringValue value = new EncodedStringValue( 326 charset, getBytes(s)); 327 headers.setEncodedStringValue(value, mapColumn); 328 } 329 } 330 setTextStringToHeaders( final Cursor c, final int columnIndex, final PduHeaders headers, final int mapColumn)331 private void setTextStringToHeaders( 332 final Cursor c, final int columnIndex, 333 final PduHeaders headers, final int mapColumn) { 334 final String s = c.getString(columnIndex); 335 if (s != null) { 336 headers.setTextString(getBytes(s), mapColumn); 337 } 338 } 339 setOctetToHeaders( final Cursor c, final int columnIndex, final PduHeaders headers, final int mapColumn)340 private void setOctetToHeaders( 341 final Cursor c, final int columnIndex, 342 final PduHeaders headers, final int mapColumn) throws InvalidHeaderValueException { 343 if (!c.isNull(columnIndex)) { 344 final int b = c.getInt(columnIndex); 345 headers.setOctet(b, mapColumn); 346 } 347 } 348 setLongToHeaders( final Cursor c, final int columnIndex, final PduHeaders headers, final int mapColumn)349 private void setLongToHeaders( 350 final Cursor c, final int columnIndex, 351 final PduHeaders headers, final int mapColumn) { 352 if (!c.isNull(columnIndex)) { 353 final long l = c.getLong(columnIndex); 354 headers.setLongInteger(l, mapColumn); 355 } 356 } 357 getIntegerFromPartColumn(final Cursor c, final int columnIndex)358 private Integer getIntegerFromPartColumn(final Cursor c, final int columnIndex) { 359 if (!c.isNull(columnIndex)) { 360 return c.getInt(columnIndex); 361 } 362 return null; 363 } 364 getByteArrayFromPartColumn(final Cursor c, final int columnIndex)365 private byte[] getByteArrayFromPartColumn(final Cursor c, final int columnIndex) { 366 if (!c.isNull(columnIndex)) { 367 return getBytes(c.getString(columnIndex)); 368 } 369 return null; 370 } 371 loadParts(final long msgId)372 private PduPart[] loadParts(final long msgId) throws MmsException { 373 final Cursor c = SqliteWrapper.query(mContext, mContentResolver, 374 Uri.parse("content://mms/" + msgId + "/part"), 375 PART_PROJECTION, null, null, null); 376 377 PduPart[] parts = null; 378 379 try { 380 if ((c == null) || (c.getCount() == 0)) { 381 if (LOCAL_LOGV) { 382 LogUtil.v(TAG, "loadParts(" + msgId + "): no part to load."); 383 } 384 return null; 385 } 386 387 final int partCount = c.getCount(); 388 int partIdx = 0; 389 parts = new PduPart[partCount]; 390 while (c.moveToNext()) { 391 final PduPart part = new PduPart(); 392 final Integer charset = getIntegerFromPartColumn( 393 c, PART_COLUMN_CHARSET); 394 if (charset != null) { 395 part.setCharset(charset); 396 } 397 398 final byte[] contentDisposition = getByteArrayFromPartColumn( 399 c, PART_COLUMN_CONTENT_DISPOSITION); 400 if (contentDisposition != null) { 401 part.setContentDisposition(contentDisposition); 402 } 403 404 final byte[] contentId = getByteArrayFromPartColumn( 405 c, PART_COLUMN_CONTENT_ID); 406 if (contentId != null) { 407 part.setContentId(contentId); 408 } 409 410 final byte[] contentLocation = getByteArrayFromPartColumn( 411 c, PART_COLUMN_CONTENT_LOCATION); 412 if (contentLocation != null) { 413 part.setContentLocation(contentLocation); 414 } 415 416 final byte[] contentType = getByteArrayFromPartColumn( 417 c, PART_COLUMN_CONTENT_TYPE); 418 if (contentType != null) { 419 part.setContentType(contentType); 420 } else { 421 throw new MmsException("Content-Type must be set."); 422 } 423 424 final byte[] fileName = getByteArrayFromPartColumn( 425 c, PART_COLUMN_FILENAME); 426 if (fileName != null) { 427 part.setFilename(fileName); 428 } 429 430 final byte[] name = getByteArrayFromPartColumn( 431 c, PART_COLUMN_NAME); 432 if (name != null) { 433 part.setName(name); 434 } 435 436 // Construct a Uri for this part. 437 final long partId = c.getLong(PART_COLUMN_ID); 438 final Uri partURI = Uri.parse("content://mms/part/" + partId); 439 part.setDataUri(partURI); 440 441 // For images/audio/video, we won't keep their data in Part 442 // because their renderer accept Uri as source. 443 final String type = toIsoString(contentType); 444 if (!ContentType.isImageType(type) 445 && !ContentType.isAudioType(type) 446 && !ContentType.isVideoType(type)) { 447 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 448 InputStream is = null; 449 450 // Store simple string values directly in the database instead of an 451 // external file. This makes the text searchable and retrieval slightly 452 // faster. 453 if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type) 454 || ContentType.TEXT_HTML.equals(type)) { 455 final String text = c.getString(PART_COLUMN_TEXT); 456 final byte[] blob = new EncodedStringValue( 457 charset != null ? charset : CharacterSets.DEFAULT_CHARSET, 458 text != null ? text : "") 459 .getTextString(); 460 baos.write(blob, 0, blob.length); 461 } else { 462 463 try { 464 is = mContentResolver.openInputStream(partURI); 465 466 final byte[] buffer = new byte[256]; 467 int len = is.read(buffer); 468 while (len >= 0) { 469 baos.write(buffer, 0, len); 470 len = is.read(buffer); 471 } 472 } catch (final IOException e) { 473 Log.e(TAG, "Failed to load part data", e); 474 c.close(); 475 throw new MmsException(e); 476 } finally { 477 if (is != null) { 478 try { 479 is.close(); 480 } catch (final IOException e) { 481 Log.e(TAG, "Failed to close stream", e); 482 } // Ignore 483 } 484 } 485 } 486 part.setData(baos.toByteArray()); 487 } 488 parts[partIdx++] = part; 489 } 490 } finally { 491 if (c != null) { 492 c.close(); 493 } 494 } 495 496 return parts; 497 } 498 loadAddress(final long msgId, final PduHeaders headers)499 private void loadAddress(final long msgId, final PduHeaders headers) { 500 final Cursor c = SqliteWrapper.query(mContext, mContentResolver, 501 Uri.parse("content://mms/" + msgId + "/addr"), 502 new String[]{Addr.ADDRESS, Addr.CHARSET, Addr.TYPE}, 503 null, null, null); 504 505 if (c != null) { 506 try { 507 while (c.moveToNext()) { 508 final String addr = c.getString(0); 509 if (!TextUtils.isEmpty(addr)) { 510 final int addrType = c.getInt(2); 511 switch (addrType) { 512 case PduHeaders.FROM: 513 headers.setEncodedStringValue( 514 new EncodedStringValue(c.getInt(1), getBytes(addr)), 515 addrType); 516 break; 517 case PduHeaders.TO: 518 case PduHeaders.CC: 519 case PduHeaders.BCC: 520 headers.appendEncodedStringValue( 521 new EncodedStringValue(c.getInt(1), getBytes(addr)), 522 addrType); 523 break; 524 default: 525 Log.e(TAG, "Unknown address type: " + addrType); 526 break; 527 } 528 } 529 } 530 } finally { 531 c.close(); 532 } 533 } 534 } 535 536 /** 537 * Load a PDU from a given cursor 538 * 539 * @param c The cursor 540 * @return A parsed PDU from the database row 541 */ load(final Cursor c)542 public GenericPdu load(final Cursor c) throws MmsException { 543 final PduHeaders headers = new PduHeaders(); 544 final long msgId = c.getLong(PDU_COLUMN_ID); 545 // Fill in the headers from the PDU columns 546 loadHeadersFromCursor(c, headers); 547 // Load address information of the MM. 548 loadAddress(msgId, headers); 549 // Load parts for the PDU body 550 final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); 551 final PduBody body = loadBody(msgId, msgType); 552 return createPdu(msgType, headers, body); 553 } 554 555 /** 556 * Load a PDU from storage by given Uri. 557 * 558 * @param uri The Uri of the PDU to be loaded. 559 * @return A generic PDU object, it may be cast to dedicated PDU. 560 * @throws MmsException Failed to load some fields of a PDU. 561 */ load(final Uri uri)562 public GenericPdu load(final Uri uri) throws MmsException { 563 GenericPdu pdu = null; 564 PduCacheEntry cacheEntry = null; 565 int msgBox = 0; 566 final long threadId = -1; 567 try { 568 synchronized (PDU_CACHE_INSTANCE) { 569 if (PDU_CACHE_INSTANCE.isUpdating(uri)) { 570 if (LOCAL_LOGV) { 571 LogUtil.v(TAG, "load: " + uri + " blocked by isUpdating()"); 572 } 573 try { 574 PDU_CACHE_INSTANCE.wait(); 575 } catch (final InterruptedException e) { 576 Log.e(TAG, "load: ", e); 577 } 578 } 579 580 // Check if the pdu is already loaded 581 cacheEntry = PDU_CACHE_INSTANCE.get(uri); 582 if (cacheEntry != null) { 583 return cacheEntry.getPdu(); 584 } 585 586 // Tell the cache to indicate to other callers that this item 587 // is currently being updated. 588 PDU_CACHE_INSTANCE.setUpdating(uri, true); 589 } 590 591 final Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, 592 PDU_PROJECTION, null, null, null); 593 final PduHeaders headers = new PduHeaders(); 594 final long msgId = ContentUris.parseId(uri); 595 596 try { 597 if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { 598 return null; // MMS not found 599 } 600 601 msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); 602 //threadId = c.getLong(PDU_COLUMN_THREAD_ID); 603 loadHeadersFromCursor(c, headers); 604 } finally { 605 if (c != null) { 606 c.close(); 607 } 608 } 609 610 // Check whether 'msgId' has been assigned a valid value. 611 if (msgId == -1L) { 612 throw new MmsException("Error! ID of the message: -1."); 613 } 614 615 // Load address information of the MM. 616 loadAddress(msgId, headers); 617 618 final int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); 619 final PduBody body = loadBody(msgId, msgType); 620 pdu = createPdu(msgType, headers, body); 621 } finally { 622 synchronized (PDU_CACHE_INSTANCE) { 623 if (pdu != null) { 624 Assert.isNull(PDU_CACHE_INSTANCE.get(uri), "Pdu exists for " + uri); 625 // Update the cache entry with the real info 626 cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); 627 PDU_CACHE_INSTANCE.put(uri, cacheEntry); 628 } 629 PDU_CACHE_INSTANCE.setUpdating(uri, false); 630 PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead 631 } 632 } 633 return pdu; 634 } 635 loadHeadersFromCursor(final Cursor c, final PduHeaders headers)636 private void loadHeadersFromCursor(final Cursor c, final PduHeaders headers) 637 throws InvalidHeaderValueException { 638 for (int i = ENCODED_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) { 639 setEncodedStringValueToHeaders( 640 c, ENCODED_STRING_COLUMN_INDEX_MAP.valueAt(i), headers, 641 ENCODED_STRING_COLUMN_INDEX_MAP.keyAt(i)); 642 } 643 for (int i = TEXT_STRING_COLUMN_INDEX_MAP.size(); --i >= 0; ) { 644 setTextStringToHeaders( 645 c, TEXT_STRING_COLUMN_INDEX_MAP.valueAt(i), headers, 646 TEXT_STRING_COLUMN_INDEX_MAP.keyAt(i)); 647 } 648 for (int i = OCTET_COLUMN_INDEX_MAP.size(); --i >= 0; ) { 649 setOctetToHeaders( 650 c, OCTET_COLUMN_INDEX_MAP.valueAt(i), headers, 651 OCTET_COLUMN_INDEX_MAP.keyAt(i)); 652 } 653 for (int i = LONG_COLUMN_INDEX_MAP.size(); --i >= 0; ) { 654 setLongToHeaders( 655 c, LONG_COLUMN_INDEX_MAP.valueAt(i), headers, 656 LONG_COLUMN_INDEX_MAP.keyAt(i)); 657 } 658 } 659 createPdu(final int msgType, final PduHeaders headers, final PduBody body)660 private GenericPdu createPdu(final int msgType, final PduHeaders headers, final PduBody body) 661 throws MmsException { 662 switch (msgType) { 663 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 664 return new NotificationInd(headers); 665 case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: 666 return new DeliveryInd(headers); 667 case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: 668 return new ReadOrigInd(headers); 669 case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: 670 return new RetrieveConf(headers, body); 671 case PduHeaders.MESSAGE_TYPE_SEND_REQ: 672 return new SendReq(headers, body); 673 case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: 674 return new AcknowledgeInd(headers); 675 case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: 676 return new NotifyRespInd(headers); 677 case PduHeaders.MESSAGE_TYPE_READ_REC_IND: 678 return new ReadRecInd(headers); 679 case PduHeaders.MESSAGE_TYPE_SEND_CONF: 680 case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: 681 case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: 682 case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: 683 case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: 684 case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: 685 case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: 686 case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: 687 case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: 688 case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: 689 case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: 690 case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: 691 case PduHeaders.MESSAGE_TYPE_DELETE_REQ: 692 case PduHeaders.MESSAGE_TYPE_DELETE_CONF: 693 case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: 694 case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: 695 throw new MmsException( 696 "Unsupported PDU type: " + Integer.toHexString(msgType)); 697 698 default: 699 throw new MmsException( 700 "Unrecognized PDU type: " + Integer.toHexString(msgType)); 701 } 702 } 703 loadBody(final long msgId, final int msgType)704 private PduBody loadBody(final long msgId, final int msgType) throws MmsException { 705 final PduBody body = new PduBody(); 706 707 // For PDU which type is M_retrieve.conf or Send.req, we should 708 // load multiparts and put them into the body of the PDU. 709 if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) 710 || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { 711 final PduPart[] parts = loadParts(msgId); 712 if (parts != null) { 713 final int partsNum = parts.length; 714 for (int i = 0; i < partsNum; i++) { 715 body.addPart(parts[i]); 716 } 717 } 718 } 719 720 return body; 721 } 722 persistAddress( final long msgId, final int type, final EncodedStringValue[] array)723 private void persistAddress( 724 final long msgId, final int type, final EncodedStringValue[] array) { 725 final ContentValues values = new ContentValues(3); 726 727 for (final EncodedStringValue addr : array) { 728 values.clear(); // Clear all values first. 729 values.put(Addr.ADDRESS, toIsoString(addr.getTextString())); 730 values.put(Addr.CHARSET, addr.getCharacterSet()); 731 values.put(Addr.TYPE, type); 732 733 final Uri uri = Uri.parse("content://mms/" + msgId + "/addr"); 734 SqliteWrapper.insert(mContext, mContentResolver, uri, values); 735 } 736 } 737 getPartContentType(final PduPart part)738 private static String getPartContentType(final PduPart part) { 739 return part.getContentType() == null ? null : toIsoString(part.getContentType()); 740 } 741 getValues(final PduPart part, final ContentValues values)742 private static void getValues(final PduPart part, final ContentValues values) { 743 byte[] bytes = part.getFilename(); 744 if (bytes != null) { 745 values.put(Part.FILENAME, new String(bytes)); 746 } 747 748 bytes = part.getName(); 749 if (bytes != null) { 750 values.put(Part.NAME, new String(bytes)); 751 } 752 753 bytes = part.getContentDisposition(); 754 if (bytes != null) { 755 values.put(Part.CONTENT_DISPOSITION, toIsoString(bytes)); 756 } 757 758 bytes = part.getContentId(); 759 if (bytes != null) { 760 values.put(Part.CONTENT_ID, toIsoString(bytes)); 761 } 762 763 bytes = part.getContentLocation(); 764 if (bytes != null) { 765 values.put(Part.CONTENT_LOCATION, toIsoString(bytes)); 766 } 767 } 768 persistPart(final PduPart part, final long msgId, final Map<Uri, InputStream> preOpenedFiles)769 public Uri persistPart(final PduPart part, final long msgId, 770 final Map<Uri, InputStream> preOpenedFiles) throws MmsException { 771 final Uri uri = Uri.parse("content://mms/" + msgId + "/part"); 772 final ContentValues values = new ContentValues(8); 773 774 final int charset = part.getCharset(); 775 if (charset != 0) { 776 values.put(Part.CHARSET, charset); 777 } 778 779 String contentType = getPartContentType(part); 780 final byte[] data = part.getData(); 781 782 if (LOCAL_LOGV) { 783 LogUtil.v(TAG, "PduPersister.persistPart part: " + uri + " contentType: " + 784 contentType); 785 } 786 787 if (contentType != null) { 788 // There is no "image/jpg" in Android (and it's an invalid mimetype). 789 // Change it to "image/jpeg" 790 if (ContentType.IMAGE_JPG.equals(contentType)) { 791 contentType = ContentType.IMAGE_JPEG; 792 } 793 794 // On somes phones, a vcard comes in as text/plain instead of text/v-card. 795 // Fix it if necessary. 796 if (ContentType.TEXT_PLAIN.equals(contentType) && data != null) { 797 // There might be a more efficient way to just check the beginning of the string 798 // without encoding the whole thing, but we're concerned that with various 799 // characters sets, just comparing the byte data to BEGIN_VCARD would not be 800 // reliable. 801 final String encodedDataString = new EncodedStringValue(charset, data).getString(); 802 if (encodedDataString != null && encodedDataString.startsWith(BEGIN_VCARD)) { 803 contentType = ContentType.TEXT_VCARD; 804 part.setContentType(contentType.getBytes()); 805 if (LogUtil.isLoggable(TAG, LogUtil.DEBUG)) { 806 LogUtil.d(TAG, "PduPersister.persistPart part: " + uri + " contentType: " + 807 contentType + " changing to vcard"); 808 } 809 } 810 } 811 812 values.put(Part.CONTENT_TYPE, contentType); 813 // To ensure the SMIL part is always the first part. 814 if (ContentType.APP_SMIL.equals(contentType)) { 815 values.put(Part.SEQ, -1); 816 } 817 } else { 818 throw new MmsException("MIME type of the part must be set."); 819 } 820 821 getValues(part, values); 822 823 Uri res = null; 824 825 try { 826 res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); 827 } catch (IllegalStateException e) { 828 // Currently the MMS provider throws an IllegalStateException when it's out of space 829 LogUtil.e(TAG, "SqliteWrapper.insert threw: ", e); 830 } 831 832 if (res == null) { 833 throw new MmsException("Failed to persist part, return null."); 834 } 835 836 persistData(part, res, contentType, preOpenedFiles); 837 // After successfully store the data, we should update 838 // the dataUri of the part. 839 part.setDataUri(res); 840 841 return res; 842 } 843 844 /** 845 * Save data of the part into storage. The source data may be given 846 * by a byte[] or a Uri. If it's a byte[], directly save it 847 * into storage, otherwise load source data from the dataUri and then 848 * save it. If the data is an image, we may scale down it according 849 * to user preference. 850 * 851 * @param part The PDU part which contains data to be saved. 852 * @param uri The URI of the part. 853 * @param contentType The MIME type of the part. 854 * @param preOpenedFiles if not null, a map of preopened InputStreams for the parts. 855 * @throws MmsException Cannot find source data or error occurred 856 * while saving the data. 857 */ persistData(final PduPart part, final Uri uri, final String contentType, final Map<Uri, InputStream> preOpenedFiles)858 private void persistData(final PduPart part, final Uri uri, 859 final String contentType, final Map<Uri, InputStream> preOpenedFiles) 860 throws MmsException { 861 OutputStream os = null; 862 InputStream is = null; 863 DrmConvertSession drmConvertSession = null; 864 Uri dataUri = null; 865 String path = null; 866 867 try { 868 final byte[] data = part.getData(); 869 final int charset = part.getCharset(); 870 if (ContentType.TEXT_PLAIN.equals(contentType) 871 || ContentType.APP_SMIL.equals(contentType) 872 || ContentType.TEXT_HTML.equals(contentType)) { 873 // Some phone could send MMS with a text part having empty data 874 // Let's just skip those parts. 875 // EncodedStringValue() throws NPE if data is empty 876 if (data != null) { 877 final ContentValues cv = new ContentValues(); 878 cv.put(Mms.Part.TEXT, new EncodedStringValue(charset, data).getString()); 879 if (mContentResolver.update(uri, cv, null, null) != 1) { 880 throw new MmsException("unable to update " + uri.toString()); 881 } 882 } 883 } else { 884 final boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType); 885 if (isDrm) { 886 if (uri != null) { 887 try { 888 path = convertUriToPath(mContext, uri); 889 if (LOCAL_LOGV) { 890 LogUtil.v(TAG, "drm uri: " + uri + " path: " + path); 891 } 892 final File f = new File(path); 893 final long len = f.length(); 894 if (LOCAL_LOGV) { 895 LogUtil.v(TAG, "drm path: " + path + " len: " + len); 896 } 897 if (len > 0) { 898 // we're not going to re-persist and re-encrypt an already 899 // converted drm file 900 return; 901 } 902 } catch (final Exception e) { 903 Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e); 904 } 905 } 906 // We haven't converted the file yet, start the conversion 907 drmConvertSession = DrmConvertSession.open(mContext, contentType); 908 if (drmConvertSession == null) { 909 throw new MmsException("Mimetype " + contentType + 910 " can not be converted."); 911 } 912 } 913 // uri can look like: 914 // content://mms/part/98 915 os = mContentResolver.openOutputStream(uri); 916 if (os == null) { 917 throw new MmsException("Failed to create output stream on " + uri); 918 } 919 if (data == null) { 920 dataUri = part.getDataUri(); 921 if ((dataUri == null) || (dataUri.equals(uri))) { 922 Log.w(TAG, "Can't find data for this part."); 923 return; 924 } 925 // dataUri can look like: 926 // content://com.google.android.gallery3d.provider/picasa/item/5720646660183715 927 if (preOpenedFiles != null && preOpenedFiles.containsKey(dataUri)) { 928 is = preOpenedFiles.get(dataUri); 929 } 930 if (is == null) { 931 is = mContentResolver.openInputStream(dataUri); 932 } 933 if (is == null) { 934 throw new MmsException("Failed to create input stream on " + dataUri); 935 } 936 if (LOCAL_LOGV) { 937 LogUtil.v(TAG, "Saving data to: " + uri); 938 } 939 940 final byte[] buffer = new byte[8192]; 941 for (int len = 0; (len = is.read(buffer)) != -1; ) { 942 if (!isDrm) { 943 os.write(buffer, 0, len); 944 } else { 945 final byte[] convertedData = drmConvertSession.convert(buffer, len); 946 if (convertedData != null) { 947 os.write(convertedData, 0, convertedData.length); 948 } else { 949 throw new MmsException("Error converting drm data."); 950 } 951 } 952 } 953 } else { 954 if (LOCAL_LOGV) { 955 LogUtil.v(TAG, "Saving data to: " + uri); 956 } 957 if (!isDrm) { 958 os.write(data); 959 } else { 960 dataUri = uri; 961 final byte[] convertedData = drmConvertSession.convert(data, data.length); 962 if (convertedData != null) { 963 os.write(convertedData, 0, convertedData.length); 964 } else { 965 throw new MmsException("Error converting drm data."); 966 } 967 } 968 } 969 } 970 } catch (final SQLiteException e) { 971 Log.e(TAG, "Failed with SQLiteException.", e); 972 throw new MmsException(e); 973 } catch (final FileNotFoundException e) { 974 Log.e(TAG, "Failed to open Input/Output stream.", e); 975 throw new MmsException(e); 976 } catch (final IOException e) { 977 Log.e(TAG, "Failed to read/write data.", e); 978 throw new MmsException(e); 979 } finally { 980 if (os != null) { 981 try { 982 os.close(); 983 } catch (final IOException e) { 984 Log.e(TAG, "IOException while closing: " + os, e); 985 } // Ignore 986 } 987 if (is != null) { 988 try { 989 is.close(); 990 } catch (final IOException e) { 991 Log.e(TAG, "IOException while closing: " + is, e); 992 } // Ignore 993 } 994 if (drmConvertSession != null) { 995 drmConvertSession.close(path); 996 997 // Reset the permissions on the encrypted part file so everyone has only read 998 // permission. 999 final File f = new File(path); 1000 final ContentValues values = new ContentValues(0); 1001 SqliteWrapper.update(mContext, mContentResolver, 1002 Uri.parse("content://mms/resetFilePerm/" + f.getName()), 1003 values, null, null); 1004 } 1005 } 1006 } 1007 1008 /** 1009 * This method expects uri in the following format 1010 * content://media/<table_name>/<row_index> (or) 1011 * file://sdcard/test.mp4 1012 * http://test.com/test.mp4 1013 * 1014 * Here <table_name> shall be "video" or "audio" or "images" 1015 * <row_index> the index of the content in given table 1016 */ convertUriToPath(final Context context, final Uri uri)1017 public static String convertUriToPath(final Context context, final Uri uri) { 1018 String path = null; 1019 if (null != uri) { 1020 final String scheme = uri.getScheme(); 1021 if (null == scheme || scheme.equals("") || 1022 scheme.equals(ContentResolver.SCHEME_FILE)) { 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 1419 // message (the FROM field above) in addition to the other people the message 1420 // was addressed TO (or CC fields to address group messaging compatibility 1421 // issues with devices that place numbers in this field). Typically our own 1422 // number is in the TO/CC field so we have to remove it in loadRecipients. 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 for (final String number : numbers) { 1586 // Only add numbers which aren't my own number. 1587 if (TextUtils.isEmpty(selfNumber) || !PhoneNumberUtils.compare(number, selfNumber)) { 1588 if (!recipients.contains(number)) { 1589 // Only add numbers which aren't already included. 1590 recipients.add(number); 1591 } 1592 } 1593 } 1594 } 1595 1596 /** 1597 * Move a PDU object from one location to another. 1598 * 1599 * @param from Specify the PDU object to be moved. 1600 * @param to The destination location, should be one of the following: 1601 * "content://mms/inbox", "content://mms/sent", 1602 * "content://mms/drafts", "content://mms/outbox", 1603 * "content://mms/trash". 1604 * @return New Uri of the moved PDU. 1605 * @throws MmsException Error occurred while moving the message. 1606 */ move(final Uri from, final Uri to)1607 public Uri move(final Uri from, final Uri to) throws MmsException { 1608 // Check whether the 'msgId' has been assigned a valid value. 1609 final long msgId = ContentUris.parseId(from); 1610 if (msgId == -1L) { 1611 throw new MmsException("Error! ID of the message: -1."); 1612 } 1613 1614 // Get corresponding int value of destination box. 1615 final Integer msgBox = MESSAGE_BOX_MAP.get(to); 1616 if (msgBox == null) { 1617 throw new MmsException( 1618 "Bad destination, must be one of " 1619 + "content://mms/inbox, content://mms/sent, " 1620 + "content://mms/drafts, content://mms/outbox, " 1621 + "content://mms/temp." 1622 ); 1623 } 1624 1625 final ContentValues values = new ContentValues(1); 1626 values.put(Mms.MESSAGE_BOX, msgBox); 1627 SqliteWrapper.update(mContext, mContentResolver, from, values, null, null); 1628 return ContentUris.withAppendedId(to, msgId); 1629 } 1630 1631 /** 1632 * Wrap a byte[] into a String. 1633 */ toIsoString(final byte[] bytes)1634 public static String toIsoString(final byte[] bytes) { 1635 try { 1636 return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); 1637 } catch (final UnsupportedEncodingException e) { 1638 // Impossible to reach here! 1639 Log.e(TAG, "ISO_8859_1 must be supported!", e); 1640 return ""; 1641 } 1642 } 1643 1644 /** 1645 * Unpack a given String into a byte[]. 1646 */ getBytes(final String data)1647 public static byte[] getBytes(final String data) { 1648 try { 1649 return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1); 1650 } catch (final UnsupportedEncodingException e) { 1651 // Impossible to reach here! 1652 Log.e(TAG, "ISO_8859_1 must be supported!", e); 1653 return new byte[0]; 1654 } 1655 } 1656 1657 /** 1658 * Remove all objects in the temporary path. 1659 */ release()1660 public void release() { 1661 final Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI); 1662 SqliteWrapper.delete(mContext, mContentResolver, uri, null, null); 1663 } 1664 1665 /** 1666 * Find all messages to be sent or downloaded before certain time. 1667 */ getPendingMessages(final long dueTime)1668 public Cursor getPendingMessages(final long dueTime) { 1669 final Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); 1670 uriBuilder.appendQueryParameter("protocol", "mms"); 1671 1672 final String selection = PendingMessages.ERROR_TYPE + " < ?" 1673 + " AND " + PendingMessages.DUE_TIME + " <= ?"; 1674 1675 final String[] selectionArgs = new String[] { 1676 String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT), 1677 String.valueOf(dueTime) 1678 }; 1679 1680 return SqliteWrapper.query(mContext, mContentResolver, 1681 uriBuilder.build(), null, selection, selectionArgs, 1682 PendingMessages.DUE_TIME); 1683 } 1684 } 1685