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