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