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