1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.providers.telephony; 18 19 import static com.android.providers.telephony.SmsProvider.NO_ERROR_CODE; 20 21 import android.annotation.NonNull; 22 import android.content.BroadcastReceiver; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.content.SharedPreferences; 28 import android.content.pm.PackageManager; 29 import android.database.Cursor; 30 import android.database.DatabaseErrorHandler; 31 import android.database.DefaultDatabaseErrorHandler; 32 import android.database.sqlite.SQLiteDatabase; 33 import android.database.sqlite.SQLiteException; 34 import android.database.sqlite.SQLiteOpenHelper; 35 import android.os.FileUtils; 36 import android.os.storage.StorageManager; 37 import android.preference.PreferenceManager; 38 import android.provider.BaseColumns; 39 import android.provider.Telephony; 40 import android.provider.Telephony.Mms; 41 import android.provider.Telephony.Mms.Addr; 42 import android.provider.Telephony.Mms.Part; 43 import android.provider.Telephony.Mms.Rate; 44 import android.provider.Telephony.MmsSms; 45 import android.provider.Telephony.MmsSms.PendingMessages; 46 import android.provider.Telephony.Sms; 47 import android.provider.Telephony.Sms.Intents; 48 import android.provider.Telephony.Threads; 49 import android.telephony.AnomalyReporter; 50 import android.telephony.SubscriptionManager; 51 import android.text.format.DateFormat; 52 import android.util.Log; 53 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.internal.telephony.PhoneFactory; 56 import com.android.internal.telephony.TelephonyStatsLog; 57 import com.android.internal.telephony.flags.Flags; 58 59 import com.google.android.mms.pdu.EncodedStringValue; 60 import com.google.android.mms.pdu.PduHeaders; 61 62 import java.io.File; 63 import java.io.FileInputStream; 64 import java.io.IOException; 65 import java.io.InputStream; 66 import java.util.ArrayList; 67 import java.util.HashSet; 68 import java.util.Iterator; 69 import java.util.List; 70 import java.util.UUID; 71 import java.util.concurrent.atomic.AtomicBoolean; 72 /** 73 * A {@link SQLiteOpenHelper} that handles DB management of SMS and MMS tables. 74 * 75 * From N, SMS and MMS tables are split into two groups with different levels of encryption. 76 * - the raw table, which lives inside DE(Device Encrypted) storage. 77 * - all other tables, which lives under CE(Credential Encrypted) storage. 78 * 79 * All tables are created by this class in the same database that can live either in DE or CE 80 * storage. But not all tables in the same database should be used. Only DE tables should be used 81 * in the database created in DE and only CE tables should be used in the database created in CE. 82 * The only exception is a non-FBE device migrating from M to N, in which case the DE and CE tables 83 * will actually live inside the same storage/database. 84 * 85 * This class provides methods to create instances that manage databases in different storage. 86 * It's the responsibility of the clients of this class to make sure the right instance is 87 * used to access tables that are supposed to live inside the intended storage. 88 */ 89 public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { 90 private static final String TAG = "MmsSmsDatabaseHelper"; 91 private static final int SECURITY_EXCEPTION = TelephonyStatsLog 92 .MMS_SMS_DATABASE_HELPER_ON_UPGRADE_FAILED__FAILURE_CODE__FAILURE_SECURITY_EXCEPTION; 93 private static final int FAILURE_UNKNOWN = TelephonyStatsLog 94 .MMS_SMS_DATABASE_HELPER_ON_UPGRADE_FAILED__FAILURE_CODE__FAILURE_UNKNOWN; 95 private static final int SQL_EXCEPTION = TelephonyStatsLog 96 .MMS_SMS_DATABASE_HELPER_ON_UPGRADE_FAILED__FAILURE_CODE__FAILURE_SQL_EXCEPTION; 97 private static final int IO_EXCEPTION = TelephonyStatsLog 98 .MMS_SMS_DATABASE_HELPER_ON_UPGRADE_FAILED__FAILURE_CODE__FAILURE_IO_EXCEPTION; 99 100 private static final String SMS_UPDATE_THREAD_READ_BODY = 101 " UPDATE threads SET read = " + 102 " CASE (SELECT COUNT(*)" + 103 " FROM sms" + 104 " WHERE " + Sms.READ + " = 0" + 105 " AND " + Sms.THREAD_ID + " = threads._id)" + 106 " WHEN 0 THEN 1" + 107 " ELSE 0" + 108 " END" + 109 " WHERE threads._id = new." + Sms.THREAD_ID + "; "; 110 111 private static final String UPDATE_THREAD_COUNT_ON_NEW = 112 " UPDATE threads SET message_count = " + 113 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 114 " ON threads._id = " + Sms.THREAD_ID + 115 " WHERE " + Sms.THREAD_ID + " = new.thread_id" + 116 " AND sms." + Sms.TYPE + " != 3) + " + 117 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 118 " ON threads._id = " + Mms.THREAD_ID + 119 " WHERE " + Mms.THREAD_ID + " = new.thread_id" + 120 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 121 " AND " + Mms.MESSAGE_BOX + " != 3) " + 122 " WHERE threads._id = new.thread_id; "; 123 124 private static final String UPDATE_THREAD_COUNT_ON_OLD = 125 " UPDATE threads SET message_count = " + 126 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 127 " ON threads._id = " + Sms.THREAD_ID + 128 " WHERE " + Sms.THREAD_ID + " = old.thread_id" + 129 " AND sms." + Sms.TYPE + " != 3) + " + 130 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 131 " ON threads._id = " + Mms.THREAD_ID + 132 " WHERE " + Mms.THREAD_ID + " = old.thread_id" + 133 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 134 " AND " + Mms.MESSAGE_BOX + " != 3) " + 135 " WHERE threads._id = old.thread_id; "; 136 137 private static final String SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE = 138 "BEGIN" + 139 " UPDATE threads SET" + 140 " date = (strftime('%s','now') * 1000), " + 141 " snippet = new." + Sms.BODY + ", " + 142 " snippet_cs = 0" + 143 " WHERE threads._id = new." + Sms.THREAD_ID + "; " + 144 UPDATE_THREAD_COUNT_ON_NEW + 145 SMS_UPDATE_THREAD_READ_BODY + 146 "END;"; 147 148 private static final String PDU_UPDATE_THREAD_CONSTRAINTS = 149 " WHEN new." + Mms.MESSAGE_TYPE + "=" + 150 PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + 151 " OR new." + Mms.MESSAGE_TYPE + "=" + 152 PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + 153 " OR new." + Mms.MESSAGE_TYPE + "=" + 154 PduHeaders.MESSAGE_TYPE_SEND_REQ + " "; 155 156 // When looking in the pdu table for unread messages, only count messages that 157 // are displayed to the user. The constants are defined in PduHeaders and could be used 158 // here, but the string "(m_type=132 OR m_type=130 OR m_type=128)" is used throughout this 159 // file and so it is used here to be consistent. 160 // m_type=128 = MESSAGE_TYPE_SEND_REQ 161 // m_type=130 = MESSAGE_TYPE_NOTIFICATION_IND 162 // m_type=132 = MESSAGE_TYPE_RETRIEVE_CONF 163 private static final String PDU_UPDATE_THREAD_READ_BODY = 164 " UPDATE threads SET read = " + 165 " CASE (SELECT COUNT(*)" + 166 " FROM " + MmsProvider.TABLE_PDU + 167 " WHERE " + Mms.READ + " = 0" + 168 " AND " + Mms.THREAD_ID + " = threads._id " + 169 " AND (m_type=132 OR m_type=130 OR m_type=128)) " + 170 " WHEN 0 THEN 1" + 171 " ELSE 0" + 172 " END" + 173 " WHERE threads._id = new." + Mms.THREAD_ID + "; "; 174 175 private static final String PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE = 176 "BEGIN" + 177 " UPDATE threads SET" + 178 " date = (strftime('%s','now') * 1000), " + 179 " snippet = new." + Mms.SUBJECT + ", " + 180 " snippet_cs = new." + Mms.SUBJECT_CHARSET + 181 " WHERE threads._id = new." + Mms.THREAD_ID + "; " + 182 UPDATE_THREAD_COUNT_ON_NEW + 183 PDU_UPDATE_THREAD_READ_BODY + 184 "END;"; 185 186 private static final String UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE = 187 " UPDATE threads SET snippet = " + 188 " (SELECT snippet FROM" + 189 " (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" + 190 " UNION SELECT date, body AS snippet, thread_id FROM sms)" + 191 " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " + 192 " WHERE threads._id = OLD.thread_id; " + 193 " UPDATE threads SET snippet_cs = " + 194 " (SELECT snippet_cs FROM" + 195 " (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" + 196 " UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" + 197 " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " + 198 " WHERE threads._id = OLD.thread_id; "; 199 200 201 // When a part is inserted, if it is not text/plain or application/smil 202 // (which both can exist with text-only MMSes), then there is an attachment. 203 // Set has_attachment=1 in the threads table for the thread in question. 204 private static final String PART_UPDATE_THREADS_ON_INSERT_TRIGGER = 205 "CREATE TRIGGER update_threads_on_insert_part " + 206 " AFTER INSERT ON part " + 207 " WHEN new.ct != 'text/plain' AND new.ct != 'application/smil' " + 208 " BEGIN " + 209 " UPDATE threads SET has_attachment=1 WHERE _id IN " + 210 " (SELECT pdu.thread_id FROM part JOIN pdu ON pdu._id=part.mid " + 211 " WHERE part._id=new._id LIMIT 1); " + 212 " END"; 213 214 // When the 'mid' column in the part table is updated, we need to run the trigger to update 215 // the threads table's has_attachment column, if the part is an attachment. 216 private static final String PART_UPDATE_THREADS_ON_UPDATE_TRIGGER = 217 "CREATE TRIGGER update_threads_on_update_part " + 218 " AFTER UPDATE of " + Part.MSG_ID + " ON part " + 219 " WHEN new.ct != 'text/plain' AND new.ct != 'application/smil' " + 220 " BEGIN " + 221 " UPDATE threads SET has_attachment=1 WHERE _id IN " + 222 " (SELECT pdu.thread_id FROM part JOIN pdu ON pdu._id=part.mid " + 223 " WHERE part._id=new._id LIMIT 1); " + 224 " END"; 225 226 227 // When a part is deleted (with the same non-text/SMIL constraint as when 228 // we set has_attachment), update the threads table for all threads. 229 // Unfortunately we cannot update only the thread that the part was 230 // attached to, as it is possible that the part has been orphaned and 231 // the message it was attached to is already gone. 232 private static final String PART_UPDATE_THREADS_ON_DELETE_TRIGGER = 233 "CREATE TRIGGER update_threads_on_delete_part " + 234 " AFTER DELETE ON part " + 235 " WHEN old.ct != 'text/plain' AND old.ct != 'application/smil' " + 236 " BEGIN " + 237 " UPDATE threads SET has_attachment = " + 238 " CASE " + 239 " (SELECT COUNT(*) FROM part JOIN pdu " + 240 " WHERE pdu.thread_id = threads._id " + 241 " AND part.ct != 'text/plain' AND part.ct != 'application/smil' " + 242 " AND part.mid = pdu._id)" + 243 " WHEN 0 THEN 0 " + 244 " ELSE 1 " + 245 " END; " + 246 " END"; 247 248 // When the 'thread_id' column in the pdu table is updated, we need to run the trigger to update 249 // the threads table's has_attachment column, if the message has an attachment in 'part' table 250 private static final String PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER = 251 "CREATE TRIGGER update_threads_on_update_pdu " + 252 " AFTER UPDATE of thread_id ON pdu " + 253 " BEGIN " + 254 " UPDATE threads SET has_attachment=1 WHERE _id IN " + 255 " (SELECT pdu.thread_id FROM part JOIN pdu " + 256 " WHERE part.ct != 'text/plain' AND part.ct != 'application/smil' " + 257 " AND part.mid = pdu._id);" + 258 " END"; 259 260 private static MmsSmsDatabaseHelper sDeInstance = null; 261 private static MmsSmsDatabaseHelper sCeInstance = null; 262 private static MmsSmsDatabaseErrorHandler sDbErrorHandler = null; 263 264 private static final String[] BIND_ARGS_NONE = new String[0]; 265 266 private static boolean sTriedAutoIncrement = false; 267 private static boolean sFakeLowStorageTest = false; // for testing only 268 269 static final String DATABASE_NAME = "mmssms.db"; 270 static final int DATABASE_VERSION = 69; 271 private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000; 272 273 private final Context mContext; 274 private LowStorageMonitor mLowStorageMonitor; 275 private final List<String> mDatabaseReadOpeningInfos = new ArrayList<>(); 276 private final List<String> mDatabaseWriteOpeningInfos = new ArrayList<>(); 277 private final Object mDatabaseOpeningInfoLock = new Object(); 278 private static final int MAX_DATABASE_OPENING_INFO_STORED = 10; 279 280 // SharedPref key used to check if initial create has been done (if onCreate has already been 281 // called once) 282 private static final String INITIAL_CREATE_DONE = "initial_create_done"; 283 // cache for INITIAL_CREATE_DONE shared pref so access to it can be avoided when possible 284 private static AtomicBoolean sInitialCreateDone = new AtomicBoolean(false); 285 286 private static final UUID CREATE_CALLED_MULTIPLE_TIMES_UUID = UUID.fromString( 287 "6ead002e-c001-4c05-9bca-67d7c4e29782"); 288 private static final UUID DATABASE_OPENING_EXCEPTION_UUID = UUID.fromString( 289 "de3f61e1-ecd8-41ee-b059-9282b294b235"); 290 291 /** 292 * The primary purpose of this DatabaseErrorHandler is to broadcast an intent on corruption and 293 * print a Log.wtf so database corruption can be caught earlier. 294 */ 295 private static class MmsSmsDatabaseErrorHandler implements DatabaseErrorHandler { 296 private DefaultDatabaseErrorHandler mDefaultDatabaseErrorHandler 297 = new DefaultDatabaseErrorHandler(); 298 private Context mContext; 299 MmsSmsDatabaseErrorHandler(Context context)300 MmsSmsDatabaseErrorHandler(Context context) { 301 mContext = context; 302 } 303 304 @Override onCorruption(SQLiteDatabase dbObj)305 public void onCorruption(SQLiteDatabase dbObj) { 306 String logMsg = "Corruption reported by sqlite on database: " + dbObj.getPath(); 307 localLogWtf(logMsg); 308 sendDbLostIntent(mContext, true); 309 // Let the default error handler take other actions 310 mDefaultDatabaseErrorHandler.onCorruption(dbObj); 311 } 312 } 313 314 @VisibleForTesting MmsSmsDatabaseHelper(Context context, MmsSmsDatabaseErrorHandler dbErrorHandler)315 MmsSmsDatabaseHelper(Context context, MmsSmsDatabaseErrorHandler dbErrorHandler) { 316 super(context, DATABASE_NAME, null, DATABASE_VERSION, dbErrorHandler); 317 mContext = context; 318 // Memory optimization - close idle connections after 30s of inactivity 319 setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS); 320 setWriteAheadLoggingEnabled(false); 321 try { 322 PhoneFactory.addLocalLog(TAG, 64); 323 } catch (IllegalArgumentException e) { 324 // ignore 325 } 326 } 327 getDbErrorHandler(Context context)328 private static synchronized MmsSmsDatabaseErrorHandler getDbErrorHandler(Context context) { 329 if (sDbErrorHandler == null) { 330 sDbErrorHandler = new MmsSmsDatabaseErrorHandler(context); 331 } 332 return sDbErrorHandler; 333 } 334 sendDbLostIntent(Context context, boolean isCorrupted)335 private static void sendDbLostIntent(Context context, boolean isCorrupted) { 336 // Broadcast ACTION_SMS_MMS_DB_LOST 337 Intent intent = new Intent(Sms.Intents.ACTION_SMS_MMS_DB_LOST); 338 intent.putExtra(Sms.Intents.EXTRA_IS_CORRUPTED, isCorrupted); 339 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 340 context.sendBroadcast(intent); 341 } 342 /** 343 * Returns a singleton helper for the combined MMS and SMS database in device encrypted storage. 344 */ getInstanceForDe(Context context)345 /* package */ static synchronized MmsSmsDatabaseHelper getInstanceForDe(Context context) { 346 if (sDeInstance == null) { 347 Context deContext = ProviderUtil.getDeviceEncryptedContext(context); 348 sDeInstance = new MmsSmsDatabaseHelper(deContext, getDbErrorHandler(deContext)); 349 } 350 return sDeInstance; 351 } 352 353 /** 354 * Returns a singleton helper for the combined MMS and SMS database in credential encrypted 355 * storage. If FBE is not available, use the device encrypted storage instead. 356 */ getInstanceForCe(Context context)357 /* package */ static synchronized MmsSmsDatabaseHelper getInstanceForCe(Context context) { 358 if (sCeInstance == null) { 359 if (StorageManager.isFileEncrypted()) { 360 Context ceContext = ProviderUtil.getCredentialEncryptedContext(context); 361 sCeInstance = new MmsSmsDatabaseHelper(ceContext, getDbErrorHandler(ceContext)); 362 } else { 363 sCeInstance = getInstanceForDe(context); 364 } 365 } 366 return sCeInstance; 367 } 368 369 /** 370 * Look through all the recipientIds referenced by the threads and then delete any 371 * unreferenced rows from the canonical_addresses table. 372 */ removeUnferencedCanonicalAddresses(SQLiteDatabase db)373 private static void removeUnferencedCanonicalAddresses(SQLiteDatabase db) { 374 Cursor c = db.query(MmsSmsProvider.TABLE_THREADS, new String[] { "recipient_ids" }, 375 null, null, null, null, null); 376 if (c != null) { 377 try { 378 if (c.getCount() == 0) { 379 // no threads, delete all addresses 380 int rows = db.delete("canonical_addresses", null, null); 381 } else { 382 // Find all the referenced recipient_ids from the threads. recipientIds is 383 // a space-separated list of recipient ids: "1 14 21" 384 HashSet<Integer> recipientIds = new HashSet<Integer>(); 385 while (c.moveToNext()) { 386 String[] recips = c.getString(0).split(" "); 387 for (String recip : recips) { 388 try { 389 int recipientId = Integer.parseInt(recip); 390 recipientIds.add(recipientId); 391 } catch (Exception e) { 392 } 393 } 394 } 395 // Now build a selection string of all the unique recipient ids 396 StringBuilder sb = new StringBuilder(); 397 Iterator<Integer> iter = recipientIds.iterator(); 398 sb.append("_id NOT IN ("); 399 while (iter.hasNext()) { 400 sb.append(iter.next()); 401 if (iter.hasNext()) { 402 sb.append(","); 403 } 404 } 405 sb.append(")"); 406 int rows = db.delete("canonical_addresses", sb.toString(), null); 407 } 408 } finally { 409 c.close(); 410 } 411 } 412 } 413 updateThread(SQLiteDatabase db, long thread_id)414 public static void updateThread(SQLiteDatabase db, long thread_id) { 415 if (thread_id < 0) { 416 updateThreads(db, null, null); 417 return; 418 } 419 updateThreads(db, "(thread_id = ?)", new String[]{ String.valueOf(thread_id) }); 420 } 421 422 /** 423 * Update all threads containing SMS matching the 'where' condition. Note that the condition 424 * is applied to individual messages in the sms table, NOT the threads table. 425 */ updateThreads(SQLiteDatabase db, String where, String[] whereArgs)426 public static void updateThreads(SQLiteDatabase db, String where, String[] whereArgs) { 427 if (where == null) { 428 where = "1"; 429 } 430 if (whereArgs == null) { 431 whereArgs = BIND_ARGS_NONE; 432 } 433 db.beginTransaction(); 434 try { 435 // Delete rows in the threads table if 436 // there are no more messages attached to it in either 437 // the sms or pdu tables. 438 // Note that we do this regardless of whether they match 'where'. 439 int rows = db.delete(MmsSmsProvider.TABLE_THREADS, 440 "_id NOT IN (" + 441 " SELECT DISTINCT thread_id FROM sms WHERE thread_id IS NOT NULL" + 442 " UNION" + 443 " SELECT DISTINCT thread_id FROM pdu WHERE thread_id IS NOT NULL)", 444 null); 445 if (rows > 0) { 446 // If this deleted a row, let's remove orphaned canonical_addresses 447 removeUnferencedCanonicalAddresses(db); 448 } 449 450 // Update the message count in the threads table as the sum 451 // of all messages in both the sms and pdu tables. 452 db.execSQL( 453 " UPDATE threads" + 454 " SET message_count = (" + 455 " SELECT COUNT(sms._id) FROM sms" + 456 " WHERE " + Sms.THREAD_ID + " = threads._id" + 457 " AND sms." + Sms.TYPE + " != 3" + 458 " ) + (" + 459 " SELECT COUNT(pdu._id) FROM pdu" + 460 " WHERE " + Mms.THREAD_ID + " = threads._id" + 461 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 462 " AND " + Mms.MESSAGE_BOX + " != 3" + 463 " )" + 464 " WHERE EXISTS (" + 465 " SELECT _id" + 466 " FROM sms" + 467 " WHERE thread_id = threads._id" + 468 " AND (" + where + ")" + 469 " LIMIT 1" + 470 " );", 471 whereArgs); 472 473 // Update the date and the snippet (and its character set) in 474 // the threads table to be that of the most recent message in 475 // the thread. 476 db.execSQL( 477 " WITH matches AS (" + 478 " SELECT date * 1000 AS date, sub AS snippet, sub_cs AS snippet_cs, thread_id" + 479 " FROM pdu" + 480 " WHERE thread_id = threads._id" + 481 " UNION" + 482 " SELECT date, body AS snippet, 0 AS snippet_cs, thread_id" + 483 " FROM sms" + 484 " WHERE thread_id = threads._id" + 485 " ORDER BY date DESC" + 486 " LIMIT 1" + 487 " )" + 488 " UPDATE threads" + 489 " SET date = (SELECT date FROM matches)," + 490 " snippet = (SELECT snippet FROM matches)," + 491 " snippet_cs = (SELECT snippet_cs FROM matches)" + 492 " WHERE EXISTS (" + 493 " SELECT _id" + 494 " FROM sms" + 495 " WHERE thread_id = threads._id" + 496 " AND (" + where + ")" + 497 " LIMIT 1" + 498 " );", 499 whereArgs); 500 501 // Update the error column of the thread to indicate if there 502 // are any messages in it that have failed to send. 503 // First check to see if there are any messages with errors in this thread. 504 db.execSQL( 505 " UPDATE threads" + 506 " SET error = EXISTS (" + 507 " SELECT type" + 508 " FROM sms" + 509 " WHERE type=" + Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED + 510 " AND thread_id = threads._id" + 511 " )" + 512 " WHERE EXISTS (" + 513 " SELECT _id" + 514 " FROM sms" + 515 " WHERE thread_id = threads._id" + 516 " AND (" + where + ")" + 517 " LIMIT 1" + 518 " );", 519 whereArgs); 520 521 db.setTransactionSuccessful(); 522 } catch (Throwable ex) { 523 Log.e(TAG, ex.getMessage(), ex); 524 } finally { 525 db.endTransaction(); 526 } 527 } 528 deleteOneSms(SQLiteDatabase db, int message_id)529 public static int deleteOneSms(SQLiteDatabase db, int message_id) { 530 int thread_id = -1; 531 // Find the thread ID that the specified SMS belongs to. 532 Cursor c = db.query("sms", new String[] { "thread_id" }, 533 "_id=" + message_id, null, null, null, null); 534 if (c != null) { 535 if (c.moveToFirst()) { 536 thread_id = c.getInt(0); 537 } 538 c.close(); 539 } 540 541 // Delete the specified message. 542 int rows = db.delete("sms", "_id=" + message_id, null); 543 if (thread_id > 0) { 544 // Update its thread. 545 updateThread(db, thread_id); 546 } 547 return rows; 548 } 549 clearMmsParts()550 private void clearMmsParts() { 551 try { 552 String partsDirPath = mContext.getDir(MmsProvider.PARTS_DIR_NAME, 0) 553 .getCanonicalPath(); 554 localLog("clearMmsParts: removing all attachments from: " + partsDirPath); 555 File partsDir = new File(partsDirPath); 556 if (!FileUtils.deleteContents(partsDir)) { 557 localLogWtf("clearMmsParts: couldn't delete all attachments"); 558 } 559 } 560 catch (IOException e){ 561 Log.e(TAG, "clearMmsParts: failed " + e, e); 562 } 563 } 564 565 @Override onCreate(SQLiteDatabase db)566 public void onCreate(SQLiteDatabase db) { 567 localLog("onCreate: Creating all SMS-MMS tables."); 568 569 createMmsTables(db); 570 createSmsTables(db); 571 createCommonTables(db); 572 createCommonTriggers(db); 573 createMmsTriggers(db); 574 createWordsTables(db); 575 createIndices(db); 576 577 clearMmsParts(); // leave no dangling MMS attachments when rebuilding the DB 578 579 // if FBE is not supported, or if this onCreate is for CE partition database 580 if (!StorageManager.isFileEncrypted() 581 || (mContext != null && mContext.isCredentialProtectedStorage())) { 582 localLog("onCreate: broadcasting ACTION_SMS_MMS_DB_CREATED"); 583 // Broadcast ACTION_SMS_MMS_DB_CREATED 584 Intent intent = new Intent(Sms.Intents.ACTION_SMS_MMS_DB_CREATED); 585 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 586 587 if (isInitialCreateDone()) { 588 // this onCreate is called after onCreate was called once initially. The db file 589 // disappeared mysteriously? 590 AnomalyReporter.reportAnomaly(CREATE_CALLED_MULTIPLE_TIMES_UUID, 591 "MmsSmsDatabaseHelper: onCreate() was already " 592 + "called once earlier"); 593 intent.putExtra(Intents.EXTRA_IS_INITIAL_CREATE, false); 594 } else { 595 setInitialCreateDone(); 596 intent.putExtra(Intents.EXTRA_IS_INITIAL_CREATE, true); 597 } 598 599 mContext.sendBroadcast(intent); 600 } 601 } 602 localLog(String logMsg)603 private static void localLog(String logMsg) { 604 Log.d(TAG, logMsg); 605 PhoneFactory.localLog(TAG, logMsg); 606 } 607 localLogWtf(String logMsg)608 private static void localLogWtf(String logMsg) { 609 Log.wtf(TAG, logMsg); 610 PhoneFactory.localLog(TAG, logMsg); 611 } 612 isInitialCreateDone()613 private boolean isInitialCreateDone() { 614 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); 615 return sp.getBoolean(INITIAL_CREATE_DONE, false); 616 } 617 setInitialCreateDone()618 private void setInitialCreateDone() { 619 if (!sInitialCreateDone.getAndSet(true)) { 620 SharedPreferences.Editor editor 621 = PreferenceManager.getDefaultSharedPreferences(mContext).edit(); 622 editor.putBoolean(INITIAL_CREATE_DONE, true); 623 editor.commit(); 624 } 625 } 626 627 // When upgrading the database we need to populate the words 628 // table with the rows out of sms and part. populateWordsTable(SQLiteDatabase db)629 private void populateWordsTable(SQLiteDatabase db) { 630 final String TABLE_WORDS = "words"; 631 { 632 Cursor smsRows = db.query( 633 "sms", 634 new String[] { Sms._ID, Sms.BODY }, 635 null, 636 null, 637 null, 638 null, 639 null); 640 try { 641 if (smsRows != null) { 642 smsRows.moveToPosition(-1); 643 ContentValues cv = new ContentValues(); 644 while (smsRows.moveToNext()) { 645 cv.clear(); 646 647 long id = smsRows.getLong(0); // 0 for Sms._ID 648 String body = smsRows.getString(1); // 1 for Sms.BODY 649 650 cv.put(Telephony.MmsSms.WordsTable.ID, id); 651 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body); 652 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id); 653 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); 654 cv.put(MmsSms.WordsTable.SUBSCRIPTION_ID, -1); 655 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 656 } 657 } 658 } finally { 659 if (smsRows != null) { 660 smsRows.close(); 661 } 662 } 663 } 664 665 { 666 Cursor mmsRows = db.query( 667 "part", 668 new String[] { Part._ID, Part.TEXT }, 669 "ct = 'text/plain'", 670 null, 671 null, 672 null, 673 null); 674 try { 675 if (mmsRows != null) { 676 mmsRows.moveToPosition(-1); 677 ContentValues cv = new ContentValues(); 678 while (mmsRows.moveToNext()) { 679 cv.clear(); 680 681 long id = mmsRows.getLong(0); // 0 for Part._ID 682 String body = mmsRows.getString(1); // 1 for Part.TEXT 683 684 // we're using the row id of the part table row but we're also using ids 685 // from the sms table so this divides the space into two large chunks. 686 // The row ids from the part table start at 2 << 32. 687 cv.put(Telephony.MmsSms.WordsTable.ID, (2L << 32) + id); 688 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body); 689 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id); 690 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 2); 691 cv.put(MmsSms.WordsTable.SUBSCRIPTION_ID, -1); 692 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 693 } 694 } 695 } finally { 696 if (mmsRows != null) { 697 mmsRows.close(); 698 } 699 } 700 } 701 } 702 createWordsTables(SQLiteDatabase db)703 private void createWordsTables(SQLiteDatabase db) { 704 createWordsTables(db, -1, -1, -1); 705 } 706 createWordsTables( SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion)707 private void createWordsTables( 708 SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion) { 709 try { 710 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_WORDS); 711 db.execSQL("CREATE VIRTUAL TABLE words USING FTS3 (_id INTEGER PRIMARY KEY, index_text TEXT, source_id INTEGER, table_to_use INTEGER, sub_id INTEGER);"); 712 713 // monitor the sms table 714 // NOTE don't handle inserts using a trigger because it has an unwanted 715 // side effect: the value returned for the last row ends up being the 716 // id of one of the trigger insert not the original row insert. 717 // Handle inserts manually in the provider. 718 db.execSQL("CREATE TRIGGER IF NOT EXISTS sms_words_update AFTER UPDATE ON sms " 719 + "BEGIN UPDATE words SET index_text = NEW.body " 720 + "WHERE (source_id=NEW._id AND table_to_use=1); END;"); 721 db.execSQL("CREATE TRIGGER IF NOT EXISTS sms_words_delete AFTER DELETE ON sms " 722 + "BEGIN DELETE FROM words WHERE source_id = OLD._id AND table_to_use = 1; END;"); 723 724 populateWordsTable(db); 725 } catch (Exception ex) { 726 Log.e(TAG, "got exception creating words table: " + ex.toString()); 727 logException(ex, oldVersion, currentVersion, upgradeVersion); 728 } 729 } 730 createIndices(SQLiteDatabase db)731 private void createIndices(SQLiteDatabase db) { 732 createThreadIdIndex(db); 733 createThreadIdDateIndex(db); 734 createPartMidIndex(db); 735 createAddrMsgIdIndex(db); 736 } 737 createThreadIdIndex(SQLiteDatabase db)738 private void createThreadIdIndex(SQLiteDatabase db) { 739 createThreadIdIndex(db, -1, -1, -1); 740 } 741 createThreadIdIndex( SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion)742 private void createThreadIdIndex( 743 SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion) { 744 try { 745 db.execSQL("CREATE INDEX IF NOT EXISTS typeThreadIdIndex ON sms" + 746 " (type, thread_id);"); 747 } catch (Exception ex) { 748 Log.e(TAG, "got exception creating indices: " + ex.toString()); 749 logException(ex, oldVersion, currentVersion, upgradeVersion); 750 } 751 } 752 createThreadIdDateIndex(SQLiteDatabase db)753 private void createThreadIdDateIndex(SQLiteDatabase db) { 754 createThreadIdDateIndex(db, -1, -1, -1); 755 } 756 createThreadIdDateIndex( SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion)757 private void createThreadIdDateIndex( 758 SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion) { 759 try { 760 db.execSQL("CREATE INDEX IF NOT EXISTS threadIdDateIndex ON sms" + 761 " (thread_id, date);"); 762 } catch (Exception ex) { 763 Log.e(TAG, "got exception creating indices: " + ex.toString()); 764 logException(ex, oldVersion, currentVersion, upgradeVersion); 765 } 766 } 767 createPartMidIndex(SQLiteDatabase db)768 private void createPartMidIndex(SQLiteDatabase db) { 769 createPartMidIndex(db, -1, -1, -1); 770 } 771 createPartMidIndex( SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion)772 private void createPartMidIndex( 773 SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion) { 774 try { 775 db.execSQL("CREATE INDEX IF NOT EXISTS partMidIndex ON part (mid)"); 776 } catch (Exception ex) { 777 Log.e(TAG, "got exception creating indices: " + ex.toString()); 778 logException(ex, oldVersion, currentVersion, upgradeVersion); 779 } 780 } 781 createAddrMsgIdIndex(SQLiteDatabase db)782 private void createAddrMsgIdIndex(SQLiteDatabase db) { 783 createAddrMsgIdIndex(db, -1, -1, -1); 784 } 785 createAddrMsgIdIndex( SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion)786 private void createAddrMsgIdIndex( 787 SQLiteDatabase db, int oldVersion, int currentVersion, int upgradeVersion) { 788 try { 789 db.execSQL("CREATE INDEX IF NOT EXISTS addrMsgIdIndex ON addr (msg_id)"); 790 } catch (Exception ex) { 791 Log.e(TAG, "got exception creating indices: " + ex.toString()); 792 logException(ex, oldVersion, currentVersion, upgradeVersion); 793 } 794 } 795 796 797 @VisibleForTesting 798 public static String CREATE_ADDR_TABLE_STR = 799 "CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" + 800 Addr._ID + " INTEGER PRIMARY KEY," + 801 Addr.MSG_ID + " INTEGER," + 802 Addr.CONTACT_ID + " INTEGER," + 803 Addr.ADDRESS + " TEXT," + 804 Addr.TYPE + " INTEGER," + 805 Addr.CHARSET + " INTEGER," + 806 Addr.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" + 807 ");"; 808 809 @VisibleForTesting 810 public static String CREATE_PART_TABLE_STR = 811 "CREATE TABLE " + MmsProvider.TABLE_PART + " (" + 812 Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 813 Part.MSG_ID + " INTEGER," + 814 Part.SEQ + " INTEGER DEFAULT 0," + 815 Part.CONTENT_TYPE + " TEXT," + 816 Part.NAME + " TEXT," + 817 Part.CHARSET + " INTEGER," + 818 Part.CONTENT_DISPOSITION + " TEXT," + 819 Part.FILENAME + " TEXT," + 820 Part.CONTENT_ID + " TEXT," + 821 Part.CONTENT_LOCATION + " TEXT," + 822 Part.CT_START + " INTEGER," + 823 Part.CT_TYPE + " TEXT," + 824 Part._DATA + " TEXT," + 825 Part.TEXT + " TEXT," + 826 Part.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" + 827 ");"; 828 829 public static String CREATE_PDU_TABLE_STR = 830 "CREATE TABLE " + MmsProvider.TABLE_PDU + " (" + 831 Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 832 Mms.THREAD_ID + " INTEGER," + 833 Mms.DATE + " INTEGER," + 834 Mms.DATE_SENT + " INTEGER DEFAULT 0," + 835 Mms.MESSAGE_BOX + " INTEGER," + 836 Mms.READ + " INTEGER DEFAULT 0," + 837 Mms.MESSAGE_ID + " TEXT," + 838 Mms.SUBJECT + " TEXT," + 839 Mms.SUBJECT_CHARSET + " INTEGER," + 840 Mms.CONTENT_TYPE + " TEXT," + 841 Mms.CONTENT_LOCATION + " TEXT," + 842 Mms.EXPIRY + " INTEGER," + 843 Mms.MESSAGE_CLASS + " TEXT," + 844 Mms.MESSAGE_TYPE + " INTEGER," + 845 Mms.MMS_VERSION + " INTEGER," + 846 Mms.MESSAGE_SIZE + " INTEGER," + 847 Mms.PRIORITY + " INTEGER," + 848 Mms.READ_REPORT + " INTEGER," + 849 Mms.REPORT_ALLOWED + " INTEGER," + 850 Mms.RESPONSE_STATUS + " INTEGER," + 851 Mms.STATUS + " INTEGER," + 852 Mms.TRANSACTION_ID + " TEXT," + 853 Mms.RETRIEVE_STATUS + " INTEGER," + 854 Mms.RETRIEVE_TEXT + " TEXT," + 855 Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," + 856 Mms.READ_STATUS + " INTEGER," + 857 Mms.CONTENT_CLASS + " INTEGER," + 858 Mms.RESPONSE_TEXT + " TEXT," + 859 Mms.DELIVERY_TIME + " INTEGER," + 860 Mms.DELIVERY_REPORT + " INTEGER," + 861 Mms.LOCKED + " INTEGER DEFAULT 0," + 862 Mms.SUBSCRIPTION_ID + " INTEGER DEFAULT " 863 + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " + 864 Mms.SEEN + " INTEGER DEFAULT 0," + 865 Mms.CREATOR + " TEXT," + 866 Mms.TEXT_ONLY + " INTEGER DEFAULT 0);"; 867 868 @VisibleForTesting 869 public static String CREATE_RATE_TABLE_STR = 870 "CREATE TABLE " + MmsProvider.TABLE_RATE + " (" + 871 Rate.SENT_TIME + " INTEGER," + 872 Rate.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" + 873 ");"; 874 875 @VisibleForTesting 876 public static String CREATE_DRM_TABLE_STR = 877 "CREATE TABLE " + MmsProvider.TABLE_DRM + " (" + 878 BaseColumns._ID + " INTEGER PRIMARY KEY," + 879 "_data TEXT," + 880 "sub_id INTEGER DEFAULT -1" + 881 ");"; 882 883 884 @VisibleForTesting createMmsTables(SQLiteDatabase db)885 void createMmsTables(SQLiteDatabase db) { 886 // N.B.: Whenever the columns here are changed, the columns in 887 // {@ref MmsSmsProvider} must be changed to match. 888 889 db.execSQL(CREATE_PDU_TABLE_STR); 890 891 db.execSQL(CREATE_ADDR_TABLE_STR); 892 893 db.execSQL(CREATE_PART_TABLE_STR); 894 895 db.execSQL(CREATE_RATE_TABLE_STR); 896 897 db.execSQL(CREATE_DRM_TABLE_STR); 898 899 // Restricted view of pdu table, only sent/received messages without wap pushes 900 db.execSQL("CREATE VIEW " + MmsProvider.VIEW_PDU_RESTRICTED + " AS " + 901 "SELECT * FROM " + MmsProvider.TABLE_PDU + " WHERE " + 902 "(" + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_INBOX + 903 " OR " + 904 Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_SENT + ")" + 905 " AND " + 906 "(" + Mms.MESSAGE_TYPE + "!=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + ");"); 907 } 908 909 // Unlike the other trigger-creating functions, this function can be called multiple times 910 // without harm. createMmsTriggers(SQLiteDatabase db)911 private void createMmsTriggers(SQLiteDatabase db) { 912 // Cleans up parts when a MM is deleted. 913 db.execSQL("DROP TRIGGER IF EXISTS part_cleanup"); 914 db.execSQL("CREATE TRIGGER part_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 915 "BEGIN " + 916 " DELETE FROM " + MmsProvider.TABLE_PART + 917 " WHERE " + Part.MSG_ID + "=old._id;" + 918 "END;"); 919 920 // Cleans up address info when a MM is deleted. 921 db.execSQL("DROP TRIGGER IF EXISTS addr_cleanup"); 922 db.execSQL("CREATE TRIGGER addr_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 923 "BEGIN " + 924 " DELETE FROM " + MmsProvider.TABLE_ADDR + 925 " WHERE " + Addr.MSG_ID + "=old._id;" + 926 "END;"); 927 928 // Delete obsolete delivery-report, read-report while deleting their 929 // associated Send.req. 930 db.execSQL("DROP TRIGGER IF EXISTS cleanup_delivery_and_read_report"); 931 db.execSQL("CREATE TRIGGER cleanup_delivery_and_read_report " + 932 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 933 "WHEN old." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ + " " + 934 "BEGIN " + 935 " DELETE FROM " + MmsProvider.TABLE_PDU + 936 " WHERE (" + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_DELIVERY_IND + 937 " OR " + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_ORIG_IND + 938 ")" + 939 " AND " + Mms.MESSAGE_ID + "=old." + Mms.MESSAGE_ID + "; " + 940 "END;"); 941 942 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_insert_part"); 943 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 944 945 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_part"); 946 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 947 948 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_delete_part"); 949 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 950 951 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_pdu"); 952 db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER); 953 954 // Delete pending status for a message when it is deleted. 955 db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_delete"); 956 db.execSQL("CREATE TRIGGER delete_mms_pending_on_delete " + 957 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 958 "BEGIN " + 959 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 960 " WHERE " + PendingMessages.MSG_ID + "=old._id; " + 961 "END;"); 962 963 // When a message is moved out of Outbox, delete its pending status. 964 db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_update"); 965 db.execSQL("CREATE TRIGGER delete_mms_pending_on_update " + 966 "AFTER UPDATE ON " + MmsProvider.TABLE_PDU + " " + 967 "WHEN old." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 968 " AND new." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 969 "BEGIN " + 970 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 971 " WHERE " + PendingMessages.MSG_ID + "=new._id; " + 972 "END;"); 973 974 // Insert pending status for M-Notification.ind or M-ReadRec.ind 975 // when they are inserted into Inbox/Outbox. 976 db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_insert"); 977 db.execSQL("CREATE TRIGGER insert_mms_pending_on_insert " + 978 "AFTER INSERT ON pdu " + 979 "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + 980 " OR new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_REC_IND + 981 " " + 982 "BEGIN " + 983 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 984 " (" + PendingMessages.PROTO_TYPE + "," + 985 " " + PendingMessages.MSG_ID + "," + 986 " " + PendingMessages.MSG_TYPE + "," + 987 " " + PendingMessages.ERROR_TYPE + "," + 988 " " + PendingMessages.ERROR_CODE + "," + 989 " " + PendingMessages.RETRY_INDEX + "," + 990 " " + PendingMessages.DUE_TIME + ") " + 991 " VALUES " + 992 " (" + MmsSms.MMS_PROTO + "," + 993 " new." + BaseColumns._ID + "," + 994 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 995 "END;"); 996 997 998 // Insert pending status for M-Send.req when it is moved into Outbox. 999 db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_update"); 1000 db.execSQL("CREATE TRIGGER insert_mms_pending_on_update " + 1001 "AFTER UPDATE ON pdu " + 1002 "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ + 1003 " AND new." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 1004 " AND old." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 1005 "BEGIN " + 1006 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 1007 " (" + PendingMessages.PROTO_TYPE + "," + 1008 " " + PendingMessages.MSG_ID + "," + 1009 " " + PendingMessages.MSG_TYPE + "," + 1010 " " + PendingMessages.ERROR_TYPE + "," + 1011 " " + PendingMessages.ERROR_CODE + "," + 1012 " " + PendingMessages.RETRY_INDEX + "," + 1013 " " + PendingMessages.DUE_TIME + ") " + 1014 " VALUES " + 1015 " (" + MmsSms.MMS_PROTO + "," + 1016 " new." + BaseColumns._ID + "," + 1017 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 1018 "END;"); 1019 1020 // monitor the mms table 1021 db.execSQL("DROP TRIGGER IF EXISTS mms_words_update"); 1022 db.execSQL("CREATE TRIGGER mms_words_update AFTER UPDATE ON part BEGIN UPDATE words " + 1023 " SET index_text = NEW.text WHERE (source_id=NEW._id AND table_to_use=2); " + 1024 " END;"); 1025 1026 db.execSQL("DROP TRIGGER IF EXISTS mms_words_delete"); 1027 db.execSQL("CREATE TRIGGER mms_words_delete AFTER DELETE ON part BEGIN DELETE FROM " + 1028 " words WHERE source_id = OLD._id AND table_to_use = 2; END;"); 1029 1030 // Updates threads table whenever a message in pdu is updated. 1031 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update"); 1032 db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" + 1033 " UPDATE OF " + Mms.DATE + ", " + Mms.SUBJECT + ", " + Mms.MESSAGE_BOX + 1034 " ON " + MmsProvider.TABLE_PDU + " " + 1035 PDU_UPDATE_THREAD_CONSTRAINTS + 1036 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 1037 1038 // Update threads table whenever a message in pdu is deleted 1039 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete"); 1040 db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " + 1041 "AFTER DELETE ON pdu " + 1042 "BEGIN " + 1043 " UPDATE threads SET " + 1044 " date = (strftime('%s','now') * 1000)" + 1045 " WHERE threads._id = old." + Mms.THREAD_ID + "; " + 1046 UPDATE_THREAD_COUNT_ON_OLD + 1047 UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE + 1048 "END;"); 1049 1050 // Updates threads table whenever a message is added to pdu. 1051 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert"); 1052 db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " + 1053 MmsProvider.TABLE_PDU + " " + 1054 PDU_UPDATE_THREAD_CONSTRAINTS + 1055 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 1056 1057 // Updates threads table whenever a message in pdu is updated. 1058 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update"); 1059 db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" + 1060 " UPDATE OF " + Mms.READ + 1061 " ON " + MmsProvider.TABLE_PDU + " " + 1062 PDU_UPDATE_THREAD_CONSTRAINTS + 1063 "BEGIN " + 1064 PDU_UPDATE_THREAD_READ_BODY + 1065 "END;"); 1066 1067 // Update the error flag of threads when delete pending message. 1068 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_mms"); 1069 db.execSQL("CREATE TRIGGER update_threads_error_on_delete_mms " + 1070 " BEFORE DELETE ON pdu" + 1071 " WHEN OLD._id IN (SELECT DISTINCT msg_id" + 1072 " FROM pending_msgs" + 1073 " WHERE err_type >= 10) " + 1074 "BEGIN " + 1075 " UPDATE threads SET error = error - 1" + 1076 " WHERE _id = OLD.thread_id; " + 1077 "END;"); 1078 1079 // Update the error flag of threads while moving an MM out of Outbox, 1080 // which was failed to be sent permanently. 1081 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms"); 1082 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 1083 " BEFORE UPDATE OF msg_box ON pdu " + 1084 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 1085 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 1086 " FROM pending_msgs" + 1087 " WHERE err_type >= 10)) " + 1088 "BEGIN " + 1089 " UPDATE threads SET error = error - 1" + 1090 " WHERE _id = OLD.thread_id; " + 1091 "END;"); 1092 } 1093 1094 @VisibleForTesting 1095 public static String CREATE_SMS_TABLE_STRING = 1096 "CREATE TABLE sms (" + 1097 "_id INTEGER PRIMARY KEY," + 1098 "thread_id INTEGER," + 1099 "address TEXT," + 1100 "person INTEGER," + 1101 "date INTEGER," + 1102 "date_sent INTEGER DEFAULT 0," + 1103 "protocol INTEGER," + 1104 "read INTEGER DEFAULT 0," + 1105 "status INTEGER DEFAULT -1," + // a TP-Status value 1106 // or -1 if it 1107 // status hasn't 1108 // been received 1109 "type INTEGER," + 1110 "reply_path_present INTEGER," + 1111 "subject TEXT," + 1112 "body TEXT," + 1113 "service_center TEXT," + 1114 "locked INTEGER DEFAULT 0," + 1115 "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " + 1116 "error_code INTEGER DEFAULT " + NO_ERROR_CODE + ", " + 1117 "creator TEXT," + 1118 "seen INTEGER DEFAULT 0" + 1119 ");"; 1120 1121 @VisibleForTesting 1122 public static String CREATE_ATTACHMENTS_TABLE_STRING = 1123 "CREATE TABLE attachments (" + 1124 "sms_id INTEGER," + 1125 "content_url TEXT," + 1126 "offset INTEGER," + 1127 "sub_id INTEGER DEFAULT -1" + 1128 ");"; 1129 1130 /** 1131 * This table is used by the SMS dispatcher to hold 1132 * incomplete partial messages until all the parts arrive. 1133 */ 1134 @VisibleForTesting 1135 public static String CREATE_RAW_TABLE_STRING = 1136 "CREATE TABLE raw (" + 1137 "_id INTEGER PRIMARY KEY," + 1138 "date INTEGER," + 1139 "reference_number INTEGER," + // one per full message 1140 "count INTEGER," + // the number of parts 1141 "sequence INTEGER," + // the part number of this message 1142 "destination_port INTEGER," + 1143 "address TEXT," + 1144 "sub_id INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " + 1145 "pdu TEXT," + // the raw PDU for this part 1146 "deleted INTEGER DEFAULT 0," + // bool to indicate if row is deleted 1147 "message_body TEXT," + // message body 1148 "display_originating_addr TEXT);"; 1149 // email address if from an email gateway, otherwise same as address 1150 @VisibleForTesting createSmsTables(SQLiteDatabase db)1151 void createSmsTables(SQLiteDatabase db) { 1152 // N.B.: Whenever the columns here are changed, the columns in 1153 // {@ref MmsSmsProvider} must be changed to match. 1154 db.execSQL(CREATE_SMS_TABLE_STRING); 1155 1156 db.execSQL(CREATE_RAW_TABLE_STRING); 1157 1158 db.execSQL(CREATE_ATTACHMENTS_TABLE_STRING); 1159 1160 /** 1161 * This table is used by the SMS dispatcher to hold pending 1162 * delivery status report intents. 1163 */ 1164 db.execSQL("CREATE TABLE sr_pending (" + 1165 "reference_number INTEGER," + 1166 "action TEXT," + 1167 "data TEXT," + 1168 "sub_id INTEGER DEFAULT -1" + 1169 ");"); 1170 1171 // Restricted view of sms table, only sent/received messages 1172 db.execSQL("CREATE VIEW " + SmsProvider.VIEW_SMS_RESTRICTED + " AS " + 1173 "SELECT * FROM " + SmsProvider.TABLE_SMS + " WHERE " + 1174 Sms.TYPE + "=" + Sms.MESSAGE_TYPE_INBOX + 1175 " OR " + 1176 Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";"); 1177 1178 if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { 1179 // Create a table to keep track of changes to SMS table - specifically on update to read 1180 // and deletion of msgs 1181 db.execSQL("CREATE TABLE sms_changes (" + 1182 "_id INTEGER PRIMARY KEY," + 1183 "orig_rowid INTEGER," + 1184 "sub_id INTEGER," + 1185 "type INTEGER," + 1186 "new_read_status INTEGER" + 1187 ");"); 1188 db.execSQL("CREATE TRIGGER sms_update_on_read_change_row " + 1189 "AFTER UPDATE OF read ON sms WHEN NEW.read != OLD.read " + 1190 "BEGIN " + 1191 " INSERT INTO sms_changes VALUES(null, NEW._id, NEW.sub_id, " + 1192 "0, NEW.read); " + 1193 "END;"); 1194 db.execSQL("CREATE TRIGGER sms_delete_change_row " + 1195 "AFTER DELETE ON sms " + 1196 "BEGIN " + 1197 " INSERT INTO sms_changes values(null, OLD._id, OLD.sub_id, 1, null); " + 1198 "END;"); 1199 } 1200 } 1201 1202 @VisibleForTesting createCommonTables(SQLiteDatabase db)1203 void createCommonTables(SQLiteDatabase db) { 1204 // TODO Ensure that each entry is removed when the last use of 1205 // any address equivalent to its address is removed. 1206 1207 /** 1208 * This table maps the first instance seen of any particular 1209 * MMS/SMS address to an ID, which is then used as its 1210 * canonical representation. If the same address or an 1211 * equivalent address (as determined by our Sqlite 1212 * PHONE_NUMBERS_EQUAL extension) is seen later, this same ID 1213 * will be used. The _id is created with AUTOINCREMENT so it 1214 * will never be reused again if a recipient is deleted. 1215 */ 1216 db.execSQL("CREATE TABLE canonical_addresses (" + 1217 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 1218 "address TEXT," + 1219 Telephony.CanonicalAddressesColumns.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" 1220 + ");"); 1221 1222 /** 1223 * This table maps the subject and an ordered set of recipient 1224 * IDs, separated by spaces, to a unique thread ID. The IDs 1225 * come from the canonical_addresses table. This works 1226 * because messages are considered to be part of the same 1227 * thread if they have the same subject (or a null subject) 1228 * and the same set of recipients. 1229 */ 1230 db.execSQL("CREATE TABLE threads (" + 1231 Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1232 Threads.DATE + " INTEGER DEFAULT 0," + 1233 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," + 1234 Threads.RECIPIENT_IDS + " TEXT," + 1235 Threads.SNIPPET + " TEXT," + 1236 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," + 1237 Threads.READ + " INTEGER DEFAULT 1," + 1238 Threads.ARCHIVED + " INTEGER DEFAULT 0," + 1239 Threads.TYPE + " INTEGER DEFAULT 0," + 1240 Threads.ERROR + " INTEGER DEFAULT 0," + 1241 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0," + 1242 Threads.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" + 1243 ");"); 1244 1245 /** 1246 * This table stores the queue of messages to be sent/downloaded. 1247 */ 1248 db.execSQL("CREATE TABLE " + MmsSmsProvider.TABLE_PENDING_MSG +" (" + 1249 PendingMessages._ID + " INTEGER PRIMARY KEY," + 1250 PendingMessages.PROTO_TYPE + " INTEGER," + 1251 PendingMessages.MSG_ID + " INTEGER," + 1252 PendingMessages.MSG_TYPE + " INTEGER," + 1253 PendingMessages.ERROR_TYPE + " INTEGER," + 1254 PendingMessages.ERROR_CODE + " INTEGER," + 1255 PendingMessages.RETRY_INDEX + " INTEGER NOT NULL DEFAULT 0," + 1256 PendingMessages.DUE_TIME + " INTEGER," + 1257 PendingMessages.SUBSCRIPTION_ID + " INTEGER DEFAULT " + 1258 SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " + 1259 PendingMessages.LAST_TRY + " INTEGER);"); 1260 1261 } 1262 1263 // TODO Check the query plans for these triggers. createCommonTriggers(SQLiteDatabase db)1264 private void createCommonTriggers(SQLiteDatabase db) { 1265 // Updates threads table whenever a message is added to sms. 1266 db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " + 1267 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 1268 1269 // Updates threads table whenever a message in sms is updated. 1270 db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" + 1271 " UPDATE OF " + Sms.DATE + ", " + Sms.BODY + ", " + Sms.TYPE + 1272 " ON sms " + 1273 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 1274 1275 // Updates threads table whenever a message in sms is updated. 1276 db.execSQL("CREATE TRIGGER sms_update_thread_read_on_update AFTER" + 1277 " UPDATE OF " + Sms.READ + 1278 " ON sms " + 1279 "BEGIN " + 1280 SMS_UPDATE_THREAD_READ_BODY + 1281 "END;"); 1282 1283 // As of DATABASE_VERSION 55, we've removed these triggers that delete empty threads. 1284 // These triggers interfere with saving drafts on brand new threads. Instead of 1285 // triggers cleaning up empty threads, the empty threads should be cleaned up by 1286 // an explicit call to delete with Threads.OBSOLETE_THREADS_URI. 1287 1288 // // When the last message in a thread is deleted, these 1289 // // triggers ensure that the entry for its thread ID is removed 1290 // // from the threads table. 1291 // db.execSQL("CREATE TRIGGER delete_obsolete_threads_pdu " + 1292 // "AFTER DELETE ON pdu " + 1293 // "BEGIN " + 1294 // " DELETE FROM threads " + 1295 // " WHERE " + 1296 // " _id = old.thread_id " + 1297 // " AND _id NOT IN " + 1298 // " (SELECT thread_id FROM sms " + 1299 // " UNION SELECT thread_id from pdu); " + 1300 // "END;"); 1301 // 1302 // db.execSQL("CREATE TRIGGER delete_obsolete_threads_when_update_pdu " + 1303 // "AFTER UPDATE OF " + Mms.THREAD_ID + " ON pdu " + 1304 // "WHEN old." + Mms.THREAD_ID + " != new." + Mms.THREAD_ID + " " + 1305 // "BEGIN " + 1306 // " DELETE FROM threads " + 1307 // " WHERE " + 1308 // " _id = old.thread_id " + 1309 // " AND _id NOT IN " + 1310 // " (SELECT thread_id FROM sms " + 1311 // " UNION SELECT thread_id from pdu); " + 1312 // "END;"); 1313 1314 // TODO Add triggers for SMS retry-status management. 1315 1316 // Update the error flag of threads when the error type of 1317 // a pending MM is updated. 1318 db.execSQL("CREATE TRIGGER update_threads_error_on_update_mms " + 1319 " AFTER UPDATE OF err_type ON pending_msgs " + 1320 " WHEN (OLD.err_type < 10 AND NEW.err_type >= 10)" + 1321 " OR (OLD.err_type >= 10 AND NEW.err_type < 10) " + 1322 "BEGIN" + 1323 " UPDATE threads SET error = " + 1324 " CASE" + 1325 " WHEN NEW.err_type >= 10 THEN error + 1" + 1326 " ELSE error - 1" + 1327 " END " + 1328 " WHERE _id =" + 1329 " (SELECT DISTINCT thread_id" + 1330 " FROM pdu" + 1331 " WHERE _id = NEW.msg_id); " + 1332 "END;"); 1333 1334 // Update the error flag of threads after a text message was 1335 // failed to send/receive. 1336 db.execSQL("CREATE TRIGGER update_threads_error_on_update_sms " + 1337 " AFTER UPDATE OF type ON sms" + 1338 " WHEN (OLD.type != 5 AND NEW.type = 5)" + 1339 " OR (OLD.type = 5 AND NEW.type != 5) " + 1340 "BEGIN " + 1341 " UPDATE threads SET error = " + 1342 " CASE" + 1343 " WHEN NEW.type = 5 THEN error + 1" + 1344 " ELSE error - 1" + 1345 " END " + 1346 " WHERE _id = NEW.thread_id; " + 1347 "END;"); 1348 } 1349 1350 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)1351 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 1352 Log.w(TAG, "Upgrading database from version " + oldVersion 1353 + " to " + currentVersion + "."); 1354 1355 switch (oldVersion) { 1356 case 40: 1357 if (currentVersion <= 40) { 1358 return; 1359 } 1360 1361 db.beginTransaction(); 1362 try { 1363 upgradeDatabaseToVersion41(db); 1364 db.setTransactionSuccessful(); 1365 } catch (Throwable ex) { 1366 Log.e(TAG, ex.getMessage(), ex); 1367 logException(ex, oldVersion, currentVersion, 41); 1368 break; 1369 } finally { 1370 db.endTransaction(); 1371 } 1372 // fall through 1373 case 41: 1374 if (currentVersion <= 41) { 1375 return; 1376 } 1377 1378 db.beginTransaction(); 1379 try { 1380 upgradeDatabaseToVersion42(db); 1381 db.setTransactionSuccessful(); 1382 } catch (Throwable ex) { 1383 Log.e(TAG, ex.getMessage(), ex); 1384 logException(ex, oldVersion, currentVersion, 42); 1385 break; 1386 } finally { 1387 db.endTransaction(); 1388 } 1389 // fall through 1390 case 42: 1391 if (currentVersion <= 42) { 1392 return; 1393 } 1394 1395 db.beginTransaction(); 1396 try { 1397 upgradeDatabaseToVersion43(db); 1398 db.setTransactionSuccessful(); 1399 } catch (Throwable ex) { 1400 Log.e(TAG, ex.getMessage(), ex); 1401 logException(ex, oldVersion, currentVersion, 43); 1402 break; 1403 } finally { 1404 db.endTransaction(); 1405 } 1406 // fall through 1407 case 43: 1408 if (currentVersion <= 43) { 1409 return; 1410 } 1411 1412 db.beginTransaction(); 1413 try { 1414 upgradeDatabaseToVersion44(db); 1415 db.setTransactionSuccessful(); 1416 } catch (Throwable ex) { 1417 Log.e(TAG, ex.getMessage(), ex); 1418 logException(ex, oldVersion, currentVersion, 44); 1419 break; 1420 } finally { 1421 db.endTransaction(); 1422 } 1423 // fall through 1424 case 44: 1425 if (currentVersion <= 44) { 1426 return; 1427 } 1428 1429 db.beginTransaction(); 1430 try { 1431 upgradeDatabaseToVersion45(db); 1432 db.setTransactionSuccessful(); 1433 } catch (Throwable ex) { 1434 Log.e(TAG, ex.getMessage(), ex); 1435 logException(ex, oldVersion, currentVersion, 45); 1436 break; 1437 } finally { 1438 db.endTransaction(); 1439 } 1440 // fall through 1441 case 45: 1442 if (currentVersion <= 45) { 1443 return; 1444 } 1445 db.beginTransaction(); 1446 try { 1447 upgradeDatabaseToVersion46(db, oldVersion, currentVersion); 1448 db.setTransactionSuccessful(); 1449 } catch (Throwable ex) { 1450 Log.e(TAG, ex.getMessage(), ex); 1451 logException(ex, oldVersion, currentVersion, 46); 1452 break; 1453 } finally { 1454 db.endTransaction(); 1455 } 1456 // fall through 1457 case 46: 1458 if (currentVersion <= 46) { 1459 return; 1460 } 1461 1462 db.beginTransaction(); 1463 try { 1464 upgradeDatabaseToVersion47(db); 1465 db.setTransactionSuccessful(); 1466 } catch (Throwable ex) { 1467 Log.e(TAG, ex.getMessage(), ex); 1468 logException(ex, oldVersion, currentVersion, 47); 1469 break; 1470 } finally { 1471 db.endTransaction(); 1472 } 1473 // fall through 1474 case 47: 1475 if (currentVersion <= 47) { 1476 return; 1477 } 1478 1479 db.beginTransaction(); 1480 try { 1481 upgradeDatabaseToVersion48(db); 1482 db.setTransactionSuccessful(); 1483 } catch (Throwable ex) { 1484 Log.e(TAG, ex.getMessage(), ex); 1485 logException(ex, oldVersion, currentVersion, 48); 1486 break; 1487 } finally { 1488 db.endTransaction(); 1489 } 1490 // fall through 1491 case 48: 1492 if (currentVersion <= 48) { 1493 return; 1494 } 1495 1496 db.beginTransaction(); 1497 try { 1498 createWordsTables(db, oldVersion, currentVersion, 49); 1499 db.setTransactionSuccessful(); 1500 } catch (Throwable ex) { 1501 Log.e(TAG, ex.getMessage(), ex); 1502 logException(ex, oldVersion, currentVersion, 49); 1503 break; 1504 } finally { 1505 db.endTransaction(); 1506 } 1507 // fall through 1508 case 49: 1509 if (currentVersion <= 49) { 1510 return; 1511 } 1512 db.beginTransaction(); 1513 try { 1514 createThreadIdIndex(db, oldVersion, currentVersion, 50); 1515 db.setTransactionSuccessful(); 1516 } catch (Throwable ex) { 1517 Log.e(TAG, ex.getMessage(), ex); 1518 logException(ex, oldVersion, currentVersion, 50); 1519 break; // force to destroy all old data; 1520 } finally { 1521 db.endTransaction(); 1522 } 1523 // fall through 1524 case 50: 1525 if (currentVersion <= 50) { 1526 return; 1527 } 1528 1529 db.beginTransaction(); 1530 try { 1531 upgradeDatabaseToVersion51(db); 1532 db.setTransactionSuccessful(); 1533 } catch (Throwable ex) { 1534 Log.e(TAG, ex.getMessage(), ex); 1535 logException(ex, oldVersion, currentVersion, 51); 1536 break; 1537 } finally { 1538 db.endTransaction(); 1539 } 1540 // fall through 1541 case 51: 1542 if (currentVersion <= 51) { 1543 return; 1544 } 1545 // 52 was adding a new meta_data column, but that was removed. 1546 // fall through 1547 case 52: 1548 if (currentVersion <= 52) { 1549 return; 1550 } 1551 1552 db.beginTransaction(); 1553 try { 1554 upgradeDatabaseToVersion53(db); 1555 db.setTransactionSuccessful(); 1556 } catch (Throwable ex) { 1557 Log.e(TAG, ex.getMessage(), ex); 1558 logException(ex, oldVersion, currentVersion, 53); 1559 break; 1560 } finally { 1561 db.endTransaction(); 1562 } 1563 // fall through 1564 case 53: 1565 if (currentVersion <= 53) { 1566 return; 1567 } 1568 1569 db.beginTransaction(); 1570 try { 1571 upgradeDatabaseToVersion54(db); 1572 db.setTransactionSuccessful(); 1573 } catch (Throwable ex) { 1574 Log.e(TAG, ex.getMessage(), ex); 1575 logException(ex, oldVersion, currentVersion, 54); 1576 break; 1577 } finally { 1578 db.endTransaction(); 1579 } 1580 // fall through 1581 case 54: 1582 if (currentVersion <= 54) { 1583 return; 1584 } 1585 1586 db.beginTransaction(); 1587 try { 1588 upgradeDatabaseToVersion55(db); 1589 db.setTransactionSuccessful(); 1590 } catch (Throwable ex) { 1591 Log.e(TAG, ex.getMessage(), ex); 1592 logException(ex, oldVersion, currentVersion, 55); 1593 break; 1594 } finally { 1595 db.endTransaction(); 1596 } 1597 // fall through 1598 case 55: 1599 if (currentVersion <= 55) { 1600 return; 1601 } 1602 1603 db.beginTransaction(); 1604 try { 1605 upgradeDatabaseToVersion56(db); 1606 db.setTransactionSuccessful(); 1607 } catch (Throwable ex) { 1608 Log.e(TAG, ex.getMessage(), ex); 1609 logException(ex, oldVersion, currentVersion, 56); 1610 break; 1611 } finally { 1612 db.endTransaction(); 1613 } 1614 // fall through 1615 case 56: 1616 if (currentVersion <= 56) { 1617 return; 1618 } 1619 1620 db.beginTransaction(); 1621 try { 1622 upgradeDatabaseToVersion57(db); 1623 db.setTransactionSuccessful(); 1624 } catch (Throwable ex) { 1625 Log.e(TAG, ex.getMessage(), ex); 1626 logException(ex, oldVersion, currentVersion, 57); 1627 break; 1628 } finally { 1629 db.endTransaction(); 1630 } 1631 // fall through 1632 case 57: 1633 if (currentVersion <= 57) { 1634 return; 1635 } 1636 1637 db.beginTransaction(); 1638 try { 1639 upgradeDatabaseToVersion58(db); 1640 db.setTransactionSuccessful(); 1641 } catch (Throwable ex) { 1642 Log.e(TAG, ex.getMessage(), ex); 1643 logException(ex, oldVersion, currentVersion, 58); 1644 break; 1645 } finally { 1646 db.endTransaction(); 1647 } 1648 // fall through 1649 case 58: 1650 if (currentVersion <= 58) { 1651 return; 1652 } 1653 1654 db.beginTransaction(); 1655 try { 1656 upgradeDatabaseToVersion59(db); 1657 db.setTransactionSuccessful(); 1658 } catch (Throwable ex) { 1659 Log.e(TAG, ex.getMessage(), ex); 1660 logException(ex, oldVersion, currentVersion, 59); 1661 break; 1662 } finally { 1663 db.endTransaction(); 1664 } 1665 // fall through 1666 case 59: 1667 if (currentVersion <= 59) { 1668 return; 1669 } 1670 1671 db.beginTransaction(); 1672 try { 1673 upgradeDatabaseToVersion60(db); 1674 db.setTransactionSuccessful(); 1675 } catch (Throwable ex) { 1676 Log.e(TAG, ex.getMessage(), ex); 1677 logException(ex, oldVersion, currentVersion, 60); 1678 break; 1679 } finally { 1680 db.endTransaction(); 1681 } 1682 // fall through 1683 case 60: 1684 if (currentVersion <= 60) { 1685 return; 1686 } 1687 1688 db.beginTransaction(); 1689 try { 1690 upgradeDatabaseToVersion61(db); 1691 db.setTransactionSuccessful(); 1692 } catch (Throwable ex) { 1693 Log.e(TAG, ex.getMessage(), ex); 1694 logException(ex, oldVersion, currentVersion, 61); 1695 break; 1696 } finally { 1697 db.endTransaction(); 1698 } 1699 // fall through 1700 case 61: 1701 if (currentVersion <= 61) { 1702 return; 1703 } 1704 1705 db.beginTransaction(); 1706 try { 1707 upgradeDatabaseToVersion62(db, oldVersion, currentVersion); 1708 db.setTransactionSuccessful(); 1709 } catch (Throwable ex) { 1710 Log.e(TAG, ex.getMessage(), ex); 1711 logException(ex, oldVersion, currentVersion, 62); 1712 break; 1713 } finally { 1714 db.endTransaction(); 1715 } 1716 // fall through 1717 case 62: 1718 if (currentVersion <= 62) { 1719 return; 1720 } 1721 1722 db.beginTransaction(); 1723 try { 1724 // upgrade to 63: just add a happy little index. 1725 createThreadIdDateIndex(db, oldVersion, currentVersion, 63); 1726 db.setTransactionSuccessful(); 1727 } catch (Throwable ex) { 1728 Log.e(TAG, ex.getMessage(), ex); 1729 logException(ex, oldVersion, currentVersion, 63); 1730 break; 1731 } finally { 1732 db.endTransaction(); 1733 } 1734 // fall through 1735 case 63: 1736 if (currentVersion <= 63) { 1737 return; 1738 } 1739 1740 db.beginTransaction(); 1741 try { 1742 upgradeDatabaseToVersion64(db); 1743 db.setTransactionSuccessful(); 1744 } catch (Throwable ex) { 1745 Log.e(TAG, ex.getMessage(), ex); 1746 logException(ex, oldVersion, currentVersion, 64); 1747 break; 1748 } finally { 1749 db.endTransaction(); 1750 } 1751 // fall through 1752 case 64: 1753 if (currentVersion <= 64) { 1754 return; 1755 } 1756 1757 db.beginTransaction(); 1758 try { 1759 upgradeDatabaseToVersion65(db, oldVersion, currentVersion); 1760 db.setTransactionSuccessful(); 1761 } catch (Throwable ex) { 1762 Log.e(TAG, ex.getMessage(), ex); 1763 logException(ex, oldVersion, currentVersion, 65); 1764 break; 1765 } finally { 1766 db.endTransaction(); 1767 } 1768 // fall through 1769 case 65: 1770 if (currentVersion <= 65) { 1771 return; 1772 } 1773 1774 db.beginTransaction(); 1775 try { 1776 upgradeDatabaseToVersion66(db, oldVersion, currentVersion); 1777 db.setTransactionSuccessful(); 1778 } catch (Throwable ex) { 1779 Log.e(TAG, ex.getMessage(), ex); 1780 logException(ex, oldVersion, currentVersion, 66); 1781 break; 1782 } finally { 1783 db.endTransaction(); 1784 } 1785 // fall through 1786 case 66: 1787 if (currentVersion <= 66) { 1788 return; 1789 } 1790 db.beginTransaction(); 1791 try { 1792 createPartMidIndex(db, oldVersion, currentVersion, 67); 1793 createAddrMsgIdIndex(db, oldVersion, currentVersion, 67); 1794 db.setTransactionSuccessful(); 1795 } catch (Throwable ex) { 1796 Log.e(TAG, ex.getMessage(), ex); 1797 logException(ex, oldVersion, currentVersion, 67); 1798 break; // force to destroy all old data; 1799 } finally { 1800 db.endTransaction(); 1801 } 1802 // fall through 1803 case 67: 1804 if (currentVersion <= 67) { 1805 return; 1806 } 1807 db.beginTransaction(); 1808 try { 1809 upgradeDatabaseToVersion68(db, oldVersion, currentVersion); 1810 db.setTransactionSuccessful(); 1811 } catch(Throwable ex) { 1812 Log.e(TAG, ex.getMessage(), ex); 1813 break; // force to destroy all old data; 1814 } finally { 1815 db.endTransaction(); 1816 } 1817 // fall through 1818 case 68: 1819 if (currentVersion <= 68) { 1820 return; 1821 } 1822 1823 db.beginTransaction(); 1824 try { 1825 // Create words table with new sub_id column 1826 createWordsTables(db, oldVersion, currentVersion, 69); 1827 if (!isColumnExists(db, SmsProvider.TABLE_SR_PENDING, "sub_id")) { 1828 // Add sub_id to sr_pending table if it is not present already 1829 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SR_PENDING 1830 + " ADD COLUMN sub_id" 1831 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 1832 } 1833 db.setTransactionSuccessful(); 1834 } catch (Throwable ex) { 1835 Log.e(TAG, ex.getMessage(), ex); 1836 logException(ex, oldVersion, currentVersion, 69); 1837 break; // force to destroy all old data; 1838 } finally { 1839 db.endTransaction(); 1840 } 1841 return; 1842 } 1843 1844 Log.e(TAG, "Destroying all old data."); 1845 localLog("onUpgrade: Calling wipeDbOnFailedUpgrade() and onCreate()." 1846 + " Upgrading database" 1847 + " from version " + oldVersion + " to " + currentVersion + "failed."); 1848 db = wipeDbOnFailedUpgrade(db); 1849 onCreate(db); 1850 } 1851 logException( Throwable ex, int oldVersion, int currentVersion, int upgradeVersion)1852 private void logException( 1853 Throwable ex, int oldVersion, int currentVersion, int upgradeVersion) { 1854 int exception = FAILURE_UNKNOWN; 1855 if (ex instanceof SQLiteException) { 1856 exception = SQL_EXCEPTION; 1857 } else if (ex instanceof IOException) { 1858 exception = IO_EXCEPTION; 1859 } else if (ex instanceof SecurityException) { 1860 exception = SECURITY_EXCEPTION; 1861 } 1862 TelephonyStatsLog.write( 1863 TelephonyStatsLog.MMS_SMS_DATABASE_HELPER_ON_UPGRADE_FAILED, 1864 oldVersion, 1865 currentVersion, 1866 upgradeVersion, 1867 exception); 1868 } 1869 wipeDbOnFailedUpgrade(SQLiteDatabase db)1870 public SQLiteDatabase wipeDbOnFailedUpgrade(SQLiteDatabase db) { 1871 // Delete the database in order to start over from scratch. 1872 File databaseFile = new File(db.getPath()); 1873 db.close(); 1874 boolean didDelete = SQLiteDatabase.deleteDatabase(databaseFile); 1875 Log.e(TAG, "wipeDbOnFailedUpgrade: didDelete: " + didDelete); 1876 return getWritableDatabase(); 1877 } 1878 upgradeDatabaseToVersion41(SQLiteDatabase db)1879 private void upgradeDatabaseToVersion41(SQLiteDatabase db) { 1880 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms"); 1881 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 1882 " BEFORE UPDATE OF msg_box ON pdu " + 1883 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 1884 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 1885 " FROM pending_msgs" + 1886 " WHERE err_type >= 10)) " + 1887 "BEGIN " + 1888 " UPDATE threads SET error = error - 1" + 1889 " WHERE _id = OLD.thread_id; " + 1890 "END;"); 1891 } 1892 upgradeDatabaseToVersion42(SQLiteDatabase db)1893 private void upgradeDatabaseToVersion42(SQLiteDatabase db) { 1894 db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete"); 1895 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_sms"); 1896 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_sms"); 1897 } 1898 upgradeDatabaseToVersion43(SQLiteDatabase db)1899 private void upgradeDatabaseToVersion43(SQLiteDatabase db) { 1900 // Add 'has_attachment' column to threads table. 1901 db.execSQL("ALTER TABLE threads ADD COLUMN has_attachment INTEGER DEFAULT 0"); 1902 1903 updateThreadsAttachmentColumn(db); 1904 1905 // Add insert and delete triggers for keeping it up to date. 1906 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 1907 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 1908 } 1909 upgradeDatabaseToVersion44(SQLiteDatabase db)1910 private void upgradeDatabaseToVersion44(SQLiteDatabase db) { 1911 updateThreadsAttachmentColumn(db); 1912 1913 // add the update trigger for keeping the threads up to date. 1914 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 1915 } 1916 upgradeDatabaseToVersion45(SQLiteDatabase db)1917 private void upgradeDatabaseToVersion45(SQLiteDatabase db) { 1918 // Add 'locked' column to sms table. 1919 db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.LOCKED + " INTEGER DEFAULT 0"); 1920 1921 // Add 'locked' column to pdu table. 1922 db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.LOCKED + " INTEGER DEFAULT 0"); 1923 } 1924 upgradeDatabaseToVersion46(SQLiteDatabase db, int oldVersion, int currentVersion)1925 private void upgradeDatabaseToVersion46(SQLiteDatabase db, int oldVersion, int currentVersion) { 1926 // add the "text" column for caching inline text (e.g. strings) instead of 1927 // putting them in an external file 1928 db.execSQL("ALTER TABLE part ADD COLUMN " + Part.TEXT + " TEXT"); 1929 1930 Cursor textRows = db.query( 1931 "part", 1932 new String[] { Part._ID, Part._DATA, Part.TEXT}, 1933 "ct = 'text/plain' OR ct == 'application/smil'", 1934 null, 1935 null, 1936 null, 1937 null); 1938 ArrayList<String> filesToDelete = new ArrayList<String>(); 1939 try { 1940 db.beginTransaction(); 1941 if (textRows != null) { 1942 int partDataColumn = textRows.getColumnIndex(Part._DATA); 1943 1944 // This code is imperfect in that we can't guarantee that all the 1945 // backing files get deleted. For example if the system aborts after 1946 // the database is updated but before we complete the process of 1947 // deleting files. 1948 while (textRows.moveToNext()) { 1949 String path = textRows.getString(partDataColumn); 1950 if (path != null) { 1951 try { 1952 InputStream is = new FileInputStream(path); 1953 byte [] data = new byte[is.available()]; 1954 is.read(data); 1955 EncodedStringValue v = new EncodedStringValue(data); 1956 db.execSQL("UPDATE part SET " + Part._DATA + " = NULL, " + 1957 Part.TEXT + " = ?", new String[] { v.getString() }); 1958 is.close(); 1959 filesToDelete.add(path); 1960 } catch (IOException e) { 1961 // TODO Auto-generated catch block 1962 e.printStackTrace(); 1963 logException(e, oldVersion, currentVersion, 46); 1964 } 1965 } 1966 } 1967 } 1968 db.setTransactionSuccessful(); 1969 } finally { 1970 db.endTransaction(); 1971 for (String pathToDelete : filesToDelete) { 1972 try { 1973 (new File(pathToDelete)).delete(); 1974 } catch (SecurityException ex) { 1975 Log.e(TAG, "unable to clean up old mms file for " + pathToDelete, ex); 1976 logException(ex, oldVersion, currentVersion, 46); 1977 } 1978 } 1979 if (textRows != null) { 1980 textRows.close(); 1981 } 1982 } 1983 } 1984 upgradeDatabaseToVersion47(SQLiteDatabase db)1985 private void upgradeDatabaseToVersion47(SQLiteDatabase db) { 1986 updateThreadsAttachmentColumn(db); 1987 1988 // add the update trigger for keeping the threads up to date. 1989 db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER); 1990 } 1991 upgradeDatabaseToVersion48(SQLiteDatabase db)1992 private void upgradeDatabaseToVersion48(SQLiteDatabase db) { 1993 // Add 'error_code' column to sms table. 1994 db.execSQL("ALTER TABLE sms ADD COLUMN error_code INTEGER DEFAULT " + NO_ERROR_CODE); 1995 } 1996 upgradeDatabaseToVersion51(SQLiteDatabase db)1997 private void upgradeDatabaseToVersion51(SQLiteDatabase db) { 1998 db.execSQL("ALTER TABLE sms add COLUMN seen INTEGER DEFAULT 0"); 1999 db.execSQL("ALTER TABLE pdu add COLUMN seen INTEGER DEFAULT 0"); 2000 2001 try { 2002 // update the existing sms and pdu tables so the new "seen" column is the same as 2003 // the "read" column for each row. 2004 ContentValues contentValues = new ContentValues(); 2005 contentValues.put("seen", 1); 2006 int count = db.update("sms", contentValues, "read=1", null); 2007 Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count + 2008 " rows in sms table to have READ=1"); 2009 count = db.update("pdu", contentValues, "read=1", null); 2010 Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count + 2011 " rows in pdu table to have READ=1"); 2012 } catch (Exception ex) { 2013 Log.e(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51 caught ", ex); 2014 } 2015 } 2016 upgradeDatabaseToVersion53(SQLiteDatabase db)2017 private void upgradeDatabaseToVersion53(SQLiteDatabase db) { 2018 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update"); 2019 2020 // Updates threads table whenever a message in pdu is updated. 2021 db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" + 2022 " UPDATE OF " + Mms.READ + 2023 " ON " + MmsProvider.TABLE_PDU + " " + 2024 PDU_UPDATE_THREAD_CONSTRAINTS + 2025 "BEGIN " + 2026 PDU_UPDATE_THREAD_READ_BODY + 2027 "END;"); 2028 } 2029 upgradeDatabaseToVersion54(SQLiteDatabase db)2030 private void upgradeDatabaseToVersion54(SQLiteDatabase db) { 2031 // Add 'date_sent' column to sms table. 2032 db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.DATE_SENT + " INTEGER DEFAULT 0"); 2033 2034 // Add 'date_sent' column to pdu table. 2035 db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.DATE_SENT + " INTEGER DEFAULT 0"); 2036 } 2037 upgradeDatabaseToVersion55(SQLiteDatabase db)2038 private void upgradeDatabaseToVersion55(SQLiteDatabase db) { 2039 // Drop removed triggers 2040 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_pdu"); 2041 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_when_update_pdu"); 2042 } 2043 upgradeDatabaseToVersion56(SQLiteDatabase db)2044 private void upgradeDatabaseToVersion56(SQLiteDatabase db) { 2045 // Add 'text_only' column to pdu table. 2046 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU + " ADD COLUMN " + Mms.TEXT_ONLY + 2047 " INTEGER DEFAULT 0"); 2048 } 2049 upgradeDatabaseToVersion57(SQLiteDatabase db)2050 private void upgradeDatabaseToVersion57(SQLiteDatabase db) { 2051 // Clear out bad rows, those with empty threadIds, from the pdu table. 2052 db.execSQL("DELETE FROM " + MmsProvider.TABLE_PDU + " WHERE " + Mms.THREAD_ID + " IS NULL"); 2053 } 2054 upgradeDatabaseToVersion58(SQLiteDatabase db)2055 private void upgradeDatabaseToVersion58(SQLiteDatabase db) { 2056 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU + 2057 " ADD COLUMN " + Mms.SUBSCRIPTION_ID 2058 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2059 db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_PENDING_MSG 2060 +" ADD COLUMN " + "pending_sub_id" 2061 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2062 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SMS 2063 + " ADD COLUMN " + Sms.SUBSCRIPTION_ID 2064 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2065 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW 2066 +" ADD COLUMN " + Sms.SUBSCRIPTION_ID 2067 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2068 } 2069 upgradeDatabaseToVersion59(SQLiteDatabase db)2070 private void upgradeDatabaseToVersion59(SQLiteDatabase db) { 2071 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU +" ADD COLUMN " 2072 + Mms.CREATOR + " TEXT"); 2073 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SMS +" ADD COLUMN " 2074 + Sms.CREATOR + " TEXT"); 2075 } 2076 upgradeDatabaseToVersion60(SQLiteDatabase db)2077 private void upgradeDatabaseToVersion60(SQLiteDatabase db) { 2078 db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_THREADS +" ADD COLUMN " 2079 + Threads.ARCHIVED + " INTEGER DEFAULT 0"); 2080 } 2081 upgradeDatabaseToVersion61(SQLiteDatabase db)2082 private void upgradeDatabaseToVersion61(SQLiteDatabase db) { 2083 db.execSQL("CREATE VIEW " + SmsProvider.VIEW_SMS_RESTRICTED + " AS " + 2084 "SELECT * FROM " + SmsProvider.TABLE_SMS + " WHERE " + 2085 Sms.TYPE + "=" + Sms.MESSAGE_TYPE_INBOX + 2086 " OR " + 2087 Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";"); 2088 db.execSQL("CREATE VIEW " + MmsProvider.VIEW_PDU_RESTRICTED + " AS " + 2089 "SELECT * FROM " + MmsProvider.TABLE_PDU + " WHERE " + 2090 "(" + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_INBOX + 2091 " OR " + 2092 Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_SENT + ")" + 2093 " AND " + 2094 "(" + Mms.MESSAGE_TYPE + "!=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + ");"); 2095 2096 } 2097 upgradeDatabaseToVersion62(SQLiteDatabase db, int oldVersion, int currentVersion)2098 private void upgradeDatabaseToVersion62(SQLiteDatabase db, int oldVersion, int currentVersion) { 2099 // When a non-FBE device is upgraded to N, all MMS attachment files are moved from 2100 // /data/data to /data/user_de. We need to update the paths stored in the parts table to 2101 // reflect this change. 2102 String newPartsDirPath; 2103 try { 2104 newPartsDirPath = mContext.getDir(MmsProvider.PARTS_DIR_NAME, 0).getCanonicalPath(); 2105 } 2106 catch (IOException e){ 2107 Log.e(TAG, "openFile: check file path failed " + e, e); 2108 logException(e, oldVersion, currentVersion, 62); 2109 return; 2110 } 2111 2112 // The old path of the part files will be something like this: 2113 // /data/data/0/com.android.providers.telephony/app_parts 2114 // The new path of the part files will be something like this: 2115 // /data/user_de/0/com.android.providers.telephony/app_parts 2116 int partsDirIndex = newPartsDirPath.lastIndexOf( 2117 File.separator, newPartsDirPath.lastIndexOf(MmsProvider.PARTS_DIR_NAME)); 2118 String partsDirName = newPartsDirPath.substring(partsDirIndex) + File.separator; 2119 // The query to update the part path will be: 2120 // UPDATE part SET _data = '/data/user_de/0/com.android.providers.telephony' || 2121 // SUBSTR(_data, INSTR(_data, '/app_parts/')) 2122 // WHERE INSTR(_data, '/app_parts/') > 0 2123 db.execSQL("UPDATE " + MmsProvider.TABLE_PART + 2124 " SET " + Part._DATA + " = '" + newPartsDirPath.substring(0, partsDirIndex) + "' ||" + 2125 " SUBSTR(" + Part._DATA + ", INSTR(" + Part._DATA + ", '" + partsDirName + "'))" + 2126 " WHERE INSTR(" + Part._DATA + ", '" + partsDirName + "') > 0"); 2127 } 2128 upgradeDatabaseToVersion64(SQLiteDatabase db)2129 private void upgradeDatabaseToVersion64(SQLiteDatabase db) { 2130 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW +" ADD COLUMN deleted INTEGER DEFAULT 0"); 2131 } 2132 upgradeDatabaseToVersion65(SQLiteDatabase db, int oldVersion, int currentVersion)2133 private void upgradeDatabaseToVersion65(SQLiteDatabase db, int oldVersion, int currentVersion) { 2134 // aosp and internal code diverged at version 63. Aosp did createThreadIdDateIndex() on 2135 // upgrading to 63, whereas internal (nyc) added column 'deleted'. A device upgrading from 2136 // nyc will have columns deleted and message_body in raw table with version 64, but not 2137 // createThreadIdDateIndex() 2138 try { 2139 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW + " ADD COLUMN message_body TEXT"); 2140 } catch (SQLiteException e) { 2141 Log.w(TAG, "[upgradeDatabaseToVersion65] Exception adding column message_body; " + 2142 "trying createThreadIdDateIndex() instead: " + e); 2143 logException(e, oldVersion, currentVersion, 65); 2144 createThreadIdDateIndex(db); 2145 } 2146 } 2147 upgradeDatabaseToVersion66(SQLiteDatabase db, int oldVersion, int currentVersion)2148 private void upgradeDatabaseToVersion66(SQLiteDatabase db, int oldVersion, int currentVersion) { 2149 try { 2150 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_RAW 2151 + " ADD COLUMN display_originating_addr TEXT"); 2152 } catch (SQLiteException e) { 2153 Log.e(TAG, "[upgradeDatabaseToVersion66] Exception adding column " 2154 + "display_originating_addr; " + e); 2155 logException(e, oldVersion, currentVersion, 66); 2156 } 2157 } 2158 upgradeDatabaseToVersion68(SQLiteDatabase db, int oldVersion, int currentVersion)2159 private void upgradeDatabaseToVersion68(SQLiteDatabase db, int oldVersion, int currentVersion) { 2160 try { 2161 db.execSQL("ALTER TABLE " + MmsSmsProvider.TABLE_THREADS 2162 + " ADD COLUMN " + Telephony.ThreadsColumns.SUBSCRIPTION_ID 2163 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2164 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PART 2165 + " ADD COLUMN " + Part.SUBSCRIPTION_ID 2166 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2167 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_CANONICAL_ADDRESSES 2168 + " ADD COLUMN " + Telephony.CanonicalAddressesColumns.SUBSCRIPTION_ID 2169 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2170 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_ATTACHMENTS 2171 + " ADD COLUMN sub_id" 2172 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2173 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_ADDR 2174 + " ADD COLUMN " + Addr.SUBSCRIPTION_ID 2175 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2176 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_RATE 2177 + " ADD COLUMN " + Rate.SUBSCRIPTION_ID 2178 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2179 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_DRM 2180 + " ADD COLUMN sub_id" 2181 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2182 db.execSQL("ALTER TABLE " + SmsProvider.TABLE_SR_PENDING 2183 + " ADD COLUMN sub_id" 2184 + " INTEGER DEFAULT " + SubscriptionManager.INVALID_SUBSCRIPTION_ID); 2185 } catch (SQLiteException e) { 2186 Log.e(TAG, "[upgradeDatabaseToVersion68] Exception adding column " 2187 + "sub_id; " + e); 2188 logException(e, oldVersion, currentVersion, 68); 2189 } 2190 } 2191 2192 @Override getReadableDatabase()2193 public synchronized SQLiteDatabase getReadableDatabase() { 2194 SQLiteDatabase db; 2195 try { 2196 db = super.getWritableDatabase(); 2197 } catch (SQLiteException ex) { 2198 reportAnomalyForDatabaseOpeningException(ex); 2199 throw ex; 2200 } 2201 2202 // getReadableDatabase gets or creates a database. So we know for sure that a database has 2203 // already been created at this point. 2204 if (mContext.isCredentialProtectedStorage()) { 2205 setInitialCreateDone(); 2206 } 2207 2208 return db; 2209 } 2210 2211 @Override getWritableDatabase()2212 public synchronized SQLiteDatabase getWritableDatabase() { 2213 SQLiteDatabase db; 2214 try { 2215 db = super.getWritableDatabase(); 2216 } catch (SQLiteException ex) { 2217 reportAnomalyForDatabaseOpeningException(ex); 2218 throw ex; 2219 } 2220 2221 // getWritableDatabase gets or creates a database. So we know for sure that a database has 2222 // already been created at this point. 2223 if (mContext.isCredentialProtectedStorage()) { 2224 setInitialCreateDone(); 2225 } 2226 2227 if (!sTriedAutoIncrement) { 2228 sTriedAutoIncrement = true; 2229 boolean hasAutoIncrementThreads = hasAutoIncrement(db, MmsSmsProvider.TABLE_THREADS); 2230 boolean hasAutoIncrementAddresses = hasAutoIncrement(db, "canonical_addresses"); 2231 boolean hasAutoIncrementPart = hasAutoIncrement(db, "part"); 2232 boolean hasAutoIncrementPdu = hasAutoIncrement(db, "pdu"); 2233 String logMsg = "[getWritableDatabase]" + 2234 " hasAutoIncrementThreads: " + hasAutoIncrementThreads + 2235 " hasAutoIncrementAddresses: " + hasAutoIncrementAddresses + 2236 " hasAutoIncrementPart: " + hasAutoIncrementPart + 2237 " hasAutoIncrementPdu: " + hasAutoIncrementPdu; 2238 Log.d(TAG, logMsg); 2239 localLog(logMsg); 2240 boolean autoIncrementThreadsSuccess = true; 2241 boolean autoIncrementAddressesSuccess = true; 2242 boolean autoIncrementPartSuccess = true; 2243 boolean autoIncrementPduSuccess = true; 2244 if (!hasAutoIncrementThreads) { 2245 db.beginTransaction(); 2246 try { 2247 if (false && sFakeLowStorageTest) { 2248 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 2249 " - fake exception"); 2250 throw new Exception("FakeLowStorageTest"); 2251 } 2252 upgradeThreadsTableToAutoIncrement(db); // a no-op if already upgraded 2253 db.setTransactionSuccessful(); 2254 } catch (Throwable ex) { 2255 Log.e(TAG, "Failed to add autoIncrement to threads;: " + ex.getMessage(), ex); 2256 autoIncrementThreadsSuccess = false; 2257 } finally { 2258 db.endTransaction(); 2259 } 2260 } 2261 if (!hasAutoIncrementAddresses) { 2262 db.beginTransaction(); 2263 try { 2264 if (false && sFakeLowStorageTest) { 2265 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 2266 " - fake exception"); 2267 throw new Exception("FakeLowStorageTest"); 2268 } 2269 upgradeAddressTableToAutoIncrement(db); // a no-op if already upgraded 2270 db.setTransactionSuccessful(); 2271 } catch (Throwable ex) { 2272 Log.e(TAG, "Failed to add autoIncrement to canonical_addresses: " + 2273 ex.getMessage(), ex); 2274 autoIncrementAddressesSuccess = false; 2275 } finally { 2276 db.endTransaction(); 2277 } 2278 } 2279 if (!hasAutoIncrementPart) { 2280 db.beginTransaction(); 2281 try { 2282 if (false && sFakeLowStorageTest) { 2283 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 2284 " - fake exception"); 2285 throw new Exception("FakeLowStorageTest"); 2286 } 2287 upgradePartTableToAutoIncrement(db); // a no-op if already upgraded 2288 db.setTransactionSuccessful(); 2289 } catch (Throwable ex) { 2290 Log.e(TAG, "Failed to add autoIncrement to part: " + 2291 ex.getMessage(), ex); 2292 autoIncrementPartSuccess = false; 2293 } finally { 2294 db.endTransaction(); 2295 } 2296 } 2297 if (!hasAutoIncrementPdu) { 2298 db.beginTransaction(); 2299 try { 2300 if (false && sFakeLowStorageTest) { 2301 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 2302 " - fake exception"); 2303 throw new Exception("FakeLowStorageTest"); 2304 } 2305 upgradePduTableToAutoIncrement(db); // a no-op if already upgraded 2306 db.setTransactionSuccessful(); 2307 } catch (Throwable ex) { 2308 Log.e(TAG, "Failed to add autoIncrement to pdu: " + 2309 ex.getMessage(), ex); 2310 autoIncrementPduSuccess = false; 2311 } finally { 2312 db.endTransaction(); 2313 } 2314 } 2315 if (autoIncrementThreadsSuccess && 2316 autoIncrementAddressesSuccess && 2317 autoIncrementPartSuccess && 2318 autoIncrementPduSuccess) { 2319 if (mLowStorageMonitor != null) { 2320 // We've already updated the database. This receiver is no longer necessary. 2321 Log.d(TAG, "Unregistering mLowStorageMonitor - we've upgraded"); 2322 mContext.unregisterReceiver(mLowStorageMonitor); 2323 mLowStorageMonitor = null; 2324 } 2325 } else { 2326 if (sFakeLowStorageTest) { 2327 sFakeLowStorageTest = false; 2328 } 2329 2330 // We failed, perhaps because of low storage. Turn on a receiver to watch for 2331 // storage space. 2332 if (mLowStorageMonitor == null) { 2333 Log.d(TAG, "[getWritableDatabase] turning on storage monitor"); 2334 mLowStorageMonitor = new LowStorageMonitor(); 2335 IntentFilter intentFilter = new IntentFilter(); 2336 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); 2337 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 2338 mContext.registerReceiver(mLowStorageMonitor, intentFilter); 2339 } 2340 } 2341 } 2342 return db; 2343 } 2344 2345 // Determine whether a particular table has AUTOINCREMENT in its schema. hasAutoIncrement(SQLiteDatabase db, String tableName)2346 private boolean hasAutoIncrement(SQLiteDatabase db, String tableName) { 2347 boolean result = false; 2348 String query = "SELECT sql FROM sqlite_master WHERE type='table' AND name='" + 2349 tableName + "'"; 2350 Cursor c = db.rawQuery(query, null); 2351 if (c != null) { 2352 try { 2353 if (c.moveToFirst()) { 2354 String schema = c.getString(0); 2355 result = schema != null ? schema.contains("AUTOINCREMENT") : false; 2356 Log.d(TAG, "[MmsSmsDb] tableName: " + tableName + " hasAutoIncrement: " + 2357 schema + " result: " + result); 2358 } 2359 } finally { 2360 c.close(); 2361 } 2362 } 2363 return result; 2364 } 2365 2366 // upgradeThreadsTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 2367 // the threads table. This could fail if the user has a lot of conversations and not enough 2368 // storage to make a copy of the threads table. That's ok. This upgrade is optional. It'll 2369 // be called again next time the device is rebooted. upgradeThreadsTableToAutoIncrement(SQLiteDatabase db)2370 private void upgradeThreadsTableToAutoIncrement(SQLiteDatabase db) { 2371 if (hasAutoIncrement(db, MmsSmsProvider.TABLE_THREADS)) { 2372 Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: already upgraded"); 2373 return; 2374 } 2375 Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: upgrading"); 2376 2377 // Make the _id of the threads table autoincrement so we never re-use thread ids 2378 // Have to create a new temp threads table. Copy all the info from the old table. 2379 // Drop the old table and rename the new table to that of the old. 2380 db.execSQL("CREATE TABLE threads_temp (" + 2381 Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 2382 Threads.DATE + " INTEGER DEFAULT 0," + 2383 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," + 2384 Threads.RECIPIENT_IDS + " TEXT," + 2385 Threads.SNIPPET + " TEXT," + 2386 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," + 2387 Threads.READ + " INTEGER DEFAULT 1," + 2388 Threads.TYPE + " INTEGER DEFAULT 0," + 2389 Threads.ERROR + " INTEGER DEFAULT 0," + 2390 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0," + 2391 Threads.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" 2392 +");"); 2393 2394 db.execSQL("INSERT INTO threads_temp SELECT * from threads;"); 2395 db.execSQL("DROP TABLE threads;"); 2396 db.execSQL("ALTER TABLE threads_temp RENAME TO threads;"); 2397 } 2398 2399 // upgradeAddressTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 2400 // the canonical_addresses table. This could fail if the user has a lot of people they've 2401 // messaged with and not enough storage to make a copy of the canonical_addresses table. 2402 // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted. upgradeAddressTableToAutoIncrement(SQLiteDatabase db)2403 private void upgradeAddressTableToAutoIncrement(SQLiteDatabase db) { 2404 if (hasAutoIncrement(db, "canonical_addresses")) { 2405 Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: already upgraded"); 2406 return; 2407 } 2408 Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: upgrading"); 2409 2410 // Make the _id of the canonical_addresses table autoincrement so we never re-use ids 2411 // Have to create a new temp canonical_addresses table. Copy all the info from the old 2412 // table. Drop the old table and rename the new table to that of the old. 2413 db.execSQL("CREATE TABLE canonical_addresses_temp (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 2414 "address TEXT," + 2415 Telephony.CanonicalAddressesColumns.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" + 2416 ");"); 2417 2418 db.execSQL("INSERT INTO canonical_addresses_temp SELECT * from canonical_addresses;"); 2419 db.execSQL("DROP TABLE canonical_addresses;"); 2420 db.execSQL("ALTER TABLE canonical_addresses_temp RENAME TO canonical_addresses;"); 2421 } 2422 2423 // upgradePartTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 2424 // the part table. This could fail if the user has a lot of sound/video/picture attachments 2425 // and not enough storage to make a copy of the part table. 2426 // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted. upgradePartTableToAutoIncrement(SQLiteDatabase db)2427 private void upgradePartTableToAutoIncrement(SQLiteDatabase db) { 2428 if (hasAutoIncrement(db, "part")) { 2429 Log.d(TAG, "[MmsSmsDb] upgradePartTableToAutoIncrement: already upgraded"); 2430 return; 2431 } 2432 Log.d(TAG, "[MmsSmsDb] upgradePartTableToAutoIncrement: upgrading"); 2433 2434 // Make the _id of the part table autoincrement so we never re-use ids 2435 // Have to create a new temp part table. Copy all the info from the old 2436 // table. Drop the old table and rename the new table to that of the old. 2437 db.execSQL("CREATE TABLE part_temp (" + 2438 Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 2439 Part.MSG_ID + " INTEGER," + 2440 Part.SEQ + " INTEGER DEFAULT 0," + 2441 Part.CONTENT_TYPE + " TEXT," + 2442 Part.NAME + " TEXT," + 2443 Part.CHARSET + " INTEGER," + 2444 Part.CONTENT_DISPOSITION + " TEXT," + 2445 Part.FILENAME + " TEXT," + 2446 Part.CONTENT_ID + " TEXT," + 2447 Part.CONTENT_LOCATION + " TEXT," + 2448 Part.CT_START + " INTEGER," + 2449 Part.CT_TYPE + " TEXT," + 2450 Part._DATA + " TEXT," + 2451 Part.TEXT + " TEXT," + 2452 Part.SUBSCRIPTION_ID + " INTEGER DEFAULT -1" 2453 + ");"); 2454 2455 db.execSQL("INSERT INTO part_temp SELECT * from part;"); 2456 db.execSQL("DROP TABLE part;"); 2457 db.execSQL("ALTER TABLE part_temp RENAME TO part;"); 2458 2459 // part-related triggers get tossed when the part table is dropped -- rebuild them. 2460 createMmsTriggers(db); 2461 } 2462 2463 // upgradePduTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 2464 // the pdu table. This could fail if the user has a lot of mms messages 2465 // and not enough storage to make a copy of the pdu table. 2466 // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted. upgradePduTableToAutoIncrement(SQLiteDatabase db)2467 private void upgradePduTableToAutoIncrement(SQLiteDatabase db) { 2468 if (hasAutoIncrement(db, "pdu")) { 2469 Log.d(TAG, "[MmsSmsDb] upgradePduTableToAutoIncrement: already upgraded"); 2470 return; 2471 } 2472 Log.d(TAG, "[MmsSmsDb] upgradePduTableToAutoIncrement: upgrading"); 2473 2474 // Make the _id of the part table autoincrement so we never re-use ids 2475 // Have to create a new temp part table. Copy all the info from the old 2476 // table. Drop the old table and rename the new table to that of the old. 2477 db.execSQL("CREATE TABLE pdu_temp (" + 2478 Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 2479 Mms.THREAD_ID + " INTEGER," + 2480 Mms.DATE + " INTEGER," + 2481 Mms.DATE_SENT + " INTEGER DEFAULT 0," + 2482 Mms.MESSAGE_BOX + " INTEGER," + 2483 Mms.READ + " INTEGER DEFAULT 0," + 2484 Mms.MESSAGE_ID + " TEXT," + 2485 Mms.SUBJECT + " TEXT," + 2486 Mms.SUBJECT_CHARSET + " INTEGER," + 2487 Mms.CONTENT_TYPE + " TEXT," + 2488 Mms.CONTENT_LOCATION + " TEXT," + 2489 Mms.EXPIRY + " INTEGER," + 2490 Mms.MESSAGE_CLASS + " TEXT," + 2491 Mms.MESSAGE_TYPE + " INTEGER," + 2492 Mms.MMS_VERSION + " INTEGER," + 2493 Mms.MESSAGE_SIZE + " INTEGER," + 2494 Mms.PRIORITY + " INTEGER," + 2495 Mms.READ_REPORT + " INTEGER," + 2496 Mms.REPORT_ALLOWED + " INTEGER," + 2497 Mms.RESPONSE_STATUS + " INTEGER," + 2498 Mms.STATUS + " INTEGER," + 2499 Mms.TRANSACTION_ID + " TEXT," + 2500 Mms.RETRIEVE_STATUS + " INTEGER," + 2501 Mms.RETRIEVE_TEXT + " TEXT," + 2502 Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," + 2503 Mms.READ_STATUS + " INTEGER," + 2504 Mms.CONTENT_CLASS + " INTEGER," + 2505 Mms.RESPONSE_TEXT + " TEXT," + 2506 Mms.DELIVERY_TIME + " INTEGER," + 2507 Mms.DELIVERY_REPORT + " INTEGER," + 2508 Mms.LOCKED + " INTEGER DEFAULT 0," + 2509 Mms.SUBSCRIPTION_ID + " INTEGER DEFAULT " 2510 + SubscriptionManager.INVALID_SUBSCRIPTION_ID + ", " + 2511 Mms.SEEN + " INTEGER DEFAULT 0," + 2512 Mms.TEXT_ONLY + " INTEGER DEFAULT 0" + 2513 ");"); 2514 2515 db.execSQL("INSERT INTO pdu_temp SELECT * from pdu;"); 2516 db.execSQL("DROP TABLE pdu;"); 2517 db.execSQL("ALTER TABLE pdu_temp RENAME TO pdu;"); 2518 2519 // pdu-related triggers get tossed when the part table is dropped -- rebuild them. 2520 createMmsTriggers(db); 2521 } 2522 2523 private class LowStorageMonitor extends BroadcastReceiver { 2524 LowStorageMonitor()2525 public LowStorageMonitor() { 2526 } 2527 onReceive(Context context, Intent intent)2528 public void onReceive(Context context, Intent intent) { 2529 String action = intent.getAction(); 2530 2531 Log.d(TAG, "[LowStorageMonitor] onReceive intent " + action); 2532 2533 if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 2534 sTriedAutoIncrement = false; // try to upgrade on the next getWriteableDatabase 2535 } 2536 } 2537 } 2538 updateThreadsAttachmentColumn(SQLiteDatabase db)2539 private void updateThreadsAttachmentColumn(SQLiteDatabase db) { 2540 // Set the values of that column correctly based on the current 2541 // contents of the database. 2542 db.execSQL("UPDATE threads SET has_attachment=1 WHERE _id IN " + 2543 " (SELECT DISTINCT pdu.thread_id FROM part " + 2544 " JOIN pdu ON pdu._id=part.mid " + 2545 " WHERE part.ct != 'text/plain' AND part.ct != 'application/smil')"); 2546 } 2547 isColumnExists(SQLiteDatabase db, String table, String column)2548 private boolean isColumnExists(SQLiteDatabase db, String table, String column) { 2549 boolean isExists = false; 2550 try (Cursor cursor = db.rawQuery("PRAGMA table_info("+ table +")", null)) { 2551 if (cursor != null) { 2552 while (cursor.moveToNext()) { 2553 String name = cursor.getString(cursor.getColumnIndex("name")); 2554 if (column.equalsIgnoreCase(name)) { 2555 isExists = true; 2556 break; 2557 } 2558 } 2559 } 2560 } 2561 Log.d(TAG, "tableName: " + table + " columnName: " + column + " isExists: " + isExists); 2562 return isExists; 2563 } 2564 2565 /** 2566 * Add the MMS/SMS database opening info to the debug log. 2567 */ addDatabaseOpeningDebugLog(@onNull String databaseOpeningLog, boolean isQuery)2568 public void addDatabaseOpeningDebugLog(@NonNull String databaseOpeningLog, boolean isQuery) { 2569 if (!Flags.logMmsSmsDatabaseAccessInfo()) { 2570 return; 2571 } 2572 addDatabaseOpeningDebugLog(isQuery ? mDatabaseReadOpeningInfos : mDatabaseWriteOpeningInfos, 2573 databaseOpeningLog); 2574 } 2575 2576 /** 2577 * Print the MMS/SMS database opening debug log to file. 2578 */ printDatabaseOpeningDebugLog()2579 public void printDatabaseOpeningDebugLog() { 2580 if (!Flags.logMmsSmsDatabaseAccessInfo()) { 2581 return; 2582 } 2583 Log.e(TAG, "MMS/SMS database read opening info: " 2584 + getDatabaseOpeningInfo(mDatabaseReadOpeningInfos)); 2585 Log.e(TAG, "MMS/SMS database write opening info: " 2586 + getDatabaseOpeningInfo(mDatabaseWriteOpeningInfos)); 2587 ProviderUtil.logRunningTelephonyProviderProcesses(mContext); 2588 } 2589 addDatabaseOpeningDebugLog(List<String> databaseOpeningInfos, @NonNull String callingPackage)2590 private void addDatabaseOpeningDebugLog(List<String> databaseOpeningInfos, 2591 @NonNull String callingPackage) { 2592 synchronized (mDatabaseOpeningInfoLock) { 2593 if (databaseOpeningInfos.size() >= MAX_DATABASE_OPENING_INFO_STORED) { 2594 databaseOpeningInfos.remove(0); 2595 } 2596 databaseOpeningInfos.add(buildDatabaseOpeningInfoStr(callingPackage)); 2597 } 2598 } 2599 buildDatabaseOpeningInfoStr(@onNull String databaseOpeningLog)2600 private String buildDatabaseOpeningInfoStr(@NonNull String databaseOpeningLog) { 2601 StringBuilder sb = new StringBuilder(); 2602 sb.append(DateFormat.format( 2603 "MM-dd HH:mm:ss.mmm", System.currentTimeMillis()).toString()); 2604 sb.append(" "); 2605 sb.append(databaseOpeningLog); 2606 return sb.toString(); 2607 } 2608 getDatabaseOpeningInfo(List<String> databaseOpeningInfos)2609 private String getDatabaseOpeningInfo(List<String> databaseOpeningInfos) { 2610 synchronized (mDatabaseOpeningInfoLock) { 2611 StringBuilder sb = new StringBuilder(); 2612 for (String databaseOpeningInfo : databaseOpeningInfos) { 2613 sb.append("{"); 2614 sb.append(databaseOpeningInfo); 2615 sb.append("}"); 2616 } 2617 return sb.toString(); 2618 } 2619 } 2620 reportAnomalyForDatabaseOpeningException(@onNull Exception ex)2621 private void reportAnomalyForDatabaseOpeningException(@NonNull Exception ex) { 2622 if (!Flags.logMmsSmsDatabaseAccessInfo()) { 2623 return; 2624 } 2625 Log.e(TAG, "DatabaseOpeningException=" + ex); 2626 printDatabaseOpeningDebugLog(); 2627 AnomalyReporter.reportAnomaly(DATABASE_OPENING_EXCEPTION_UUID, 2628 "MmsSmsDatabaseHelper: Got exception in opening SQLite database"); 2629 } 2630 } 2631