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