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 = 57; 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(MmsSmsProvider.TABLE_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 db.beginTransaction(); 292 try { 293 // Delete the row for this thread in the threads table if 294 // there are no more messages attached to it in either 295 // the sms or pdu tables. 296 int rows = db.delete(MmsSmsProvider.TABLE_THREADS, 297 "_id = ? AND _id NOT IN" + 298 " (SELECT thread_id FROM sms " + 299 " UNION SELECT thread_id FROM pdu)", 300 new String[] { String.valueOf(thread_id) }); 301 if (rows > 0) { 302 // If this deleted a row, let's remove orphaned canonical_addresses and get outta here 303 removeUnferencedCanonicalAddresses(db); 304 } else { 305 // Update the message count in the threads table as the sum 306 // of all messages in both the sms and pdu tables. 307 db.execSQL( 308 " UPDATE threads SET message_count = " + 309 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 310 " ON threads._id = " + Sms.THREAD_ID + 311 " WHERE " + Sms.THREAD_ID + " = " + thread_id + 312 " AND sms." + Sms.TYPE + " != 3) + " + 313 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 314 " ON threads._id = " + Mms.THREAD_ID + 315 " WHERE " + Mms.THREAD_ID + " = " + thread_id + 316 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 317 " AND " + Mms.MESSAGE_BOX + " != 3) " + 318 " WHERE threads._id = " + thread_id + ";"); 319 320 // Update the date and the snippet (and its character set) in 321 // the threads table to be that of the most recent message in 322 // the thread. 323 db.execSQL( 324 " UPDATE threads" + 325 " SET" + 326 " date =" + 327 " (SELECT date FROM" + 328 " (SELECT date * 1000 AS date, thread_id FROM pdu" + 329 " UNION SELECT date, thread_id FROM sms)" + 330 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 331 " snippet =" + 332 " (SELECT snippet FROM" + 333 " (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" + 334 " UNION SELECT date, body AS snippet, thread_id FROM sms)" + 335 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 336 " snippet_cs =" + 337 " (SELECT snippet_cs FROM" + 338 " (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" + 339 " UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" + 340 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" + 341 " WHERE threads._id = " + thread_id + ";"); 342 343 // Update the error column of the thread to indicate if there 344 // are any messages in it that have failed to send. 345 // First check to see if there are any messages with errors in this thread. 346 String query = "SELECT thread_id FROM sms WHERE type=" + 347 Telephony.TextBasedSmsColumns.MESSAGE_TYPE_FAILED + 348 " AND thread_id = " + thread_id + 349 " LIMIT 1"; 350 int setError = 0; 351 Cursor c = db.rawQuery(query, null); 352 if (c != null) { 353 try { 354 setError = c.getCount(); // Because of the LIMIT 1, count will be 1 or 0. 355 } finally { 356 c.close(); 357 } 358 } 359 // What's the current state of the error flag in the threads table? 360 String errorQuery = "SELECT error FROM threads WHERE _id = " + thread_id; 361 c = db.rawQuery(errorQuery, null); 362 if (c != null) { 363 try { 364 if (c.moveToNext()) { 365 int curError = c.getInt(0); 366 if (curError != setError) { 367 // The current thread error column differs, update it. 368 db.execSQL("UPDATE threads SET error=" + setError + 369 " WHERE _id = " + thread_id); 370 } 371 } 372 } finally { 373 c.close(); 374 } 375 } 376 } 377 db.setTransactionSuccessful(); 378 } catch (Throwable ex) { 379 Log.e(TAG, ex.getMessage(), ex); 380 } finally { 381 db.endTransaction(); 382 } 383 } 384 385 public static void updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs) { 386 db.beginTransaction(); 387 try { 388 if (where == null) { 389 where = ""; 390 } else { 391 where = "WHERE (" + where + ")"; 392 } 393 String query = "SELECT _id FROM threads WHERE _id IN " + 394 "(SELECT DISTINCT thread_id FROM sms " + where + ")"; 395 Cursor c = db.rawQuery(query, whereArgs); 396 if (c != null) { 397 try { 398 while (c.moveToNext()) { 399 updateThread(db, c.getInt(0)); 400 } 401 } finally { 402 c.close(); 403 } 404 } 405 // TODO: there are several db operations in this function. Lets wrap them in a 406 // transaction to make it faster. 407 // remove orphaned threads 408 db.delete(MmsSmsProvider.TABLE_THREADS, 409 "_id NOT IN (SELECT DISTINCT thread_id FROM sms where thread_id NOT NULL " + 410 "UNION SELECT DISTINCT thread_id FROM pdu where thread_id NOT NULL)", null); 411 412 // remove orphaned canonical_addresses 413 removeUnferencedCanonicalAddresses(db); 414 415 db.setTransactionSuccessful(); 416 } catch (Throwable ex) { 417 Log.e(TAG, ex.getMessage(), ex); 418 } finally { 419 db.endTransaction(); 420 } 421 } 422 423 public static int deleteOneSms(SQLiteDatabase db, int message_id) { 424 int thread_id = -1; 425 // Find the thread ID that the specified SMS belongs to. 426 Cursor c = db.query("sms", new String[] { "thread_id" }, 427 "_id=" + message_id, null, null, null, null); 428 if (c != null) { 429 if (c.moveToFirst()) { 430 thread_id = c.getInt(0); 431 } 432 c.close(); 433 } 434 435 // Delete the specified message. 436 int rows = db.delete("sms", "_id=" + message_id, null); 437 if (thread_id > 0) { 438 // Update its thread. 439 updateThread(db, thread_id); 440 } 441 return rows; 442 } 443 444 @Override 445 public void onCreate(SQLiteDatabase db) { 446 createMmsTables(db); 447 createSmsTables(db); 448 createCommonTables(db); 449 createCommonTriggers(db); 450 createMmsTriggers(db); 451 createWordsTables(db); 452 createIndices(db); 453 } 454 455 // When upgrading the database we need to populate the words 456 // table with the rows out of sms and part. 457 private void populateWordsTable(SQLiteDatabase db) { 458 final String TABLE_WORDS = "words"; 459 { 460 Cursor smsRows = db.query( 461 "sms", 462 new String[] { Sms._ID, Sms.BODY }, 463 null, 464 null, 465 null, 466 null, 467 null); 468 try { 469 if (smsRows != null) { 470 smsRows.moveToPosition(-1); 471 ContentValues cv = new ContentValues(); 472 while (smsRows.moveToNext()) { 473 cv.clear(); 474 475 long id = smsRows.getLong(0); // 0 for Sms._ID 476 String body = smsRows.getString(1); // 1 for Sms.BODY 477 478 cv.put(Telephony.MmsSms.WordsTable.ID, id); 479 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body); 480 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id); 481 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); 482 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 483 } 484 } 485 } finally { 486 if (smsRows != null) { 487 smsRows.close(); 488 } 489 } 490 } 491 492 { 493 Cursor mmsRows = db.query( 494 "part", 495 new String[] { Part._ID, Part.TEXT }, 496 "ct = 'text/plain'", 497 null, 498 null, 499 null, 500 null); 501 try { 502 if (mmsRows != null) { 503 mmsRows.moveToPosition(-1); 504 ContentValues cv = new ContentValues(); 505 while (mmsRows.moveToNext()) { 506 cv.clear(); 507 508 long id = mmsRows.getLong(0); // 0 for Part._ID 509 String body = mmsRows.getString(1); // 1 for Part.TEXT 510 511 cv.put(Telephony.MmsSms.WordsTable.ID, id); 512 cv.put(Telephony.MmsSms.WordsTable.INDEXED_TEXT, body); 513 cv.put(Telephony.MmsSms.WordsTable.SOURCE_ROW_ID, id); 514 cv.put(Telephony.MmsSms.WordsTable.TABLE_ID, 1); 515 db.insert(TABLE_WORDS, Telephony.MmsSms.WordsTable.INDEXED_TEXT, cv); 516 } 517 } 518 } finally { 519 if (mmsRows != null) { 520 mmsRows.close(); 521 } 522 } 523 } 524 } 525 526 private void createWordsTables(SQLiteDatabase db) { 527 try { 528 db.execSQL("CREATE VIRTUAL TABLE words USING FTS3 (_id INTEGER PRIMARY KEY, index_text TEXT, source_id INTEGER, table_to_use INTEGER);"); 529 530 // monitor the sms table 531 // NOTE don't handle inserts using a trigger because it has an unwanted 532 // side effect: the value returned for the last row ends up being the 533 // id of one of the trigger insert not the original row insert. 534 // Handle inserts manually in the provider. 535 db.execSQL("CREATE TRIGGER sms_words_update AFTER UPDATE ON sms BEGIN UPDATE words " + 536 " SET index_text = NEW.body WHERE (source_id=NEW._id AND table_to_use=1); " + 537 " END;"); 538 db.execSQL("CREATE TRIGGER sms_words_delete AFTER DELETE ON sms BEGIN DELETE FROM " + 539 " words WHERE source_id = OLD._id AND table_to_use = 1; END;"); 540 541 populateWordsTable(db); 542 } catch (Exception ex) { 543 Log.e(TAG, "got exception creating words table: " + ex.toString()); 544 } 545 } 546 547 private void createIndices(SQLiteDatabase db) { 548 createThreadIdIndex(db); 549 } 550 551 private void createThreadIdIndex(SQLiteDatabase db) { 552 try { 553 db.execSQL("CREATE INDEX IF NOT EXISTS typeThreadIdIndex ON sms" + 554 " (type, thread_id);"); 555 } catch (Exception ex) { 556 Log.e(TAG, "got exception creating indices: " + ex.toString()); 557 } 558 } 559 560 private void createMmsTables(SQLiteDatabase db) { 561 // N.B.: Whenever the columns here are changed, the columns in 562 // {@ref MmsSmsProvider} must be changed to match. 563 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" + 564 Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 565 Mms.THREAD_ID + " INTEGER," + 566 Mms.DATE + " INTEGER," + 567 Mms.DATE_SENT + " INTEGER DEFAULT 0," + 568 Mms.MESSAGE_BOX + " INTEGER," + 569 Mms.READ + " INTEGER DEFAULT 0," + 570 Mms.MESSAGE_ID + " TEXT," + 571 Mms.SUBJECT + " TEXT," + 572 Mms.SUBJECT_CHARSET + " INTEGER," + 573 Mms.CONTENT_TYPE + " TEXT," + 574 Mms.CONTENT_LOCATION + " TEXT," + 575 Mms.EXPIRY + " INTEGER," + 576 Mms.MESSAGE_CLASS + " TEXT," + 577 Mms.MESSAGE_TYPE + " INTEGER," + 578 Mms.MMS_VERSION + " INTEGER," + 579 Mms.MESSAGE_SIZE + " INTEGER," + 580 Mms.PRIORITY + " INTEGER," + 581 Mms.READ_REPORT + " INTEGER," + 582 Mms.REPORT_ALLOWED + " INTEGER," + 583 Mms.RESPONSE_STATUS + " INTEGER," + 584 Mms.STATUS + " INTEGER," + 585 Mms.TRANSACTION_ID + " TEXT," + 586 Mms.RETRIEVE_STATUS + " INTEGER," + 587 Mms.RETRIEVE_TEXT + " TEXT," + 588 Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," + 589 Mms.READ_STATUS + " INTEGER," + 590 Mms.CONTENT_CLASS + " INTEGER," + 591 Mms.RESPONSE_TEXT + " TEXT," + 592 Mms.DELIVERY_TIME + " INTEGER," + 593 Mms.DELIVERY_REPORT + " INTEGER," + 594 Mms.LOCKED + " INTEGER DEFAULT 0," + 595 Mms.SEEN + " INTEGER DEFAULT 0," + 596 Mms.TEXT_ONLY + " INTEGER DEFAULT 0" + 597 ");"); 598 599 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" + 600 Addr._ID + " INTEGER PRIMARY KEY," + 601 Addr.MSG_ID + " INTEGER," + 602 Addr.CONTACT_ID + " INTEGER," + 603 Addr.ADDRESS + " TEXT," + 604 Addr.TYPE + " INTEGER," + 605 Addr.CHARSET + " INTEGER);"); 606 607 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PART + " (" + 608 Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 609 Part.MSG_ID + " INTEGER," + 610 Part.SEQ + " INTEGER DEFAULT 0," + 611 Part.CONTENT_TYPE + " TEXT," + 612 Part.NAME + " TEXT," + 613 Part.CHARSET + " INTEGER," + 614 Part.CONTENT_DISPOSITION + " TEXT," + 615 Part.FILENAME + " TEXT," + 616 Part.CONTENT_ID + " TEXT," + 617 Part.CONTENT_LOCATION + " TEXT," + 618 Part.CT_START + " INTEGER," + 619 Part.CT_TYPE + " TEXT," + 620 Part._DATA + " TEXT," + 621 Part.TEXT + " TEXT);"); 622 623 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_RATE + " (" + 624 Rate.SENT_TIME + " INTEGER);"); 625 626 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_DRM + " (" + 627 BaseColumns._ID + " INTEGER PRIMARY KEY," + 628 "_data TEXT);"); 629 } 630 631 // Unlike the other trigger-creating functions, this function can be called multiple times 632 // without harm. 633 private void createMmsTriggers(SQLiteDatabase db) { 634 // Cleans up parts when a MM is deleted. 635 db.execSQL("DROP TRIGGER IF EXISTS part_cleanup"); 636 db.execSQL("CREATE TRIGGER part_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 637 "BEGIN " + 638 " DELETE FROM " + MmsProvider.TABLE_PART + 639 " WHERE " + Part.MSG_ID + "=old._id;" + 640 "END;"); 641 642 // Cleans up address info when a MM is deleted. 643 db.execSQL("DROP TRIGGER IF EXISTS addr_cleanup"); 644 db.execSQL("CREATE TRIGGER addr_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 645 "BEGIN " + 646 " DELETE FROM " + MmsProvider.TABLE_ADDR + 647 " WHERE " + Addr.MSG_ID + "=old._id;" + 648 "END;"); 649 650 // Delete obsolete delivery-report, read-report while deleting their 651 // associated Send.req. 652 db.execSQL("DROP TRIGGER IF EXISTS cleanup_delivery_and_read_report"); 653 db.execSQL("CREATE TRIGGER cleanup_delivery_and_read_report " + 654 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 655 "WHEN old." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ + " " + 656 "BEGIN " + 657 " DELETE FROM " + MmsProvider.TABLE_PDU + 658 " WHERE (" + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_DELIVERY_IND + 659 " OR " + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_ORIG_IND + 660 ")" + 661 " AND " + Mms.MESSAGE_ID + "=old." + Mms.MESSAGE_ID + "; " + 662 "END;"); 663 664 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_insert_part"); 665 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 666 667 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_part"); 668 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 669 670 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_delete_part"); 671 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 672 673 db.execSQL("DROP TRIGGER IF EXISTS update_threads_on_update_pdu"); 674 db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER); 675 676 // Delete pending status for a message when it is deleted. 677 db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_delete"); 678 db.execSQL("CREATE TRIGGER delete_mms_pending_on_delete " + 679 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 680 "BEGIN " + 681 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 682 " WHERE " + PendingMessages.MSG_ID + "=old._id; " + 683 "END;"); 684 685 // When a message is moved out of Outbox, delete its pending status. 686 db.execSQL("DROP TRIGGER IF EXISTS delete_mms_pending_on_update"); 687 db.execSQL("CREATE TRIGGER delete_mms_pending_on_update " + 688 "AFTER UPDATE ON " + MmsProvider.TABLE_PDU + " " + 689 "WHEN old." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 690 " AND new." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 691 "BEGIN " + 692 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 693 " WHERE " + PendingMessages.MSG_ID + "=new._id; " + 694 "END;"); 695 696 // Insert pending status for M-Notification.ind or M-ReadRec.ind 697 // when they are inserted into Inbox/Outbox. 698 db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_insert"); 699 db.execSQL("CREATE TRIGGER insert_mms_pending_on_insert " + 700 "AFTER INSERT ON pdu " + 701 "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND + 702 " OR new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_READ_REC_IND + 703 " " + 704 "BEGIN " + 705 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 706 " (" + PendingMessages.PROTO_TYPE + "," + 707 " " + PendingMessages.MSG_ID + "," + 708 " " + PendingMessages.MSG_TYPE + "," + 709 " " + PendingMessages.ERROR_TYPE + "," + 710 " " + PendingMessages.ERROR_CODE + "," + 711 " " + PendingMessages.RETRY_INDEX + "," + 712 " " + PendingMessages.DUE_TIME + ") " + 713 " VALUES " + 714 " (" + MmsSms.MMS_PROTO + "," + 715 " new." + BaseColumns._ID + "," + 716 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 717 "END;"); 718 719 720 // Insert pending status for M-Send.req when it is moved into Outbox. 721 db.execSQL("DROP TRIGGER IF EXISTS insert_mms_pending_on_update"); 722 db.execSQL("CREATE TRIGGER insert_mms_pending_on_update " + 723 "AFTER UPDATE ON pdu " + 724 "WHEN new." + Mms.MESSAGE_TYPE + "=" + PduHeaders.MESSAGE_TYPE_SEND_REQ + 725 " AND new." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 726 " AND old." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 727 "BEGIN " + 728 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 729 " (" + PendingMessages.PROTO_TYPE + "," + 730 " " + PendingMessages.MSG_ID + "," + 731 " " + PendingMessages.MSG_TYPE + "," + 732 " " + PendingMessages.ERROR_TYPE + "," + 733 " " + PendingMessages.ERROR_CODE + "," + 734 " " + PendingMessages.RETRY_INDEX + "," + 735 " " + PendingMessages.DUE_TIME + ") " + 736 " VALUES " + 737 " (" + MmsSms.MMS_PROTO + "," + 738 " new." + BaseColumns._ID + "," + 739 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 740 "END;"); 741 742 // monitor the mms table 743 db.execSQL("DROP TRIGGER IF EXISTS mms_words_update"); 744 db.execSQL("CREATE TRIGGER mms_words_update AFTER UPDATE ON part BEGIN UPDATE words " + 745 " SET index_text = NEW.text WHERE (source_id=NEW._id AND table_to_use=2); " + 746 " END;"); 747 748 db.execSQL("DROP TRIGGER IF EXISTS mms_words_delete"); 749 db.execSQL("CREATE TRIGGER mms_words_delete AFTER DELETE ON part BEGIN DELETE FROM " + 750 " words WHERE source_id = OLD._id AND table_to_use = 2; END;"); 751 752 // Updates threads table whenever a message in pdu is updated. 753 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_date_subject_on_update"); 754 db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" + 755 " UPDATE OF " + Mms.DATE + ", " + Mms.SUBJECT + ", " + Mms.MESSAGE_BOX + 756 " ON " + MmsProvider.TABLE_PDU + " " + 757 PDU_UPDATE_THREAD_CONSTRAINTS + 758 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 759 760 // Update threads table whenever a message in pdu is deleted 761 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_delete"); 762 db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " + 763 "AFTER DELETE ON pdu " + 764 "BEGIN " + 765 " UPDATE threads SET " + 766 " date = (strftime('%s','now') * 1000)" + 767 " WHERE threads._id = old." + Mms.THREAD_ID + "; " + 768 UPDATE_THREAD_COUNT_ON_OLD + 769 UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE + 770 "END;"); 771 772 // Updates threads table whenever a message is added to pdu. 773 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_on_insert"); 774 db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " + 775 MmsProvider.TABLE_PDU + " " + 776 PDU_UPDATE_THREAD_CONSTRAINTS + 777 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 778 779 // Updates threads table whenever a message in pdu is updated. 780 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update"); 781 db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" + 782 " UPDATE OF " + Mms.READ + 783 " ON " + MmsProvider.TABLE_PDU + " " + 784 PDU_UPDATE_THREAD_CONSTRAINTS + 785 "BEGIN " + 786 PDU_UPDATE_THREAD_READ_BODY + 787 "END;"); 788 789 // Update the error flag of threads when delete pending message. 790 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_mms"); 791 db.execSQL("CREATE TRIGGER update_threads_error_on_delete_mms " + 792 " BEFORE DELETE ON pdu" + 793 " WHEN OLD._id IN (SELECT DISTINCT msg_id" + 794 " FROM pending_msgs" + 795 " WHERE err_type >= 10) " + 796 "BEGIN " + 797 " UPDATE threads SET error = error - 1" + 798 " WHERE _id = OLD.thread_id; " + 799 "END;"); 800 801 // Update the error flag of threads while moving an MM out of Outbox, 802 // which was failed to be sent permanently. 803 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms"); 804 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 805 " BEFORE UPDATE OF msg_box ON pdu " + 806 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 807 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 808 " FROM pending_msgs" + 809 " WHERE err_type >= 10)) " + 810 "BEGIN " + 811 " UPDATE threads SET error = error - 1" + 812 " WHERE _id = OLD.thread_id; " + 813 "END;"); 814 } 815 816 private void createSmsTables(SQLiteDatabase db) { 817 // N.B.: Whenever the columns here are changed, the columns in 818 // {@ref MmsSmsProvider} must be changed to match. 819 db.execSQL("CREATE TABLE sms (" + 820 "_id INTEGER PRIMARY KEY," + 821 "thread_id INTEGER," + 822 "address TEXT," + 823 "person INTEGER," + 824 "date INTEGER," + 825 "date_sent INTEGER DEFAULT 0," + 826 "protocol INTEGER," + 827 "read INTEGER DEFAULT 0," + 828 "status INTEGER DEFAULT -1," + // a TP-Status value 829 // or -1 if it 830 // status hasn't 831 // been received 832 "type INTEGER," + 833 "reply_path_present INTEGER," + 834 "subject TEXT," + 835 "body TEXT," + 836 "service_center TEXT," + 837 "locked INTEGER DEFAULT 0," + 838 "error_code INTEGER DEFAULT 0," + 839 "seen INTEGER DEFAULT 0" + 840 ");"); 841 842 /** 843 * This table is used by the SMS dispatcher to hold 844 * incomplete partial messages until all the parts arrive. 845 */ 846 db.execSQL("CREATE TABLE raw (" + 847 "_id INTEGER PRIMARY KEY," + 848 "date INTEGER," + 849 "reference_number INTEGER," + // one per full message 850 "count INTEGER," + // the number of parts 851 "sequence INTEGER," + // the part number of this message 852 "destination_port INTEGER," + 853 "address TEXT," + 854 "pdu TEXT);"); // the raw PDU for this part 855 856 db.execSQL("CREATE TABLE attachments (" + 857 "sms_id INTEGER," + 858 "content_url TEXT," + 859 "offset INTEGER);"); 860 861 /** 862 * This table is used by the SMS dispatcher to hold pending 863 * delivery status report intents. 864 */ 865 db.execSQL("CREATE TABLE sr_pending (" + 866 "reference_number INTEGER," + 867 "action TEXT," + 868 "data TEXT);"); 869 } 870 871 private void createCommonTables(SQLiteDatabase db) { 872 // TODO Ensure that each entry is removed when the last use of 873 // any address equivalent to its address is removed. 874 875 /** 876 * This table maps the first instance seen of any particular 877 * MMS/SMS address to an ID, which is then used as its 878 * canonical representation. If the same address or an 879 * equivalent address (as determined by our Sqlite 880 * PHONE_NUMBERS_EQUAL extension) is seen later, this same ID 881 * will be used. The _id is created with AUTOINCREMENT so it 882 * will never be reused again if a recipient is deleted. 883 */ 884 db.execSQL("CREATE TABLE canonical_addresses (" + 885 "_id INTEGER PRIMARY KEY AUTOINCREMENT," + 886 "address TEXT);"); 887 888 /** 889 * This table maps the subject and an ordered set of recipient 890 * IDs, separated by spaces, to a unique thread ID. The IDs 891 * come from the canonical_addresses table. This works 892 * because messages are considered to be part of the same 893 * thread if they have the same subject (or a null subject) 894 * and the same set of recipients. 895 */ 896 db.execSQL("CREATE TABLE threads (" + 897 Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 898 Threads.DATE + " INTEGER DEFAULT 0," + 899 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," + 900 Threads.RECIPIENT_IDS + " TEXT," + 901 Threads.SNIPPET + " TEXT," + 902 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," + 903 Threads.READ + " INTEGER DEFAULT 1," + 904 Threads.TYPE + " INTEGER DEFAULT 0," + 905 Threads.ERROR + " INTEGER DEFAULT 0," + 906 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);"); 907 908 /** 909 * This table stores the queue of messages to be sent/downloaded. 910 */ 911 db.execSQL("CREATE TABLE " + MmsSmsProvider.TABLE_PENDING_MSG +" (" + 912 PendingMessages._ID + " INTEGER PRIMARY KEY," + 913 PendingMessages.PROTO_TYPE + " INTEGER," + 914 PendingMessages.MSG_ID + " INTEGER," + 915 PendingMessages.MSG_TYPE + " INTEGER," + 916 PendingMessages.ERROR_TYPE + " INTEGER," + 917 PendingMessages.ERROR_CODE + " INTEGER," + 918 PendingMessages.RETRY_INDEX + " INTEGER NOT NULL DEFAULT 0," + 919 PendingMessages.DUE_TIME + " INTEGER," + 920 PendingMessages.LAST_TRY + " INTEGER);"); 921 922 } 923 924 // TODO Check the query plans for these triggers. 925 private void createCommonTriggers(SQLiteDatabase db) { 926 // Updates threads table whenever a message is added to sms. 927 db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " + 928 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 929 930 // Updates threads table whenever a message in sms is updated. 931 db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" + 932 " UPDATE OF " + Sms.DATE + ", " + Sms.BODY + ", " + Sms.TYPE + 933 " ON sms " + 934 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 935 936 // Updates threads table whenever a message in sms is updated. 937 db.execSQL("CREATE TRIGGER sms_update_thread_read_on_update AFTER" + 938 " UPDATE OF " + Sms.READ + 939 " ON sms " + 940 "BEGIN " + 941 SMS_UPDATE_THREAD_READ_BODY + 942 "END;"); 943 944 // As of DATABASE_VERSION 55, we've removed these triggers that delete empty threads. 945 // These triggers interfere with saving drafts on brand new threads. Instead of 946 // triggers cleaning up empty threads, the empty threads should be cleaned up by 947 // an explicit call to delete with Threads.OBSOLETE_THREADS_URI. 948 949// // When the last message in a thread is deleted, these 950// // triggers ensure that the entry for its thread ID is removed 951// // from the threads table. 952// db.execSQL("CREATE TRIGGER delete_obsolete_threads_pdu " + 953// "AFTER DELETE ON pdu " + 954// "BEGIN " + 955// " DELETE FROM threads " + 956// " WHERE " + 957// " _id = old.thread_id " + 958// " AND _id NOT IN " + 959// " (SELECT thread_id FROM sms " + 960// " UNION SELECT thread_id from pdu); " + 961// "END;"); 962// 963// db.execSQL("CREATE TRIGGER delete_obsolete_threads_when_update_pdu " + 964// "AFTER UPDATE OF " + Mms.THREAD_ID + " ON pdu " + 965// "WHEN old." + Mms.THREAD_ID + " != new." + Mms.THREAD_ID + " " + 966// "BEGIN " + 967// " DELETE FROM threads " + 968// " WHERE " + 969// " _id = old.thread_id " + 970// " AND _id NOT IN " + 971// " (SELECT thread_id FROM sms " + 972// " UNION SELECT thread_id from pdu); " + 973// "END;"); 974 975 // TODO Add triggers for SMS retry-status management. 976 977 // Update the error flag of threads when the error type of 978 // a pending MM is updated. 979 db.execSQL("CREATE TRIGGER update_threads_error_on_update_mms " + 980 " AFTER UPDATE OF err_type ON pending_msgs " + 981 " WHEN (OLD.err_type < 10 AND NEW.err_type >= 10)" + 982 " OR (OLD.err_type >= 10 AND NEW.err_type < 10) " + 983 "BEGIN" + 984 " UPDATE threads SET error = " + 985 " CASE" + 986 " WHEN NEW.err_type >= 10 THEN error + 1" + 987 " ELSE error - 1" + 988 " END " + 989 " WHERE _id =" + 990 " (SELECT DISTINCT thread_id" + 991 " FROM pdu" + 992 " WHERE _id = NEW.msg_id); " + 993 "END;"); 994 995 // Update the error flag of threads after a text message was 996 // failed to send/receive. 997 db.execSQL("CREATE TRIGGER update_threads_error_on_update_sms " + 998 " AFTER UPDATE OF type ON sms" + 999 " WHEN (OLD.type != 5 AND NEW.type = 5)" + 1000 " OR (OLD.type = 5 AND NEW.type != 5) " + 1001 "BEGIN " + 1002 " UPDATE threads SET error = " + 1003 " CASE" + 1004 " WHEN NEW.type = 5 THEN error + 1" + 1005 " ELSE error - 1" + 1006 " END " + 1007 " WHERE _id = NEW.thread_id; " + 1008 "END;"); 1009 } 1010 1011 @Override 1012 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 1013 Log.w(TAG, "Upgrading database from version " + oldVersion 1014 + " to " + currentVersion + "."); 1015 1016 switch (oldVersion) { 1017 case 40: 1018 if (currentVersion <= 40) { 1019 return; 1020 } 1021 1022 db.beginTransaction(); 1023 try { 1024 upgradeDatabaseToVersion41(db); 1025 db.setTransactionSuccessful(); 1026 } catch (Throwable ex) { 1027 Log.e(TAG, ex.getMessage(), ex); 1028 break; 1029 } finally { 1030 db.endTransaction(); 1031 } 1032 // fall through 1033 case 41: 1034 if (currentVersion <= 41) { 1035 return; 1036 } 1037 1038 db.beginTransaction(); 1039 try { 1040 upgradeDatabaseToVersion42(db); 1041 db.setTransactionSuccessful(); 1042 } catch (Throwable ex) { 1043 Log.e(TAG, ex.getMessage(), ex); 1044 break; 1045 } finally { 1046 db.endTransaction(); 1047 } 1048 // fall through 1049 case 42: 1050 if (currentVersion <= 42) { 1051 return; 1052 } 1053 1054 db.beginTransaction(); 1055 try { 1056 upgradeDatabaseToVersion43(db); 1057 db.setTransactionSuccessful(); 1058 } catch (Throwable ex) { 1059 Log.e(TAG, ex.getMessage(), ex); 1060 break; 1061 } finally { 1062 db.endTransaction(); 1063 } 1064 // fall through 1065 case 43: 1066 if (currentVersion <= 43) { 1067 return; 1068 } 1069 1070 db.beginTransaction(); 1071 try { 1072 upgradeDatabaseToVersion44(db); 1073 db.setTransactionSuccessful(); 1074 } catch (Throwable ex) { 1075 Log.e(TAG, ex.getMessage(), ex); 1076 break; 1077 } finally { 1078 db.endTransaction(); 1079 } 1080 // fall through 1081 case 44: 1082 if (currentVersion <= 44) { 1083 return; 1084 } 1085 1086 db.beginTransaction(); 1087 try { 1088 upgradeDatabaseToVersion45(db); 1089 db.setTransactionSuccessful(); 1090 } catch (Throwable ex) { 1091 Log.e(TAG, ex.getMessage(), ex); 1092 break; 1093 } finally { 1094 db.endTransaction(); 1095 } 1096 // fall through 1097 case 45: 1098 if (currentVersion <= 45) { 1099 return; 1100 } 1101 db.beginTransaction(); 1102 try { 1103 upgradeDatabaseToVersion46(db); 1104 db.setTransactionSuccessful(); 1105 } catch (Throwable ex) { 1106 Log.e(TAG, ex.getMessage(), ex); 1107 break; 1108 } finally { 1109 db.endTransaction(); 1110 } 1111 // fall through 1112 case 46: 1113 if (currentVersion <= 46) { 1114 return; 1115 } 1116 1117 db.beginTransaction(); 1118 try { 1119 upgradeDatabaseToVersion47(db); 1120 db.setTransactionSuccessful(); 1121 } catch (Throwable ex) { 1122 Log.e(TAG, ex.getMessage(), ex); 1123 break; 1124 } finally { 1125 db.endTransaction(); 1126 } 1127 // fall through 1128 case 47: 1129 if (currentVersion <= 47) { 1130 return; 1131 } 1132 1133 db.beginTransaction(); 1134 try { 1135 upgradeDatabaseToVersion48(db); 1136 db.setTransactionSuccessful(); 1137 } catch (Throwable ex) { 1138 Log.e(TAG, ex.getMessage(), ex); 1139 break; 1140 } finally { 1141 db.endTransaction(); 1142 } 1143 // fall through 1144 case 48: 1145 if (currentVersion <= 48) { 1146 return; 1147 } 1148 1149 db.beginTransaction(); 1150 try { 1151 createWordsTables(db); 1152 db.setTransactionSuccessful(); 1153 } catch (Throwable ex) { 1154 Log.e(TAG, ex.getMessage(), ex); 1155 break; 1156 } finally { 1157 db.endTransaction(); 1158 } 1159 // fall through 1160 case 49: 1161 if (currentVersion <= 49) { 1162 return; 1163 } 1164 db.beginTransaction(); 1165 try { 1166 createThreadIdIndex(db); 1167 db.setTransactionSuccessful(); 1168 } catch (Throwable ex) { 1169 Log.e(TAG, ex.getMessage(), ex); 1170 break; // force to destroy all old data; 1171 } finally { 1172 db.endTransaction(); 1173 } 1174 // fall through 1175 case 50: 1176 if (currentVersion <= 50) { 1177 return; 1178 } 1179 1180 db.beginTransaction(); 1181 try { 1182 upgradeDatabaseToVersion51(db); 1183 db.setTransactionSuccessful(); 1184 } catch (Throwable ex) { 1185 Log.e(TAG, ex.getMessage(), ex); 1186 break; 1187 } finally { 1188 db.endTransaction(); 1189 } 1190 // fall through 1191 case 51: 1192 if (currentVersion <= 51) { 1193 return; 1194 } 1195 // 52 was adding a new meta_data column, but that was removed. 1196 // fall through 1197 case 52: 1198 if (currentVersion <= 52) { 1199 return; 1200 } 1201 1202 db.beginTransaction(); 1203 try { 1204 upgradeDatabaseToVersion53(db); 1205 db.setTransactionSuccessful(); 1206 } catch (Throwable ex) { 1207 Log.e(TAG, ex.getMessage(), ex); 1208 break; 1209 } finally { 1210 db.endTransaction(); 1211 } 1212 // fall through 1213 case 53: 1214 if (currentVersion <= 53) { 1215 return; 1216 } 1217 1218 db.beginTransaction(); 1219 try { 1220 upgradeDatabaseToVersion54(db); 1221 db.setTransactionSuccessful(); 1222 } catch (Throwable ex) { 1223 Log.e(TAG, ex.getMessage(), ex); 1224 break; 1225 } finally { 1226 db.endTransaction(); 1227 } 1228 // fall through 1229 case 54: 1230 if (currentVersion <= 54) { 1231 return; 1232 } 1233 1234 db.beginTransaction(); 1235 try { 1236 upgradeDatabaseToVersion55(db); 1237 db.setTransactionSuccessful(); 1238 } catch (Throwable ex) { 1239 Log.e(TAG, ex.getMessage(), ex); 1240 break; 1241 } finally { 1242 db.endTransaction(); 1243 } 1244 // fall through 1245 case 55: 1246 if (currentVersion <= 55) { 1247 return; 1248 } 1249 1250 db.beginTransaction(); 1251 try { 1252 upgradeDatabaseToVersion56(db); 1253 db.setTransactionSuccessful(); 1254 } catch (Throwable ex) { 1255 Log.e(TAG, ex.getMessage(), ex); 1256 break; 1257 } finally { 1258 db.endTransaction(); 1259 } 1260 // fall through 1261 case 56: 1262 if (currentVersion <= 56) { 1263 return; 1264 } 1265 1266 db.beginTransaction(); 1267 try { 1268 upgradeDatabaseToVersion57(db); 1269 db.setTransactionSuccessful(); 1270 } catch (Throwable ex) { 1271 Log.e(TAG, ex.getMessage(), ex); 1272 break; 1273 } finally { 1274 db.endTransaction(); 1275 } 1276 return; 1277 } 1278 1279 Log.e(TAG, "Destroying all old data."); 1280 dropAll(db); 1281 onCreate(db); 1282 } 1283 1284 private void dropAll(SQLiteDatabase db) { 1285 // Clean the database out in order to start over from scratch. 1286 // We don't need to drop our triggers here because SQLite automatically 1287 // drops a trigger when its attached database is dropped. 1288 db.execSQL("DROP TABLE IF EXISTS canonical_addresses"); 1289 db.execSQL("DROP TABLE IF EXISTS threads"); 1290 db.execSQL("DROP TABLE IF EXISTS " + MmsSmsProvider.TABLE_PENDING_MSG); 1291 db.execSQL("DROP TABLE IF EXISTS sms"); 1292 db.execSQL("DROP TABLE IF EXISTS raw"); 1293 db.execSQL("DROP TABLE IF EXISTS attachments"); 1294 db.execSQL("DROP TABLE IF EXISTS thread_ids"); 1295 db.execSQL("DROP TABLE IF EXISTS sr_pending"); 1296 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PDU + ";"); 1297 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_ADDR + ";"); 1298 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PART + ";"); 1299 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_RATE + ";"); 1300 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_DRM + ";"); 1301 } 1302 1303 private void upgradeDatabaseToVersion41(SQLiteDatabase db) { 1304 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms"); 1305 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 1306 " BEFORE UPDATE OF msg_box ON pdu " + 1307 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 1308 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 1309 " FROM pending_msgs" + 1310 " WHERE err_type >= 10)) " + 1311 "BEGIN " + 1312 " UPDATE threads SET error = error - 1" + 1313 " WHERE _id = OLD.thread_id; " + 1314 "END;"); 1315 } 1316 1317 private void upgradeDatabaseToVersion42(SQLiteDatabase db) { 1318 db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete"); 1319 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_sms"); 1320 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_sms"); 1321 } 1322 1323 private void upgradeDatabaseToVersion43(SQLiteDatabase db) { 1324 // Add 'has_attachment' column to threads table. 1325 db.execSQL("ALTER TABLE threads ADD COLUMN has_attachment INTEGER DEFAULT 0"); 1326 1327 updateThreadsAttachmentColumn(db); 1328 1329 // Add insert and delete triggers for keeping it up to date. 1330 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 1331 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 1332 } 1333 1334 private void upgradeDatabaseToVersion44(SQLiteDatabase db) { 1335 updateThreadsAttachmentColumn(db); 1336 1337 // add the update trigger for keeping the threads up to date. 1338 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 1339 } 1340 1341 private void upgradeDatabaseToVersion45(SQLiteDatabase db) { 1342 // Add 'locked' column to sms table. 1343 db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.LOCKED + " INTEGER DEFAULT 0"); 1344 1345 // Add 'locked' column to pdu table. 1346 db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.LOCKED + " INTEGER DEFAULT 0"); 1347 } 1348 1349 private void upgradeDatabaseToVersion46(SQLiteDatabase db) { 1350 // add the "text" column for caching inline text (e.g. strings) instead of 1351 // putting them in an external file 1352 db.execSQL("ALTER TABLE part ADD COLUMN " + Part.TEXT + " TEXT"); 1353 1354 Cursor textRows = db.query( 1355 "part", 1356 new String[] { Part._ID, Part._DATA, Part.TEXT}, 1357 "ct = 'text/plain' OR ct == 'application/smil'", 1358 null, 1359 null, 1360 null, 1361 null); 1362 ArrayList<String> filesToDelete = new ArrayList<String>(); 1363 try { 1364 db.beginTransaction(); 1365 if (textRows != null) { 1366 int partDataColumn = textRows.getColumnIndex(Part._DATA); 1367 1368 // This code is imperfect in that we can't guarantee that all the 1369 // backing files get deleted. For example if the system aborts after 1370 // the database is updated but before we complete the process of 1371 // deleting files. 1372 while (textRows.moveToNext()) { 1373 String path = textRows.getString(partDataColumn); 1374 if (path != null) { 1375 try { 1376 InputStream is = new FileInputStream(path); 1377 byte [] data = new byte[is.available()]; 1378 is.read(data); 1379 EncodedStringValue v = new EncodedStringValue(data); 1380 db.execSQL("UPDATE part SET " + Part._DATA + " = NULL, " + 1381 Part.TEXT + " = ?", new String[] { v.getString() }); 1382 is.close(); 1383 filesToDelete.add(path); 1384 } catch (IOException e) { 1385 // TODO Auto-generated catch block 1386 e.printStackTrace(); 1387 } 1388 } 1389 } 1390 } 1391 db.setTransactionSuccessful(); 1392 } finally { 1393 db.endTransaction(); 1394 for (String pathToDelete : filesToDelete) { 1395 try { 1396 (new File(pathToDelete)).delete(); 1397 } catch (SecurityException ex) { 1398 Log.e(TAG, "unable to clean up old mms file for " + pathToDelete, ex); 1399 } 1400 } 1401 if (textRows != null) { 1402 textRows.close(); 1403 } 1404 } 1405 } 1406 1407 private void upgradeDatabaseToVersion47(SQLiteDatabase db) { 1408 updateThreadsAttachmentColumn(db); 1409 1410 // add the update trigger for keeping the threads up to date. 1411 db.execSQL(PDU_UPDATE_THREADS_ON_UPDATE_TRIGGER); 1412 } 1413 1414 private void upgradeDatabaseToVersion48(SQLiteDatabase db) { 1415 // Add 'error_code' column to sms table. 1416 db.execSQL("ALTER TABLE sms ADD COLUMN error_code INTEGER DEFAULT 0"); 1417 } 1418 1419 private void upgradeDatabaseToVersion51(SQLiteDatabase db) { 1420 db.execSQL("ALTER TABLE sms add COLUMN seen INTEGER DEFAULT 0"); 1421 db.execSQL("ALTER TABLE pdu add COLUMN seen INTEGER DEFAULT 0"); 1422 1423 try { 1424 // update the existing sms and pdu tables so the new "seen" column is the same as 1425 // the "read" column for each row. 1426 ContentValues contentValues = new ContentValues(); 1427 contentValues.put("seen", 1); 1428 int count = db.update("sms", contentValues, "read=1", null); 1429 Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count + 1430 " rows in sms table to have READ=1"); 1431 count = db.update("pdu", contentValues, "read=1", null); 1432 Log.d(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51: updated " + count + 1433 " rows in pdu table to have READ=1"); 1434 } catch (Exception ex) { 1435 Log.e(TAG, "[MmsSmsDb] upgradeDatabaseToVersion51 caught ", ex); 1436 } 1437 } 1438 1439 private void upgradeDatabaseToVersion53(SQLiteDatabase db) { 1440 db.execSQL("DROP TRIGGER IF EXISTS pdu_update_thread_read_on_update"); 1441 1442 // Updates threads table whenever a message in pdu is updated. 1443 db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" + 1444 " UPDATE OF " + Mms.READ + 1445 " ON " + MmsProvider.TABLE_PDU + " " + 1446 PDU_UPDATE_THREAD_CONSTRAINTS + 1447 "BEGIN " + 1448 PDU_UPDATE_THREAD_READ_BODY + 1449 "END;"); 1450 } 1451 1452 private void upgradeDatabaseToVersion54(SQLiteDatabase db) { 1453 // Add 'date_sent' column to sms table. 1454 db.execSQL("ALTER TABLE sms ADD COLUMN " + Sms.DATE_SENT + " INTEGER DEFAULT 0"); 1455 1456 // Add 'date_sent' column to pdu table. 1457 db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.DATE_SENT + " INTEGER DEFAULT 0"); 1458 } 1459 1460 private void upgradeDatabaseToVersion55(SQLiteDatabase db) { 1461 // Drop removed triggers 1462 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_pdu"); 1463 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_when_update_pdu"); 1464 } 1465 1466 private void upgradeDatabaseToVersion56(SQLiteDatabase db) { 1467 // Add 'text_only' column to pdu table. 1468 db.execSQL("ALTER TABLE " + MmsProvider.TABLE_PDU + " ADD COLUMN " + Mms.TEXT_ONLY + 1469 " INTEGER DEFAULT 0"); 1470 } 1471 1472 private void upgradeDatabaseToVersion57(SQLiteDatabase db) { 1473 // Clear out bad rows, those with empty threadIds, from the pdu table. 1474 db.execSQL("DELETE FROM " + MmsProvider.TABLE_PDU + " WHERE " + Mms.THREAD_ID + " IS NULL"); 1475 } 1476 1477 @Override 1478 public synchronized SQLiteDatabase getWritableDatabase() { 1479 SQLiteDatabase db = super.getWritableDatabase(); 1480 1481 if (!sTriedAutoIncrement) { 1482 sTriedAutoIncrement = true; 1483 boolean hasAutoIncrementThreads = hasAutoIncrement(db, MmsSmsProvider.TABLE_THREADS); 1484 boolean hasAutoIncrementAddresses = hasAutoIncrement(db, "canonical_addresses"); 1485 boolean hasAutoIncrementPart = hasAutoIncrement(db, "part"); 1486 boolean hasAutoIncrementPdu = hasAutoIncrement(db, "pdu"); 1487 Log.d(TAG, "[getWritableDatabase] hasAutoIncrementThreads: " + hasAutoIncrementThreads + 1488 " hasAutoIncrementAddresses: " + hasAutoIncrementAddresses + 1489 " hasAutoIncrementPart: " + hasAutoIncrementPart + 1490 " hasAutoIncrementPdu: " + hasAutoIncrementPdu); 1491 boolean autoIncrementThreadsSuccess = true; 1492 boolean autoIncrementAddressesSuccess = true; 1493 boolean autoIncrementPartSuccess = true; 1494 boolean autoIncrementPduSuccess = true; 1495 if (!hasAutoIncrementThreads) { 1496 db.beginTransaction(); 1497 try { 1498 if (false && sFakeLowStorageTest) { 1499 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 1500 " - fake exception"); 1501 throw new Exception("FakeLowStorageTest"); 1502 } 1503 upgradeThreadsTableToAutoIncrement(db); // a no-op if already upgraded 1504 db.setTransactionSuccessful(); 1505 } catch (Throwable ex) { 1506 Log.e(TAG, "Failed to add autoIncrement to threads;: " + ex.getMessage(), ex); 1507 autoIncrementThreadsSuccess = false; 1508 } finally { 1509 db.endTransaction(); 1510 } 1511 } 1512 if (!hasAutoIncrementAddresses) { 1513 db.beginTransaction(); 1514 try { 1515 if (false && sFakeLowStorageTest) { 1516 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 1517 " - fake exception"); 1518 throw new Exception("FakeLowStorageTest"); 1519 } 1520 upgradeAddressTableToAutoIncrement(db); // a no-op if already upgraded 1521 db.setTransactionSuccessful(); 1522 } catch (Throwable ex) { 1523 Log.e(TAG, "Failed to add autoIncrement to canonical_addresses: " + 1524 ex.getMessage(), ex); 1525 autoIncrementAddressesSuccess = false; 1526 } finally { 1527 db.endTransaction(); 1528 } 1529 } 1530 if (!hasAutoIncrementPart) { 1531 db.beginTransaction(); 1532 try { 1533 if (false && sFakeLowStorageTest) { 1534 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 1535 " - fake exception"); 1536 throw new Exception("FakeLowStorageTest"); 1537 } 1538 upgradePartTableToAutoIncrement(db); // a no-op if already upgraded 1539 db.setTransactionSuccessful(); 1540 } catch (Throwable ex) { 1541 Log.e(TAG, "Failed to add autoIncrement to part: " + 1542 ex.getMessage(), ex); 1543 autoIncrementPartSuccess = false; 1544 } finally { 1545 db.endTransaction(); 1546 } 1547 } 1548 if (!hasAutoIncrementPdu) { 1549 db.beginTransaction(); 1550 try { 1551 if (false && sFakeLowStorageTest) { 1552 Log.d(TAG, "[getWritableDatabase] mFakeLowStorageTest is true " + 1553 " - fake exception"); 1554 throw new Exception("FakeLowStorageTest"); 1555 } 1556 upgradePduTableToAutoIncrement(db); // a no-op if already upgraded 1557 db.setTransactionSuccessful(); 1558 } catch (Throwable ex) { 1559 Log.e(TAG, "Failed to add autoIncrement to pdu: " + 1560 ex.getMessage(), ex); 1561 autoIncrementPduSuccess = false; 1562 } finally { 1563 db.endTransaction(); 1564 } 1565 } 1566 if (autoIncrementThreadsSuccess && 1567 autoIncrementAddressesSuccess && 1568 autoIncrementPartSuccess && 1569 autoIncrementPduSuccess) { 1570 if (mLowStorageMonitor != null) { 1571 // We've already updated the database. This receiver is no longer necessary. 1572 Log.d(TAG, "Unregistering mLowStorageMonitor - we've upgraded"); 1573 mContext.unregisterReceiver(mLowStorageMonitor); 1574 mLowStorageMonitor = null; 1575 } 1576 } else { 1577 if (sFakeLowStorageTest) { 1578 sFakeLowStorageTest = false; 1579 } 1580 1581 // We failed, perhaps because of low storage. Turn on a receiver to watch for 1582 // storage space. 1583 if (mLowStorageMonitor == null) { 1584 Log.d(TAG, "[getWritableDatabase] turning on storage monitor"); 1585 mLowStorageMonitor = new LowStorageMonitor(); 1586 IntentFilter intentFilter = new IntentFilter(); 1587 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW); 1588 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 1589 mContext.registerReceiver(mLowStorageMonitor, intentFilter); 1590 } 1591 } 1592 } 1593 return db; 1594 } 1595 1596 // Determine whether a particular table has AUTOINCREMENT in its schema. 1597 private boolean hasAutoIncrement(SQLiteDatabase db, String tableName) { 1598 boolean result = false; 1599 String query = "SELECT sql FROM sqlite_master WHERE type='table' AND name='" + 1600 tableName + "'"; 1601 Cursor c = db.rawQuery(query, null); 1602 if (c != null) { 1603 try { 1604 if (c.moveToFirst()) { 1605 String schema = c.getString(0); 1606 result = schema != null ? schema.contains("AUTOINCREMENT") : false; 1607 Log.d(TAG, "[MmsSmsDb] tableName: " + tableName + " hasAutoIncrement: " + 1608 schema + " result: " + result); 1609 } 1610 } finally { 1611 c.close(); 1612 } 1613 } 1614 return result; 1615 } 1616 1617 // upgradeThreadsTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 1618 // the threads table. This could fail if the user has a lot of conversations and not enough 1619 // storage to make a copy of the threads table. That's ok. This upgrade is optional. It'll 1620 // be called again next time the device is rebooted. 1621 private void upgradeThreadsTableToAutoIncrement(SQLiteDatabase db) { 1622 if (hasAutoIncrement(db, MmsSmsProvider.TABLE_THREADS)) { 1623 Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: already upgraded"); 1624 return; 1625 } 1626 Log.d(TAG, "[MmsSmsDb] upgradeThreadsTableToAutoIncrement: upgrading"); 1627 1628 // Make the _id of the threads table autoincrement so we never re-use thread ids 1629 // Have to create a new temp threads table. Copy all the info from the old table. 1630 // Drop the old table and rename the new table to that of the old. 1631 db.execSQL("CREATE TABLE threads_temp (" + 1632 Threads._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1633 Threads.DATE + " INTEGER DEFAULT 0," + 1634 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," + 1635 Threads.RECIPIENT_IDS + " TEXT," + 1636 Threads.SNIPPET + " TEXT," + 1637 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," + 1638 Threads.READ + " INTEGER DEFAULT 1," + 1639 Threads.TYPE + " INTEGER DEFAULT 0," + 1640 Threads.ERROR + " INTEGER DEFAULT 0," + 1641 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);"); 1642 1643 db.execSQL("INSERT INTO threads_temp SELECT * from threads;"); 1644 db.execSQL("DROP TABLE threads;"); 1645 db.execSQL("ALTER TABLE threads_temp RENAME TO threads;"); 1646 } 1647 1648 // upgradeAddressTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 1649 // the canonical_addresses table. This could fail if the user has a lot of people they've 1650 // messaged with and not enough storage to make a copy of the canonical_addresses table. 1651 // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted. 1652 private void upgradeAddressTableToAutoIncrement(SQLiteDatabase db) { 1653 if (hasAutoIncrement(db, "canonical_addresses")) { 1654 Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: already upgraded"); 1655 return; 1656 } 1657 Log.d(TAG, "[MmsSmsDb] upgradeAddressTableToAutoIncrement: upgrading"); 1658 1659 // Make the _id of the canonical_addresses table autoincrement so we never re-use ids 1660 // Have to create a new temp canonical_addresses table. Copy all the info from the old 1661 // table. Drop the old table and rename the new table to that of the old. 1662 db.execSQL("CREATE TABLE canonical_addresses_temp (_id INTEGER PRIMARY KEY AUTOINCREMENT," + 1663 "address TEXT);"); 1664 1665 db.execSQL("INSERT INTO canonical_addresses_temp SELECT * from canonical_addresses;"); 1666 db.execSQL("DROP TABLE canonical_addresses;"); 1667 db.execSQL("ALTER TABLE canonical_addresses_temp RENAME TO canonical_addresses;"); 1668 } 1669 1670 // upgradePartTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 1671 // the part table. This could fail if the user has a lot of sound/video/picture attachments 1672 // and not enough storage to make a copy of the part table. 1673 // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted. 1674 private void upgradePartTableToAutoIncrement(SQLiteDatabase db) { 1675 if (hasAutoIncrement(db, "part")) { 1676 Log.d(TAG, "[MmsSmsDb] upgradePartTableToAutoIncrement: already upgraded"); 1677 return; 1678 } 1679 Log.d(TAG, "[MmsSmsDb] upgradePartTableToAutoIncrement: upgrading"); 1680 1681 // Make the _id of the part table autoincrement so we never re-use ids 1682 // Have to create a new temp part table. Copy all the info from the old 1683 // table. Drop the old table and rename the new table to that of the old. 1684 db.execSQL("CREATE TABLE part_temp (" + 1685 Part._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1686 Part.MSG_ID + " INTEGER," + 1687 Part.SEQ + " INTEGER DEFAULT 0," + 1688 Part.CONTENT_TYPE + " TEXT," + 1689 Part.NAME + " TEXT," + 1690 Part.CHARSET + " INTEGER," + 1691 Part.CONTENT_DISPOSITION + " TEXT," + 1692 Part.FILENAME + " TEXT," + 1693 Part.CONTENT_ID + " TEXT," + 1694 Part.CONTENT_LOCATION + " TEXT," + 1695 Part.CT_START + " INTEGER," + 1696 Part.CT_TYPE + " TEXT," + 1697 Part._DATA + " TEXT," + 1698 Part.TEXT + " TEXT);"); 1699 1700 db.execSQL("INSERT INTO part_temp SELECT * from part;"); 1701 db.execSQL("DROP TABLE part;"); 1702 db.execSQL("ALTER TABLE part_temp RENAME TO part;"); 1703 1704 // part-related triggers get tossed when the part table is dropped -- rebuild them. 1705 createMmsTriggers(db); 1706 } 1707 1708 // upgradePduTableToAutoIncrement() is called to add the AUTOINCREMENT keyword to 1709 // the pdu table. This could fail if the user has a lot of mms messages 1710 // and not enough storage to make a copy of the pdu table. 1711 // That's ok. This upgrade is optional. It'll be called again next time the device is rebooted. 1712 private void upgradePduTableToAutoIncrement(SQLiteDatabase db) { 1713 if (hasAutoIncrement(db, "pdu")) { 1714 Log.d(TAG, "[MmsSmsDb] upgradePduTableToAutoIncrement: already upgraded"); 1715 return; 1716 } 1717 Log.d(TAG, "[MmsSmsDb] upgradePduTableToAutoIncrement: upgrading"); 1718 1719 // Make the _id of the part table autoincrement so we never re-use ids 1720 // Have to create a new temp part table. Copy all the info from the old 1721 // table. Drop the old table and rename the new table to that of the old. 1722 db.execSQL("CREATE TABLE pdu_temp (" + 1723 Mms._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1724 Mms.THREAD_ID + " INTEGER," + 1725 Mms.DATE + " INTEGER," + 1726 Mms.DATE_SENT + " INTEGER DEFAULT 0," + 1727 Mms.MESSAGE_BOX + " INTEGER," + 1728 Mms.READ + " INTEGER DEFAULT 0," + 1729 Mms.MESSAGE_ID + " TEXT," + 1730 Mms.SUBJECT + " TEXT," + 1731 Mms.SUBJECT_CHARSET + " INTEGER," + 1732 Mms.CONTENT_TYPE + " TEXT," + 1733 Mms.CONTENT_LOCATION + " TEXT," + 1734 Mms.EXPIRY + " INTEGER," + 1735 Mms.MESSAGE_CLASS + " TEXT," + 1736 Mms.MESSAGE_TYPE + " INTEGER," + 1737 Mms.MMS_VERSION + " INTEGER," + 1738 Mms.MESSAGE_SIZE + " INTEGER," + 1739 Mms.PRIORITY + " INTEGER," + 1740 Mms.READ_REPORT + " INTEGER," + 1741 Mms.REPORT_ALLOWED + " INTEGER," + 1742 Mms.RESPONSE_STATUS + " INTEGER," + 1743 Mms.STATUS + " INTEGER," + 1744 Mms.TRANSACTION_ID + " TEXT," + 1745 Mms.RETRIEVE_STATUS + " INTEGER," + 1746 Mms.RETRIEVE_TEXT + " TEXT," + 1747 Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," + 1748 Mms.READ_STATUS + " INTEGER," + 1749 Mms.CONTENT_CLASS + " INTEGER," + 1750 Mms.RESPONSE_TEXT + " TEXT," + 1751 Mms.DELIVERY_TIME + " INTEGER," + 1752 Mms.DELIVERY_REPORT + " INTEGER," + 1753 Mms.LOCKED + " INTEGER DEFAULT 0," + 1754 Mms.SEEN + " INTEGER DEFAULT 0," + 1755 Mms.TEXT_ONLY + " INTEGER DEFAULT 0" + 1756 ");"); 1757 1758 db.execSQL("INSERT INTO pdu_temp SELECT * from pdu;"); 1759 db.execSQL("DROP TABLE pdu;"); 1760 db.execSQL("ALTER TABLE pdu_temp RENAME TO pdu;"); 1761 1762 // pdu-related triggers get tossed when the part table is dropped -- rebuild them. 1763 createMmsTriggers(db); 1764 } 1765 1766 private class LowStorageMonitor extends BroadcastReceiver { 1767 1768 public LowStorageMonitor() { 1769 } 1770 1771 public void onReceive(Context context, Intent intent) { 1772 String action = intent.getAction(); 1773 1774 Log.d(TAG, "[LowStorageMonitor] onReceive intent " + action); 1775 1776 if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { 1777 sTriedAutoIncrement = false; // try to upgrade on the next getWriteableDatabase 1778 } 1779 } 1780 } 1781 1782 private void updateThreadsAttachmentColumn(SQLiteDatabase db) { 1783 // Set the values of that column correctly based on the current 1784 // contents of the database. 1785 db.execSQL("UPDATE threads SET has_attachment=1 WHERE _id IN " + 1786 " (SELECT DISTINCT pdu.thread_id FROM part " + 1787 " JOIN pdu ON pdu._id=part.mid " + 1788 " WHERE part.ct != 'text/plain' AND part.ct != 'application/smil')"); 1789 } 1790} 1791