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