MmsSmsDatabaseHelper.java revision f88d1d6733158144e9e0c87f29b446068edf0507
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 17package com.android.providers.telephony; 18 19import java.io.IOException; 20import java.io.InputStream; 21import java.io.FileInputStream; 22import java.io.File; 23import java.util.ArrayList; 24import java.util.HashSet; 25import java.util.Iterator; 26 27import android.content.BroadcastReceiver; 28import android.content.ContentValues; 29import android.content.Context; 30import android.content.Intent; 31import android.content.IntentFilter; 32import android.database.Cursor; 33import android.database.sqlite.SQLiteDatabase; 34import android.database.sqlite.SQLiteOpenHelper; 35import android.provider.BaseColumns; 36import android.provider.Telephony; 37import android.provider.Telephony.Mms; 38import android.provider.Telephony.MmsSms; 39import android.provider.Telephony.Sms; 40import android.provider.Telephony.Threads; 41import android.provider.Telephony.Mms.Addr; 42import android.provider.Telephony.Mms.Part; 43import android.provider.Telephony.Mms.Rate; 44import android.provider.Telephony.MmsSms.PendingMessages; 45import android.util.Log; 46 47import com.google.android.mms.pdu.EncodedStringValue; 48import com.google.android.mms.pdu.PduHeaders; 49 50public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { 51 private static final String TAG = "MmsSmsDatabaseHelper"; 52 53 private static final String SMS_UPDATE_THREAD_READ_BODY = 54 " UPDATE threads SET read = " + 55 " CASE (SELECT COUNT(*)" + 56 " FROM sms" + 57 " WHERE " + Sms.READ + " = 0" + 58 " AND " + Sms.THREAD_ID + " = threads._id)" + 59 " WHEN 0 THEN 1" + 60 " ELSE 0" + 61 " END" + 62 " WHERE threads._id = new." + Sms.THREAD_ID + "; "; 63 64 private static final String UPDATE_THREAD_COUNT_ON_NEW = 65 " UPDATE threads SET message_count = " + 66 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 67 " ON threads._id = " + Sms.THREAD_ID + 68 " WHERE " + Sms.THREAD_ID + " = new.thread_id" + 69 " AND sms." + Sms.TYPE + " != 3) + " + 70 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 71 " ON threads._id = " + Mms.THREAD_ID + 72 " WHERE " + Mms.THREAD_ID + " = new.thread_id" + 73 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 74 " AND " + Mms.MESSAGE_BOX + " != 3) " + 75 " WHERE threads._id = new.thread_id; "; 76 77 private static final String UPDATE_THREAD_COUNT_ON_OLD = 78 " UPDATE threads SET message_count = " + 79 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 80 " ON threads._id = " + Sms.THREAD_ID + 81 " WHERE " + Sms.THREAD_ID + " = old.thread_id" + 82 " AND sms." + Sms.TYPE + " != 3) + " + 83 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 84 " ON threads._id = " + Mms.THREAD_ID + 85 " WHERE " + Mms.THREAD_ID + " = old.thread_id" + 86 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 87 " AND " + Mms.MESSAGE_BOX + " != 3) " + 88 " WHERE threads._id = old.thread_id; "; 89 90 private static final String SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE = 91 "BEGIN" + 92 " UPDATE threads SET" + 93 " date = (strftime('%s','now') * 1000), " + 94 " snippet = new." + Sms.BODY + ", " + 95 " snippet_cs = 0" + 96 " WHERE threads._id = new." + Sms.THREAD_ID + "; " + 97 UPDATE_THREAD_COUNT_ON_NEW + 98 SMS_UPDATE_THREAD_READ_BODY + 99 "END;"; 100 101 private static final String PDU_UPDATE_THREAD_CONSTRAINTS = 102 " WHEN new." + Mms.MESSAGE_TYPE + "=" + 103 PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF + 104 " OR new." + Mms.MESSAGE_TYPE + "=" + 105 PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + 106 " OR new." + Mms.MESSAGE_TYPE + "=" + 107 PduHeaders.MESSAGE_TYPE_SEND_REQ + " "; 108 109 // When looking in the pdu table for unread messages, only count messages that 110 // are displayed to the user. The constants are defined in PduHeaders and could be used 111 // here, but the string "(m_type=132 OR m_type=130 OR m_type=128)" is used throughout this 112 // file and so it is used here to be consistent. 113 // m_type=128 = MESSAGE_TYPE_SEND_REQ 114 // m_type=130 = MESSAGE_TYPE_NOTIFICATION_IND 115 // m_type=132 = MESSAGE_TYPE_RETRIEVE_CONF 116 private static final String PDU_UPDATE_THREAD_READ_BODY = 117 " UPDATE threads SET read = " + 118 " CASE (SELECT COUNT(*)" + 119 " FROM " + MmsProvider.TABLE_PDU + 120 " WHERE " + Mms.READ + " = 0" + 121 " AND " + Mms.THREAD_ID + " = threads._id " + 122 " AND (m_type=132 OR m_type=130 OR m_type=128)) " + 123 " WHEN 0 THEN 1" + 124 " ELSE 0" + 125 " END" + 126 " WHERE threads._id = new." + Mms.THREAD_ID + "; "; 127 128 private static final String PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE = 129 "BEGIN" + 130 " UPDATE threads SET" + 131 " date = (strftime('%s','now') * 1000), " + 132 " snippet = new." + Mms.SUBJECT + ", " + 133 " snippet_cs = new." + Mms.SUBJECT_CHARSET + 134 " WHERE threads._id = new." + Mms.THREAD_ID + "; " + 135 UPDATE_THREAD_COUNT_ON_NEW + 136 PDU_UPDATE_THREAD_READ_BODY + 137 "END;"; 138 139 private static final String UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE = 140 " UPDATE threads SET snippet = " + 141 " (SELECT snippet FROM" + 142 " (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" + 143 " UNION SELECT date, body AS snippet, thread_id FROM sms)" + 144 " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " + 145 " WHERE threads._id = OLD.thread_id; " + 146 " UPDATE threads SET snippet_cs = " + 147 " (SELECT snippet_cs FROM" + 148 " (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" + 149 " UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" + 150 " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " + 151 " WHERE threads._id = OLD.thread_id; "; 152 153 154 // When a part is inserted, if it is not text/plain or application/smil 155 // (which both can exist with text-only MMSes), then there is an attachment. 156 // Set has_attachment=1 in the threads table for the thread in question. 157 private static final String PART_UPDATE_THREADS_ON_INSERT_TRIGGER = 158 "CREATE TRIGGER update_threads_on_insert_part " + 159 " AFTER INSERT ON part " + 160 " WHEN new.ct != 'text/plain' AND new.ct != 'application/smil' " + 161 " BEGIN " + 162 " UPDATE threads SET has_attachment=1 WHERE _id IN " + 163 " (SELECT pdu.thread_id FROM part JOIN pdu ON pdu._id=part.mid " + 164 " WHERE part._id=new._id LIMIT 1); " + 165 " END"; 166 167 // When the 'mid' column in the part table is updated, we need to run the trigger to update 168 // the threads table's has_attachment column, if the part is an attachment. 169 private static final String PART_UPDATE_THREADS_ON_UPDATE_TRIGGER = 170 "CREATE TRIGGER update_threads_on_update_part " + 171 " AFTER UPDATE of " + Part.MSG_ID + " ON part " + 172 " WHEN new.ct != 'text/plain' AND new.ct != 'application/smil' " + 173 " BEGIN " + 174 " UPDATE threads SET has_attachment=1 WHERE _id IN " + 175 " (SELECT pdu.thread_id FROM part JOIN pdu ON pdu._id=part.mid " + 176 " WHERE part._id=new._id LIMIT 1); " + 177 " END"; 178 179 180 // When a part is deleted (with the same non-text/SMIL constraint as when 181 // we set has_attachment), update the threads table for all threads. 182 // Unfortunately we cannot update only the thread that the part was 183 // attached to, as it is possible that the part has been orphaned and 184 // the message it was attached to is already gone. 185 private static final String PART_UPDATE_THREADS_ON_DELETE_TRIGGER = 186 "CREATE TRIGGER update_threads_on_delete_part " + 187 " AFTER DELETE ON part " + 188 " WHEN old.ct != 'text/plain' AND old.ct != 'application/smil' " + 189 " BEGIN " + 190 " UPDATE threads SET has_attachment = " + 191 " CASE " + 192 " (SELECT COUNT(*) FROM part JOIN pdu " + 193 " WHERE pdu.thread_id = threads._id " + 194 " AND part.ct != 'text/plain' AND part.ct != 'application/smil' " + 195 " AND part.mid = pdu._id)" + 196 " WHEN 0 THEN 0 " + 197 " ELSE 1 " + 198 " END; " + 199 " END"; 200 201 // When the 'thread_id' column in the pdu table is updated, we need to run the trigger to update 202 // the threads table's has_attachment column, if the message has an attachment in 'part' table 203 private static final String PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER = 204 "CREATE TRIGGER update_threads_on_update_pdu " + 205 " AFTER UPDATE of thread_id ON pdu " + 206 " BEGIN " + 207 " UPDATE threads SET has_attachment=1 WHERE _id IN " + 208 " (SELECT pdu.thread_id FROM part JOIN pdu " + 209 " WHERE part.ct != 'text/plain' AND part.ct != 'application/smil' " + 210 " AND part.mid = pdu._id);" + 211 " END"; 212 213 private static MmsSmsDatabaseHelper sInstance = null; 214 private static boolean sTriedAutoIncrement = false; 215 private static boolean sFakeLowStorageTest = false; // for testing only 216 217 static final String DATABASE_NAME = "mmssms.db"; 218 static final int DATABASE_VERSION = 56; 219 private final Context mContext; 220 private LowStorageMonitor mLowStorageMonitor; 221 222 223 private MmsSmsDatabaseHelper(Context context) { 224 super(context, DATABASE_NAME, null, DATABASE_VERSION); 225 226 mContext = context; 227 } 228 229 /** 230 * Return a singleton helper for the combined MMS and SMS 231 * database. 232 */ 233 /* package */ static synchronized MmsSmsDatabaseHelper getInstance(Context context) { 234 if (sInstance == null) { 235 sInstance = new MmsSmsDatabaseHelper(context); 236 } 237 return sInstance; 238 } 239 240 /** 241 * Look through all the recipientIds referenced by the threads and then delete any 242 * unreferenced rows from the canonical_addresses table. 243 */ 244 private static void removeUnferencedCanonicalAddresses(SQLiteDatabase db) { 245 Cursor c = db.query("threads", new String[] { "recipient_ids" }, 246 null, null, null, null, null); 247 if (c != null) { 248 try { 249 if (c.getCount() == 0) { 250 // no threads, delete all addresses 251 int rows = db.delete("canonical_addresses", null, null); 252 } else { 253 // Find all the referenced recipient_ids from the threads. recipientIds is 254 // a space-separated list of recipient ids: "1 14 21" 255 HashSet<Integer> recipientIds = new HashSet<Integer>(); 256 while (c.moveToNext()) { 257 String[] recips = c.getString(0).split(" "); 258 for (String recip : recips) { 259 try { 260 int recipientId = Integer.parseInt(recip); 261 recipientIds.add(recipientId); 262 } catch (Exception e) { 263 } 264 } 265 } 266 // Now build a selection string of all the unique recipient ids 267 StringBuilder sb = new StringBuilder(); 268 Iterator<Integer> iter = recipientIds.iterator(); 269 while (iter.hasNext()) { 270 sb.append("_id != " + iter.next()); 271 if (iter.hasNext()) { 272 sb.append(" AND "); 273 } 274 } 275 if (sb.length() > 0) { 276 int rows = db.delete("canonical_addresses", sb.toString(), null); 277 } 278 } 279 } finally { 280 c.close(); 281 } 282 } 283 } 284 285 public static void updateThread(SQLiteDatabase db, long thread_id) { 286 if (thread_id < 0) { 287 updateAllThreads(db, null, null); 288 return; 289 } 290 291 // Delete the row for this thread in the threads table if 292 // there are no more messages attached to it in either 293 // the sms or pdu tables. 294 int rows = db.delete("threads", 295 "_id = ? AND _id NOT IN" + 296 " (SELECT thread_id FROM sms " + 297 " UNION SELECT thread_id FROM pdu)", 298 new String[] { String.valueOf(thread_id) }); 299 if (rows > 0) { 300 // If this deleted a row, let's remove orphaned canonical_addresses and get outta here 301 removeUnferencedCanonicalAddresses(db); 302 return; 303 } 304 // Update the message count in the threads table as the sum 305 // of all messages in both the sms and pdu tables. 306 db.execSQL( 307 " UPDATE threads SET message_count = " + 308 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 309 " ON threads._id = " + Sms.THREAD_ID + 310 " WHERE " + Sms.THREAD_ID + " = " + thread_id + 311 " AND sms." + Sms.TYPE + " != 3) + " + 312 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 313 " ON threads._id = " + Mms.THREAD_ID + 314 " WHERE " + Mms.THREAD_ID + " = " + thread_id + 315 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 316 " AND " + Mms.MESSAGE_BOX + " != 3) " + 317 " WHERE threads._id = " + thread_id + ";"); 318 319 // Update the date and the snippet (and its character set) in 320 // the threads table to be that of the most recent message in 321 // the thread. 322 db.execSQL( 323 " UPDATE threads" + 324 " SET" + 325 " date =" + 326 " (SELECT date FROM" + 327 " (SELECT date * 1000 AS date, thread_id FROM pdu" + 328 " UNION SELECT date, thread_id FROM sms)" + 329 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 330 " snippet =" + 331 " (SELECT snippet FROM" + 332 " (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" + 333 " UNION SELECT date, body AS snippet, thread_id FROM sms)" + 334 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 335 " snippet_cs =" + 336 " (SELECT snippet_cs FROM" + 337 " (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" + 338 " UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" + 339 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" + 340 " WHERE threads._id = " + thread_id + ";"); 341 342 // Update the error column of the thread to indicate if there 343 // are any messages in it that have failed to send. 344 // First check to see if there are any messages with errors in this thread. 345 String query = "SELECT thread_id FROM sms WHERE type=" + 346 Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED + 347 " AND thread_id = " + thread_id + 348 " LIMIT 1"; 349 int setError = 0; 350 Cursor c = db.rawQuery(query, null); 351 if (c != null) { 352 try { 353 setError = c.getCount(); // Because of the LIMIT 1, count will be 1 or 0. 354 } finally { 355 c.close(); 356 } 357 } 358 // What's the current state of the error flag in the threads table? 359 String errorQuery = "SELECT error FROM threads WHERE _id = " + thread_id; 360 c = db.rawQuery(errorQuery, null); 361 if (c != null) { 362 try { 363 if (c.moveToNext()) { 364 int curError = c.getInt(0); 365 if (curError != setError) { 366 // The current thread error column differs, update it. 367 db.execSQL("UPDATE threads SET error=" + setError + 368 " WHERE _id = " + thread_id); 369 } 370 } 371 } finally { 372 c.close(); 373 } 374 } 375 } 376 377 public static void updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs) { 378 if (where == null) { 379 where = ""; 380 } else { 381 where = "WHERE (" + where + ")"; 382 } 383 String query = "SELECT _id FROM threads WHERE _id IN " + 384 "(SELECT DISTINCT thread_id FROM sms " + where + ")"; 385 Cursor c = db.rawQuery(query, whereArgs); 386 if (c != null) { 387 try { 388 while (c.moveToNext()) { 389 updateThread(db, c.getInt(0)); 390 } 391 } finally { 392 c.close(); 393 } 394 } 395 // TODO: there are several db operations in this function. Lets wrap them in a 396 // transaction to make it faster. 397 // remove orphaned threads 398 db.delete("threads", 399 "_id NOT IN (SELECT DISTINCT thread_id FROM sms where thread_id NOT NULL " + 400 "UNION SELECT DISTINCT thread_id FROM pdu where thread_id NOT NULL)", null); 401 402 // remove orphaned canonical_addresses 403 removeUnferencedCanonicalAddresses(db); 404 } 405 406 public static int deleteOneSms(SQLiteDatabase db, int message_id) { 407 int thread_id = -1; 408 // Find the thread ID that the specified SMS belongs to. 409 Cursor c = db.query("sms", new String[] { "thread_id" }, 410 "_id=" + message_id, null, null, null, null); 411 if (c != null) { 412 if (c.moveToFirst()) { 413 thread_id = c.getInt(0); 414 } 415 c.close(); 416 } 417 418 // Delete the specified message. 419 int rows = db.delete("sms", "_id=" + message_id, null); 420 if (thread_id > 0) { 421 // Update its thread. 422 updateThread(db, thread_id); 423 } 424 return rows; 425 } 426 427 @Override 428 public void onCreate(SQLiteDatabase db) { 429 createMmsTables(db); 430 createSmsTables(db); 431 createCommonTables(db); 432 createCommonTriggers(db); 433 createMmsTriggers(db); 434 createWordsTables(db); 435 createIndices(db); 436 } 437 438 // When upgrading the database we need to populate the words 439 // table with the rows out of sms and part. 440 private void populateWordsTable(SQLiteDatabase db) { 441 final String TABLE_WORDS = "words"; 442 { 443 Cursor smsRows = db.query( 444 "sms", 445 new String[] { Sms._ID, Sms.BODY }, 446 null, 447 null, 448 null, 449 null, 450 null); 451 try { 452 if (smsRows != null) { 453 smsRows.moveToPosition(-1); 454 ContentValues cv = new ContentValues(); 455 while (smsRows.moveToNext()) { 456 cv.clear(); 457 458 long id = smsRows.getLong(0); // 0 for Sms._ID 459 String body = smsRows.getString(1); // 1 for Sms.BODY 460 461 cv.put(Telephony.MmsSms.WordsTable.ID, id); 462 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body); 463 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id); 464 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); 465 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 466 } 467 } 468 } finally { 469 if (smsRows != null) { 470 smsRows.close(); 471 } 472 } 473 } 474 475 { 476 Cursor mmsRows = db.query( 477 "part", 478 new String[] { Part._ID, Part.TEXT }, 479 "ct = 'text/plain'", 480 null, 481 null, 482 null, 483 null); 484 try { 485 if (mmsRows != null) { 486 mmsRows.moveToPosition(-1); 487 ContentValues cv = new ContentValues(); 488 while (mmsRows.moveToNext()) { 489 cv.clear(); 490 491 long id = mmsRows.getLong(0); // 0 for Part._ID 492 String body = mmsRows.getString(1); // 1 for Part.TEXT 493 494 cv.put(Telephony.MmsSms.WordsTable.ID, id); 495 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body); 496 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id); 497 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); 498 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 499 } 500 } 501 } finally { 502 if (mmsRows != null) { 503 mmsRows.close(); 504 } 505 } 506 } 507 } 508 509 private void createWordsTables(SQLiteDatabase db) { 510 try { 511 db.execSQL("CREATE VIRTUAL TABLE words USING FTS3 (_id INTEGER PRIMARY KEY, index_text TEXT, source_id INTEGER, table_to_use INTEGER);"); 512 513 // monitor the sms table 514 // NOTE don't handle inserts using a trigger because it has an unwanted 515 // side effect: the value returned for the last row ends up being the 516 // id of one of the trigger insert not the original row insert. 517 // Handle inserts manually in the provider. 518 db.execSQL("CREATE TRIGGER sms_words_update AFTER UPDATE ON sms BEGIN UPDATE words " + 519 " SET index_text = NEW.body WHERE (source_id=NEW._id AND table_to_use=1); " + 520 " END;"); 521 db.execSQL("CREATE TRIGGER sms_words_delete AFTER DELETE ON sms BEGIN DELETE FROM " + 522 " words WHERE source_id = OLD._id AND table_to_use = 1; END;"); 523 524 // monitor the mms table 525 db.execSQL("CREATE TRIGGER mms_words_update AFTER UPDATE ON part BEGIN UPDATE words " + 526 " SET index_text = NEW.text WHERE (source_id=NEW._id AND table_to_use=2); " + 527 " END;"); 528 db.execSQL("CREATE TRIGGER mms_words_delete AFTER DELETE ON part BEGIN DELETE FROM " + 529 " words WHERE source_id = OLD._id AND table_to_use = 2; END;"); 530 531 populateWordsTable(db); 532 } catch (Exception ex) { 533 Log.e(TAG, "got exception creating words table: " + ex.toString()); 534 } 535 } 536 537 private void createIndices(SQLiteDatabase db) { 538 createThreadIdIndex(db); 539 } 540 541 private void createThreadIdIndex(SQLiteDatabase db) { 542 try { 543 db.execSQL("CREATE INDEX IF NOT EXISTS typeThreadIdIndex ON sms" + 544 " (type, thread_id);"); 545 } catch (Exception ex) { 546 Log.e(TAG, "got exception creating indices: " + ex.toString()); 547 } 548 } 549 550 private void createMmsTables(SQLiteDatabase db) { 551 // N.B.: Whenever the columns here are changed, the columns in 552 // {@ref MmsSmsProvider} must be changed to match. 553 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" + 554 Mms._ID + " INTEGER PRIMARY KEY," + 555 Mms.THREAD_ID + " INTEGER," + 556 Mms.DATE + " INTEGER," + 557 Mms.DATE_SENT + " INTEGER DEFAULT 0," + 558 Mms.MESSAGE_BOX + " INTEGER," + 559 Mms.READ + " INTEGER DEFAULT 0," + 560 Mms.MESSAGE_ID + " TEXT," + 561 Mms.SUBJECT + " TEXT," + 562 Mms.SUBJECT_CHARSET + " INTEGER," + 563 Mms.CONTENT_TYPE + " TEXT," + 564 Mms.CONTENT_LOCATION + " TEXT," + 565 Mms.EXPIRY + " INTEGER," + 566 Mms.MESSAGE_CLASS + " TEXT," + 567 Mms.MESSAGE_TYPE + " INTEGER," + 568 Mms.MMS_VERSION + " INTEGER," + 569 Mms.MESSAGE_SIZE + " INTEGER," + 570 Mms.PRIORITY + " INTEGER," + 571 Mms.READ_REPORT + " INTEGER," + 572 Mms.REPORT_ALLOWED + " INTEGER," + 573 Mms.RESPONSE_STATUS + " INTEGER," + 574 Mms.STATUS + " INTEGER," + 575 Mms.TRANSACTION_ID + " TEXT," + 576 Mms.RETRIEVE_STATUS + " INTEGER," + 577 Mms.RETRIEVE_TEXT + " TEXT," + 578 Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," + 579 Mms.READ_STATUS + " INTEGER," + 580 Mms.CONTENT_CLASS + " INTEGER," + 581 Mms.RESPONSE_TEXT + " TEXT," + 582 Mms.DELIVERY_TIME + " INTEGER," + 583 Mms.DELIVERY_REPORT + " INTEGER," + 584 Mms.LOCKED + " INTEGER DEFAULT 0," + 585 Mms.SEEN + " INTEGER DEFAULT 0" + 586 Mms.TEXT_ONLY + " INTEGER DEFAULT 0" + 587 ");"); 588 589 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" + 590 Addr._ID + " INTEGER PRIMARY KEY," + 591 Addr.MSG_ID + " INTEGER," + 592 Addr.CONTACT_ID + " INTEGER," + 593 Addr.ADDRESS + " TEXT," + 594 Addr.TYPE + " INTEGER," + 595 Addr.CHARSET + " INTEGER);"); 596 597 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PART + " (" + 598 Part._ID + " INTEGER PRIMARY KEY," + 599 Part.MSG_ID + " INTEGER," + 600 Part.SEQ + " INTEGER DEFAULT 0," + 601 Part.CONTENT_TYPE + " TEXT," + 602 Part.NAME + " TEXT," + 603 Part.CHARSET + " INTEGER," + 604 Part.CONTENT_DISPOSITION + " TEXT," + 605 Part.FILENAME + " TEXT," + 606 Part.CONTENT_ID + " TEXT," + 607 Part.CONTENT_LOCATION + " TEXT," + 608 Part.CT_START + " INTEGER," + 609 Part.CT_TYPE + " TEXT," + 610 Part._DATA + " TEXT," + 611 Part.TEXT + " TEXT);"); 612 613 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_RATE + " (" + 614 Rate.SENT_TIME + " INTEGER);"); 615 616 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_DRM + " (" + 617 BaseColumns._ID + " INTEGER PRIMARY KEY," + 618 "_data TEXT);"); 619 } 620 621 private void createMmsTriggers(SQLiteDatabase db) { 622 // Cleans up parts when a MM is deleted. 623 db.execSQL("CREATE TRIGGER part_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 624 "BEGIN " + 625 " DELETE FROM " + MmsProvider.TABLE_PART + 626 " WHERE " + Part.MSG_ID + "=old._id;" + 627 "END;"); 628 629 // Cleans up address info when a MM is deleted. 630 db.execSQL("CREATE TRIGGER addr_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 631 "BEGIN " + 632 " DELETE FROM " + MmsProvider.TABLE_ADDR + 633 " WHERE " + Addr.MSG_ID + "=old._id;" + 634 "END;"); 635 636 // Delete obsolete delivery-report, read-report while deleting their 637 // associated Send.req. 638 db.execSQL("CREATE TRIGGER cleanup_delivery_and_read_report " + 639 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 640 "WHEN old." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ + " " + 641 "BEGIN " + 642 " DELETE FROM " + MmsProvider.TABLE_PDU + 643 " WHERE (" + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_DELIVERY_IND + 644 " OR " + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_ORIG_IND + 645 ")" + 646 " AND " + Mms.MESSAGE_ID + "=old." + Mms.MESSAGE_ID + "; " + 647 "END;"); 648 649 // Update threads table to indicate whether attachments exist when 650 // parts are inserted or deleted. 651 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 652 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 653 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 654 db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER); 655 } 656 657 private void createSmsTables(SQLiteDatabase db) { 658 // N.B.: Whenever the columns here are changed, the columns in 659 // {@ref MmsSmsProvider} must be changed to match. 660 db.execSQL("CREATE TABLE sms (" + 661 "_id INTEGER PRIMARY KEY," + 662 "thread_id INTEGER," + 663 "address TEXT," + 664 "person INTEGER," + 665 "date INTEGER," + 666 "date_sent INTEGER DEFAULT 0," + 667 "protocol INTEGER," + 668 "read INTEGER DEFAULT 0," + 669 "status INTEGER DEFAULT -1," + // a TP-Status value 670 // or -1 if it 671 // status hasn't 672 // been received 673 "type INTEGER," + 674 "reply_path_present INTEGER," + 675 "subject TEXT," + 676 "body TEXT," + 677 "service_center TEXT," + 678 "locked INTEGER DEFAULT 0," + 679 "error_code INTEGER DEFAULT 0," + 680 "seen INTEGER DEFAULT 0" + 681 ");"); 682 683 /** 684 * This table is used by the SMS dispatcher to hold 685 * incomplete partial messages until all the parts arrive. 686 */ 687 db.execSQL("CREATE TABLE raw (" + 688 "_id INTEGER PRIMARY KEY," + 689 "date INTEGER," + 690 "reference_number INTEGER," + // one per full message 691 "count INTEGER," + // the number of parts 692 "sequence INTEGER," + // the part number of this message 693 "destination_port INTEGER," + 694 "address TEXT," + 695 "pdu TEXT);"); // the raw PDU for this part 696 697 db.execSQL("CREATE TABLE attachments (" + 698 "sms_id INTEGER," + 699 "content_url TEXT," + 700 "offset INTEGER);"); 701 702 /** 703 * This table is used by the SMS dispatcher to hold pending 704 * delivery status report intents. 705 */ 706 db.execSQL("CREATE TABLE sr_pending (" + 707 "reference_number INTEGER," + 708 "action TEXT," + 709 "data TEXT);"); 710 } 711 712 private void createCommonTables(SQLiteDatabase db) { 713 // TODO Ensure that each entry is removed when the last use of 714 // any address equivalent to its address is removed. 715 716 /** 717 * This table maps the first instance seen of any particular 718 * MMS/SMS address to an ID, which is then used as its 719 * canonical representation. If the same address or an 720 * equivalent address (as determined by our Sqlite 721 * PHONE_NUMBERS_EQUAL extension) is seen later, this same ID 722 * will be used. The _id is created with AUTOINCREMENT so it 723 * will never be reused again if a recipient is deleted. 724 */ 725 db.execSQL("CREATE TABLE canonical_addresses (" + 726 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 727 "address TEXT);"); 728 729 /** 730 * This table maps the subject and an ordered set of recipient 731 * IDs, separated by spaces, to a unique thread ID. The IDs 732 * come from the canonical_addresses table. This works 733 * because messages are considered to be part of the same 734 * thread if they have the same subject (or a null subject) 735 * and the same set of recipients. 736 */ 737 db.execSQL("CREATE TABLE threads (" + 738 Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 739 Threads.DATE + " INTEGER DEFAULT 0," + 740 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," + 741 Threads.RECIPIENT_IDS + " TEXT," + 742 Threads.SNIPPET + " TEXT," + 743 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," + 744 Threads.READ + " INTEGER DEFAULT 1," + 745 Threads.TYPE + " INTEGER DEFAULT 0," + 746 Threads.ERROR + " INTEGER DEFAULT 0," + 747 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);"); 748 749 /** 750 * This table stores the queue of messages to be sent/downloaded. 751 */ 752 db.execSQL("CREATE TABLE " + MmsSmsProvider.TABLE_PENDING_MSG +" (" + 753 PendingMessages._ID + " INTEGER PRIMARY KEY," + 754 PendingMessages.PROTO_TYPE + " INTEGER," + 755 PendingMessages.MSG_ID + " INTEGER," + 756 PendingMessages.MSG_TYPE + " INTEGER," + 757 PendingMessages.ERROR_TYPE + " INTEGER," + 758 PendingMessages.ERROR_CODE + " INTEGER," + 759 PendingMessages.RETRY_INDEX + " INTEGER NOT NULL DEFAULT 0," + 760 PendingMessages.DUE_TIME + " INTEGER," + 761 PendingMessages.LAST_TRY + " INTEGER);"); 762 763 } 764 765 // TODO Check the query plans for these triggers. 766 private void createCommonTriggers(SQLiteDatabase db) { 767 // Updates threads table whenever a message is added to pdu. 768 db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " + 769 MmsProvider.TABLE_PDU + " " + 770 PDU_UPDATE_THREAD_CONSTRAINTS + 771 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 772 773 // Updates threads table whenever a message is added to sms. 774 db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " + 775 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 776 777 // Updates threads table whenever a message in pdu is updated. 778 db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" + 779 " UPDATE OF " + Mms.DATE + ", " + Mms.SUBJECT + ", " + Mms.MESSAGE_BOX + 780 " ON " + MmsProvider.TABLE_PDU + " " + 781 PDU_UPDATE_THREAD_CONSTRAINTS + 782 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 783 784 // Updates threads table whenever a message in sms is updated. 785 db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" + 786 " UPDATE OF " + Sms.DATE + ", " + Sms.BODY + ", " + Sms.TYPE + 787 " ON sms " + 788 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 789 790 // Updates threads table whenever a message in pdu is updated. 791 db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" + 792 " UPDATE OF " + Mms.READ + 793 " ON " + MmsProvider.TABLE_PDU + " " + 794 PDU_UPDATE_THREAD_CONSTRAINTS + 795 "BEGIN " + 796 PDU_UPDATE_THREAD_READ_BODY + 797 "END;"); 798 799 // Updates threads table whenever a message in sms is updated. 800 db.execSQL("CREATE TRIGGER sms_update_thread_read_on_update AFTER" + 801 " UPDATE OF " + Sms.READ + 802 " ON sms " + 803 "BEGIN " + 804 SMS_UPDATE_THREAD_READ_BODY + 805 "END;"); 806 807 // Update threads table whenever a message in pdu is deleted 808 db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " + 809 "AFTER DELETE ON pdu " + 810 "BEGIN " + 811 " UPDATE threads SET " + 812 " date = (strftime('%s','now') * 1000)" + 813 " WHERE threads._id = old." + Mms.THREAD_ID + "; " + 814 UPDATE_THREAD_COUNT_ON_OLD + 815 UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE + 816 "END;"); 817 818 // As of DATABASE_VERSION 55, we've removed these triggers that delete empty threads. 819 // These triggers interfere with saving drafts on brand new threads. Instead of 820 // triggers cleaning up empty threads, the empty threads should be cleaned up by 821 // an explicit call to delete with Threads.OBSOLETE_THREADS_URI. 822 823// // When the last message in a thread is deleted, these 824// // triggers ensure that the entry for its thread ID is removed 825// // from the threads table. 826// db.execSQL("CREATE TRIGGER delete_obsolete_threads_pdu " + 827// "AFTER DELETE ON pdu " + 828// "BEGIN " + 829// " DELETE FROM threads " + 830// " WHERE " + 831// " _id = old.thread_id " + 832// " AND _id NOT IN " + 833// " (SELECT thread_id FROM sms " + 834// " UNION SELECT thread_id from pdu); " + 835// "END;"); 836// 837// db.execSQL("CREATE TRIGGER delete_obsolete_threads_when_update_pdu " + 838// "AFTER UPDATE OF " + Mms.THREAD_ID + " ON pdu " + 839// "WHEN old." + Mms.THREAD_ID + " != new." + Mms.THREAD_ID + " " + 840// "BEGIN " + 841// " DELETE FROM threads " + 842// " WHERE " + 843// " _id = old.thread_id " + 844// " AND _id NOT IN " + 845// " (SELECT thread_id FROM sms " + 846// " UNION SELECT thread_id from pdu); " + 847// "END;"); 848 849 // Insert pending status for M-Notification.ind or M-ReadRec.ind 850 // when they are inserted into Inbox/Outbox. 851 db.execSQL("CREATE TRIGGER insert_mms_pending_on_insert " + 852 "AFTER INSERT ON pdu " + 853 "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + 854 " OR new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_REC_IND + 855 " " + 856 "BEGIN " + 857 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 858 " (" + PendingMessages.PROTO_TYPE + "," + 859 " " + PendingMessages.MSG_ID + "," + 860 " " + PendingMessages.MSG_TYPE + "," + 861 " " + PendingMessages.ERROR_TYPE + "," + 862 " " + PendingMessages.ERROR_CODE + "," + 863 " " + PendingMessages.RETRY_INDEX + "," + 864 " " + PendingMessages.DUE_TIME + ") " + 865 " VALUES " + 866 " (" + MmsSms.MMS_PROTO + "," + 867 " new." + BaseColumns._ID + "," + 868 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 869 "END;"); 870 871 // Insert pending status for M-Send.req when it is moved into Outbox. 872 db.execSQL("CREATE TRIGGER insert_mms_pending_on_update " + 873 "AFTER UPDATE ON pdu " + 874 "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ + 875 " AND new." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 876 " AND old." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 877 "BEGIN " + 878 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 879 " (" + PendingMessages.PROTO_TYPE + "," + 880 " " + PendingMessages.MSG_ID + "," + 881 " " + PendingMessages.MSG_TYPE + "," + 882 " " + PendingMessages.ERROR_TYPE + "," + 883 " " + PendingMessages.ERROR_CODE + "," + 884 " " + PendingMessages.RETRY_INDEX + "," + 885 " " + PendingMessages.DUE_TIME + ") " + 886 " VALUES " + 887 " (" + MmsSms.MMS_PROTO + "," + 888 " new." + BaseColumns._ID + "," + 889 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 890 "END;"); 891 892 // When a message is moved out of Outbox, delete its pending status. 893 db.execSQL("CREATE TRIGGER delete_mms_pending_on_update " + 894 "AFTER UPDATE ON " + MmsProvider.TABLE_PDU + " " + 895 "WHEN old." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 896 " AND new." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 897 "BEGIN " + 898 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 899 " WHERE " + PendingMessages.MSG_ID + "=new._id; " + 900 "END;"); 901 902 // Delete pending status for a message when it is deleted. 903 db.execSQL("CREATE TRIGGER delete_mms_pending_on_delete " + 904 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 905 "BEGIN " + 906 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 907 " WHERE " + PendingMessages.MSG_ID + "=old._id; " + 908 "END;"); 909 910 // TODO Add triggers for SMS retry-status management. 911 912 // Update the error flag of threads when the error type of 913 // a pending MM is updated. 914 db.execSQL("CREATE TRIGGER update_threads_error_on_update_mms " + 915 " AFTER UPDATE OF err_type ON pending_msgs " + 916 " WHEN (OLD.err_type < 10 AND NEW.err_type >= 10)" + 917 " OR (OLD.err_type >= 10 AND NEW.err_type < 10) " + 918 "BEGIN" + 919 " UPDATE threads SET error = " + 920 " CASE" + 921 " WHEN NEW.err_type >= 10 THEN error + 1" + 922 " ELSE error - 1" + 923 " END " + 924 " WHERE _id =" + 925 " (SELECT DISTINCT thread_id" + 926 " FROM pdu" + 927 " WHERE _id = NEW.msg_id); " + 928 "END;"); 929 930 // Update the error flag of threads when delete pending message. 931 db.execSQL("CREATE TRIGGER update_threads_error_on_delete_mms " + 932 " BEFORE DELETE ON pdu" + 933 " WHEN OLD._id IN (SELECT DISTINCT msg_id" + 934 " FROM pending_msgs" + 935 " WHERE err_type >= 10) " + 936 "BEGIN " + 937 " UPDATE threads SET error = error - 1" + 938 " WHERE _id = OLD.thread_id; " + 939 "END;"); 940 941 // Update the error flag of threads while moving an MM out of Outbox, 942 // which was failed to be sent permanently. 943 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 944 " BEFORE UPDATE OF msg_box ON pdu " + 945 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 946 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 947 " FROM pending_msgs" + 948 " WHERE err_type >= 10)) " + 949 "BEGIN " + 950 " UPDATE threads SET error = error - 1" + 951 " WHERE _id = OLD.thread_id; " + 952 "END;"); 953 954 // Update the error flag of threads after a text message was 955 // failed to send/receive. 956 db.execSQL("CREATE TRIGGER update_threads_error_on_update_sms " + 957 " AFTER UPDATE OF type ON sms" + 958 " WHEN (OLD.type != 5 AND NEW.type = 5)" + 959 " OR (OLD.type = 5 AND NEW.type != 5) " + 960 "BEGIN " + 961 " UPDATE threads SET error = " + 962 " CASE" + 963 " WHEN NEW.type = 5 THEN error + 1" + 964 " ELSE error - 1" + 965 " END " + 966 " WHERE _id = NEW.thread_id; " + 967 "END;"); 968 } 969 970 @Override 971 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 972 Log.w(TAG, "Upgrading database from version " + oldVersion 973 + " to " + currentVersion + "."); 974 975 switch (oldVersion) { 976 case 40: 977 if (currentVersion <= 40) { 978 return; 979 } 980 981 db.beginTransaction(); 982 try { 983 upgradeDatabaseToVersion41(db); 984 db.setTransactionSuccessful(); 985 } catch (Throwable ex) { 986 Log.e(TAG, ex.getMessage(), ex); 987 break; 988 } finally { 989 db.endTransaction(); 990 } 991 // fall through 992 case 41: 993 if (currentVersion <= 41) { 994 return; 995 } 996 997 db.beginTransaction(); 998 try { 999 upgradeDatabaseToVersion42(db); 1000 db.setTransactionSuccessful(); 1001 } catch (Throwable ex) { 1002 Log.e(TAG, ex.getMessage(), ex); 1003 break; 1004 } finally { 1005 db.endTransaction(); 1006 } 1007 // fall through 1008 case 42: 1009 if (currentVersion <= 42) { 1010 return; 1011 } 1012 1013 db.beginTransaction(); 1014 try { 1015 upgradeDatabaseToVersion43(db); 1016 db.setTransactionSuccessful(); 1017 } catch (Throwable ex) { 1018 Log.e(TAG, ex.getMessage(), ex); 1019 break; 1020 } finally { 1021 db.endTransaction(); 1022 } 1023 // fall through 1024 case 43: 1025 if (currentVersion <= 43) { 1026 return; 1027 } 1028 1029 db.beginTransaction(); 1030 try { 1031 upgradeDatabaseToVersion44(db); 1032 db.setTransactionSuccessful(); 1033 } catch (Throwable ex) { 1034 Log.e(TAG, ex.getMessage(), ex); 1035 break; 1036 } finally { 1037 db.endTransaction(); 1038 } 1039 // fall through 1040 case 44: 1041 if (currentVersion <= 44) { 1042 return; 1043 } 1044 1045 db.beginTransaction(); 1046 try { 1047 upgradeDatabaseToVersion45(db); 1048 db.setTransactionSuccessful(); 1049 } catch (Throwable ex) { 1050 Log.e(TAG, ex.getMessage(), ex); 1051 break; 1052 } finally { 1053 db.endTransaction(); 1054 } 1055 // fall through 1056 case 45: 1057 if (currentVersion <= 45) { 1058 return; 1059 } 1060 db.beginTransaction(); 1061 try { 1062 upgradeDatabaseToVersion46(db); 1063 db.setTransactionSuccessful(); 1064 } catch (Throwable ex) { 1065 Log.e(TAG, ex.getMessage(), ex); 1066 break; 1067 } finally { 1068 db.endTransaction(); 1069 } 1070 // fall through 1071 case 46: 1072 if (currentVersion <= 46) { 1073 return; 1074 } 1075 1076 db.beginTransaction(); 1077 try { 1078 upgradeDatabaseToVersion47(db); 1079 db.setTransactionSuccessful(); 1080 } catch (Throwable ex) { 1081 Log.e(TAG, ex.getMessage(), ex); 1082 break; 1083 } finally { 1084 db.endTransaction(); 1085 } 1086 // fall through 1087 case 47: 1088 if (currentVersion <= 47) { 1089 return; 1090 } 1091 1092 db.beginTransaction(); 1093 try { 1094 upgradeDatabaseToVersion48(db); 1095 db.setTransactionSuccessful(); 1096 } catch (Throwable ex) { 1097 Log.e(TAG, ex.getMessage(), ex); 1098 break; 1099 } finally { 1100 db.endTransaction(); 1101 } 1102 // fall through 1103 case 48: 1104 if (currentVersion <= 48) { 1105 return; 1106 } 1107 1108 db.beginTransaction(); 1109 try { 1110 createWordsTables(db); 1111 db.setTransactionSuccessful(); 1112 } catch (Throwable ex) { 1113 Log.e(TAG, ex.getMessage(), ex); 1114 break; 1115 } finally { 1116 db.endTransaction(); 1117 } 1118 // fall through 1119 case 49: 1120 if (currentVersion <= 49) { 1121 return; 1122 } 1123 db.beginTransaction(); 1124 try { 1125 createThreadIdIndex(db); 1126 db.setTransactionSuccessful(); 1127 } catch (Throwable ex) { 1128 Log.e(TAG, ex.getMessage(), ex); 1129 break; // force to destroy all old data; 1130 } finally { 1131 db.endTransaction(); 1132 } 1133 // fall through 1134 case 50: 1135 if (currentVersion <= 50) { 1136 return; 1137 } 1138 1139 db.beginTransaction(); 1140 try { 1141 upgradeDatabaseToVersion51(db); 1142 db.setTransactionSuccessful(); 1143 } catch (Throwable ex) { 1144 Log.e(TAG, ex.getMessage(), ex); 1145 break; 1146 } finally { 1147 db.endTransaction(); 1148 } 1149 // fall through 1150 case 51: 1151 if (currentVersion <= 51) { 1152 return; 1153 } 1154 // 52 was adding a new meta_data column, but that was removed. 1155 // fall through 1156 case 52: 1157 if (currentVersion <= 52) { 1158 return; 1159 } 1160 1161 db.beginTransaction(); 1162 try { 1163 upgradeDatabaseToVersion53(db); 1164 db.setTransactionSuccessful(); 1165 } catch (Throwable ex) { 1166 Log.e(TAG, ex.getMessage(), ex); 1167 break; 1168 } finally { 1169 db.endTransaction(); 1170 } 1171 // fall through 1172 case 53: 1173 if (currentVersion <= 53) { 1174 return; 1175 } 1176 1177 db.beginTransaction(); 1178 try { 1179 upgradeDatabaseToVersion54(db); 1180 db.setTransactionSuccessful(); 1181 } catch (Throwable ex) { 1182 Log.e(TAG, ex.getMessage(), ex); 1183 break; 1184 } finally { 1185 db.endTransaction(); 1186 } 1187 // fall through 1188 case 54: 1189 if (currentVersion <= 54) { 1190 return; 1191 } 1192 1193 db.beginTransaction(); 1194 try { 1195 upgradeDatabaseToVersion55(db); 1196 db.setTransactionSuccessful(); 1197 } catch (Throwable ex) { 1198 Log.e(TAG, ex.getMessage(), ex); 1199 break; 1200 } finally { 1201 db.endTransaction(); 1202 } 1203 // fall through 1204 case 55: 1205 if (currentVersion <= 55) { 1206 return; 1207 } 1208 1209 db.beginTransaction(); 1210 try { 1211 upgradeDatabaseToVersion56(db); 1212 db.setTransactionSuccessful(); 1213 } catch (Throwable ex) { 1214 Log.e(TAG, ex.getMessage(), ex); 1215 break; 1216 } finally { 1217 db.endTransaction(); 1218 } 1219 return; 1220 } 1221 1222 Log.e(TAG, "Destroying all old data."); 1223 dropAll(db); 1224 onCreate(db); 1225 } 1226 1227 private void dropAll(SQLiteDatabase db) { 1228 // Clean the database out in order to start over from scratch. 1229 // We don't need to drop our triggers here because SQLite automatically 1230 // drops a trigger when its attached database is dropped. 1231 db.execSQL("DROP TABLE IF EXISTS canonical_addresses"); 1232 db.execSQL("DROP TABLE IF EXISTS threads"); 1233 db.execSQL("DROP TABLE IF EXISTS " + MmsSmsProvider.TABLE_PENDING_MSG); 1234 db.execSQL("DROP TABLE IF EXISTS sms"); 1235 db.execSQL("DROP TABLE IF EXISTS raw"); 1236 db.execSQL("DROP TABLE IF EXISTS attachments"); 1237 db.execSQL("DROP TABLE IF EXISTS thread_ids"); 1238 db.execSQL("DROP TABLE IF EXISTS sr_pending"); 1239 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PDU + ";"); 1240 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_ADDR + ";"); 1241 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PART + ";"); 1242 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_RATE + ";"); 1243 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_DRM + ";"); 1244 } 1245 1246 private void upgradeDatabaseToVersion41(SQLiteDatabase db) { 1247 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms"); 1248 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 1249 " BEFORE UPDATE OF msg_box ON pdu " + 1250 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 1251 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 1252 " FROM pending_msgs" + 1253 " WHERE err_type >= 10)) " + 1254 "BEGIN " + 1255 " UPDATE threads SET error = error - 1" + 1256 " WHERE _id = OLD.thread_id; " + 1257 "END;"); 1258 } 1259 1260 private void upgradeDatabaseToVersion42(SQLiteDatabase db) { 1261 db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete"); 1262 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_sms"); 1263 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_sms"); 1264 } 1265 1266 private void upgradeDatabaseToVersion43(SQLiteDatabase db) { 1267 // Add 'has_attachment' column to threads table. 1268 db.execSQL("ALTER TABLE threads ADD COLUMN has_attachment INTEGER DEFAULT 0"); 1269 1270 updateThreadsAttachmentColumn(db); 1271 1272 // Add insert and delete triggers for keeping it up to date. 1273 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 1274 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 1275 } 1276 1277 private void upgradeDatabaseToVersion44(SQLiteDatabase db) { 1278 updateThreadsAttachmentColumn(db); 1279 1280 // add the update trigger for keeping the threads up to date. 1281 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 1282 } 1283 1284 private void upgradeDatabaseToVersion45(SQLiteDatabase db) { 1285 // Add 'locked' column to sms table. 1286 db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.LOCKED + " INTEGER DEFAULT 0"); 1287 1288 // Add 'locked' column to pdu table. 1289 db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.LOCKED + " INTEGER DEFAULT 0"); 1290 } 1291 1292 private void upgradeDatabaseToVersion46(SQLiteDatabase db) { 1293 // add the "text" column for caching inline text (e.g. strings) instead of 1294 // putting them in an external file 1295 db.execSQL("ALTER TABLE part ADD COLUMN " + Part.TEXT + " TEXT"); 1296 1297 Cursor textRows = db.query( 1298 "part", 1299 new String[] { Part._ID, Part._DATA, Part.TEXT}, 1300 "ct = 'text/plain' OR ct == 'application/smil'", 1301 null, 1302 null, 1303 null, 1304 null); 1305 ArrayList<String> filesToDelete = new ArrayList<String>(); 1306 try { 1307 db.beginTransaction(); 1308 if (textRows != null) { 1309 int partDataColumn = textRows.getColumnIndex(Part._DATA); 1310 1311 // This code is imperfect in that we can't guarantee that all the 1312 // backing files get deleted. For example if the system aborts after 1313 // the database is updated but before we complete the process of 1314 // deleting files. 1315 while (textRows.moveToNext()) { 1316 String path = textRows.getString(partDataColumn); 1317 if (path != null) { 1318 try { 1319 InputStream is = new FileInputStream(path); 1320 byte [] data = new byte[is.available()]; 1321 is.read(data); 1322 EncodedStringValue v = new EncodedStringValue(data); 1323 db.execSQL("UPDATE part SET " + Part._DATA + " = NULL, " + 1324 Part.TEXT + " = ?", new String[] { v.getString() }); 1325 is.close(); 1326 filesToDelete.add(path); 1327 } catch (IOException e) { 1328 // TODO Auto-generated catch block 1329 e.printStackTrace(); 1330 } 1331 } 1332 } 1333 } 1334 db.setTransactionSuccessful(); 1335 } finally { 1336 db.endTransaction(); 1337 for (String pathToDelete : filesToDelete) { 1338 try { 1339 (new File(pathToDelete)).delete(); 1340 } catch (SecurityException ex) { 1341 Log.e(TAG, "unable to clean up old mms file for " + pathToDelete, ex); 1342 } 1343 } 1344 if (textRows != null) { 1345 textRows.close(); 1346 } 1347 } 1348 } 1349 1350 private void upgradeDatabaseToVersion47(SQLiteDatabase db) { 1351 updateThreadsAttachmentColumn(db); 1352 1353 // add the update trigger for keeping the threads up to date. 1354 db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER); 1355 } 1356 1357 private void upgradeDatabaseToVersion48(SQLiteDatabase db) { 1358 // Add 'error_code' column to sms table. 1359 db.execSQL("ALTER TABLE sms ADD COLUMN error_code INTEGER DEFAULT 0"); 1360 } 1361 1362 private void upgradeDatabaseToVersion51(SQLiteDatabase db) { 1363 db.execSQL("ALTER TABLE sms add COLUMN seen INTEGER DEFAULT 0"); 1364 db.execSQL("ALTER TABLE pdu add COLUMN seen INTEGER DEFAULT 0"); 1365 1366 try { 1367 // update the existing sms and pdu tables so the new "seen" column is the same as 1368 // the "read" column for each row. 1369 ContentValues contentValues = new ContentValues(); 1370 contentValues.put("seen", 1); 1371 int count = db.update("sms", contentValues, "read=1", null); 1372 Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count + 1373 " rows in sms table to have READ=1"); 1374 count = db.update("pdu", contentValues, "read=1", null); 1375 Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count + 1376 " rows in pdu table to have READ=1"); 1377 } catch (Exception ex) { 1378 Log.e(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51 caught ", ex); 1379 } 1380 } 1381 1382 private void upgradeDatabaseToVersion53(SQLiteDatabase db) { 1383 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update"); 1384 1385 // Updates threads table whenever a message in pdu is updated. 1386 db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" + 1387 " UPDATE OF " + Mms.READ + 1388 " ON " + MmsProvider.TABLE_PDU + " " + 1389 PDU_UPDATE_THREAD_CONSTRAINTS + 1390 "BEGIN " + 1391 PDU_UPDATE_THREAD_READ_BODY + 1392 "END;"); 1393 } 1394 1395 private void upgradeDatabaseToVersion54(SQLiteDatabase db) { 1396 // Add 'date_sent' column to sms table. 1397 db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.DATE_SENT + " INTEGER DEFAULT 0"); 1398 1399 // Add 'date_sent' column to pdu table. 1400 db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.DATE_SENT + " INTEGER DEFAULT 0"); 1401 } 1402 1403 private void upgradeDatabaseToVersion55(SQLiteDatabase db) { 1404 // Drop removed triggers 1405 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_pdu"); 1406 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_when_update_pdu"); 1407 } 1408 1409 private void upgradeDatabaseToVersion56(SQLiteDatabase db) { 1410 // Add 'text_only' column to pdu table. 1411 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU + " ADD COLUMN " + Mms.TEXT_ONLY + 1412 " INTEGER DEFAULT 0"); 1413 } 1414 1415 @Override 1416 public synchronized SQLiteDatabase getWritableDatabase() { 1417 SQLiteDatabase db = super.getWritableDatabase(); 1418 1419 if (!sTriedAutoIncrement) { 1420 sTriedAutoIncrement = true; 1421 boolean hasAutoIncrementThreads = hasAutoIncrement(db, "threads"); 1422 boolean hasAutoIncrementAddresses = hasAutoIncrement(db, "canonical_addresses"); 1423 Log.d(TAG, "[getWritableDatabase] hasAutoIncrementThreads: " + hasAutoIncrementThreads + 1424 " hasAutoIncrementAddresses: " + hasAutoIncrementAddresses); 1425 boolean autoIncrementThreadsSuccess = true; 1426 boolean autoIncrementAddressesSuccess = true; 1427 if (!hasAutoIncrementThreads) { 1428 db.beginTransaction(); 1429 try { 1430 if (false && sFakeLowStorageTest) { 1431 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 1432 " - fake exception"); 1433 throw new Exception("FakeLowStorageTest"); 1434 } 1435 upgradeThreadsTableToAutoIncrement(db); // a no-op if already upgraded 1436 db.setTransactionSuccessful(); 1437 } catch (Throwable ex) { 1438 Log.e(TAG, "Failed to add autoIncrement to threads;: " + ex.getMessage(), ex); 1439 autoIncrementThreadsSuccess = false; 1440 } finally { 1441 db.endTransaction(); 1442 } 1443 } 1444 if (!hasAutoIncrementAddresses) { 1445 db.beginTransaction(); 1446 try { 1447 if (false && sFakeLowStorageTest) { 1448 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 1449 " - fake exception"); 1450 throw new Exception("FakeLowStorageTest"); 1451 } 1452 upgradeAddressTableToAutoIncrement(db); // a no-op if already upgraded 1453 db.setTransactionSuccessful(); 1454 } catch (Throwable ex) { 1455 Log.e(TAG, "Failed to add autoIncrement to canonical_addresses: " + 1456 ex.getMessage(), ex); 1457 autoIncrementAddressesSuccess = false; 1458 } finally { 1459 db.endTransaction(); 1460 } 1461 } 1462 if (autoIncrementThreadsSuccess && autoIncrementAddressesSuccess) { 1463 if (mLowStorageMonitor != null) { 1464 // We've already updated the database. This receiver is no longer necessary. 1465 Log.d(TAG, "Unregistering mLowStorageMonitor - we've upgraded"); 1466 mContext.unregisterReceiver(mLowStorageMonitor); 1467 mLowStorageMonitor = null; 1468 } 1469 } else { 1470 if (sFakeLowStorageTest) { 1471 sFakeLowStorageTest = false; 1472 } 1473 1474 // We failed, perhaps because of low storage. Turn on a receiver to watch for 1475 // storage space. 1476 if (mLowStorageMonitor == null) { 1477 Log.d(TAG, "[getWritableDatabase] turning on storage monitor"); 1478 mLowStorageMonitor = new LowStorageMonitor(); 1479 IntentFilter intentFilter = new IntentFilter(); 1480 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); 1481 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 1482 mContext.registerReceiver(mLowStorageMonitor, intentFilter); 1483 } 1484 } 1485 } 1486 return db; 1487 } 1488 1489 // Determine whether a particular table has AUTOINCREMENT in its schema. 1490 private boolean hasAutoIncrement(SQLiteDatabase db, String tableName) { 1491 boolean result = false; 1492 String query = "SELECT sql FROM sqlite_master WHERE type='table' AND name='" + 1493 tableName + "'"; 1494 Cursor c = db.rawQuery(query, null); 1495 if (c != null) { 1496 try { 1497 if (c.moveToFirst()) { 1498 String schema = c.getString(0); 1499 result = schema != null ? schema.contains("AUTOINCREMENT") : false; 1500 Log.d(TAG, "[MmsSmsDb] tableName: " + tableName + " hasAutoIncrement: " + 1501 schema + " result: " + result); 1502 } 1503 } finally { 1504 c.close(); 1505 } 1506 } 1507 return result; 1508 } 1509 1510 // upgradeThreadsTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 1511 // the threads table. This could fail if the user has a lot of conversations and not enough 1512 // storage to make a copy of the threads table. That's ok. This upgrade is optional. It'll 1513 // be called again next time the device is rebooted. 1514 private void upgradeThreadsTableToAutoIncrement(SQLiteDatabase db) { 1515 if (hasAutoIncrement(db, "threads")) { 1516 Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: already upgraded"); 1517 return; 1518 } 1519 Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: upgrading"); 1520 1521 // Make the _id of the threads table autoincrement so we never re-use thread ids 1522 // Have to create a new temp threads table. Copy all the info from the old table. 1523 // Drop the old table and rename the new table to that of the old. 1524 db.execSQL("CREATE TABLE threads_temp (" + 1525 Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1526 Threads.DATE + " INTEGER DEFAULT 0," + 1527 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," + 1528 Threads.RECIPIENT_IDS + " TEXT," + 1529 Threads.SNIPPET + " TEXT," + 1530 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," + 1531 Threads.READ + " INTEGER DEFAULT 1," + 1532 Threads.TYPE + " INTEGER DEFAULT 0," + 1533 Threads.ERROR + " INTEGER DEFAULT 0," + 1534 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);"); 1535 1536 db.execSQL("INSERT INTO threads_temp SELECT * from threads;"); 1537 db.execSQL("DROP TABLE threads;"); 1538 db.execSQL("ALTER TABLE threads_temp RENAME TO threads;"); 1539 } 1540 1541 // upgradeAddressTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 1542 // the canonical_addresses table. This could fail if the user has a lot of people they've 1543 // messaged with and not enough storage to make a copy of the canonical_addresses table. 1544 // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted. 1545 private void upgradeAddressTableToAutoIncrement(SQLiteDatabase db) { 1546 if (hasAutoIncrement(db, "canonical_addresses")) { 1547 Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: already upgraded"); 1548 return; 1549 } 1550 Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: upgrading"); 1551 1552 // Make the _id of the canonical_addresses table autoincrement so we never re-use ids 1553 // Have to create a new temp canonical_addresses table. Copy all the info from the old 1554 // table. Drop the old table and rename the new table to that of the old. 1555 db.execSQL("CREATE TABLE canonical_addresses_temp (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 1556 "address TEXT);"); 1557 1558 db.execSQL("INSERT INTO canonical_addresses_temp SELECT * from canonical_addresses;"); 1559 db.execSQL("DROP TABLE canonical_addresses;"); 1560 db.execSQL("ALTER TABLE canonical_addresses_temp RENAME TO canonical_addresses;"); 1561 } 1562 1563 private class LowStorageMonitor extends BroadcastReceiver { 1564 1565 public LowStorageMonitor() { 1566 } 1567 1568 public void onReceive(Context context, Intent intent) { 1569 String action = intent.getAction(); 1570 1571 Log.d(TAG, "[LowStorageMonitor] onReceive intent " + action); 1572 1573 if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 1574 sTriedAutoIncrement = false; // try to upgrade on the next getWriteableDatabase 1575 } 1576 } 1577 } 1578 1579 private void updateThreadsAttachmentColumn(SQLiteDatabase db) { 1580 // Set the values of that column correctly based on the current 1581 // contents of the database. 1582 db.execSQL("UPDATE threads SET has_attachment=1 WHERE _id IN " + 1583 " (SELECT DISTINCT pdu.thread_id FROM part " + 1584 " JOIN pdu ON pdu._id=part.mid " + 1585 " WHERE part.ct != 'text/plain' AND part.ct != 'application/smil')"); 1586 } 1587} 1588