MmsSmsDatabaseHelper.java revision 075b10540170f65f868d283950216d543cfe38a0
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 static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_DELIVERY_IND; 20import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND; 21import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_ORIG_IND; 22import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_READ_REC_IND; 23import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF; 24import static com.google.android.mms.pdu.PduHeaders.MESSAGE_TYPE_SEND_REQ; 25 26import java.io.IOException; 27import java.io.InputStream; 28import java.io.FileInputStream; 29import java.io.File; 30import java.util.ArrayList; 31 32import com.google.android.mms.pdu.EncodedStringValue; 33 34import android.content.Context; 35import android.database.Cursor; 36import android.database.sqlite.SQLiteDatabase; 37import android.database.sqlite.SQLiteOpenHelper; 38import android.provider.BaseColumns; 39import android.provider.Telephony.Mms; 40import android.provider.Telephony.MmsSms; 41import android.provider.Telephony.Sms; 42import android.provider.Telephony.Threads; 43import android.provider.Telephony.Mms.Addr; 44import android.provider.Telephony.Mms.Part; 45import android.provider.Telephony.Mms.Rate; 46import android.provider.Telephony.MmsSms.PendingMessages; 47import android.util.Log; 48 49public class MmsSmsDatabaseHelper extends SQLiteOpenHelper { 50 private static final String TAG = "MmsSmsDatabaseHelper"; 51 52 private static final String SMS_UPDATE_THREAD_READ_BODY = 53 " UPDATE threads SET read = " + 54 " CASE (SELECT COUNT(*)" + 55 " FROM sms" + 56 " WHERE " + Sms.READ + " = 0" + 57 " AND " + Sms.THREAD_ID + " = threads._id)" + 58 " WHEN 0 THEN 1" + 59 " ELSE 0" + 60 " END" + 61 " WHERE threads._id = new." + Sms.THREAD_ID + "; "; 62 63 private static final String UPDATE_THREAD_COUNT_ON_NEW = 64 " UPDATE threads SET message_count = " + 65 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 66 " ON threads._id = " + Sms.THREAD_ID + 67 " WHERE " + Sms.THREAD_ID + " = new.thread_id" + 68 " AND sms." + Sms.TYPE + " != 3) + " + 69 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 70 " ON threads._id = " + Mms.THREAD_ID + 71 " WHERE " + Mms.THREAD_ID + " = new.thread_id" + 72 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 73 " AND " + Mms.MESSAGE_BOX + " != 3) " + 74 " WHERE threads._id = new.thread_id; "; 75 76 private static final String UPDATE_THREAD_COUNT_ON_OLD = 77 " UPDATE threads SET message_count = " + 78 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 79 " ON threads._id = " + Sms.THREAD_ID + 80 " WHERE " + Sms.THREAD_ID + " = old.thread_id" + 81 " AND sms." + Sms.TYPE + " != 3) + " + 82 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 83 " ON threads._id = " + Mms.THREAD_ID + 84 " WHERE " + Mms.THREAD_ID + " = old.thread_id" + 85 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 86 " AND " + Mms.MESSAGE_BOX + " != 3) " + 87 " WHERE threads._id = old.thread_id; "; 88 89 private static final String SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE = 90 "BEGIN" + 91 " UPDATE threads SET" + 92 " date = (strftime('%s','now') * 1000), " + 93 " snippet = new." + Sms.BODY + ", " + 94 " snippet_cs = 0" + 95 " WHERE threads._id = new." + Sms.THREAD_ID + "; " + 96 UPDATE_THREAD_COUNT_ON_NEW + 97 SMS_UPDATE_THREAD_READ_BODY + 98 "END;"; 99 100 private static final String PDU_UPDATE_THREAD_CONSTRAINTS = 101 " WHEN new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_RETRIEVE_CONF + 102 " OR new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_NOTIFICATION_IND + 103 " OR new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_SEND_REQ + " "; 104 105 private static final String PDU_UPDATE_THREAD_READ_BODY = 106 " UPDATE threads SET read = " + 107 " CASE (SELECT COUNT(*)" + 108 " FROM " + MmsProvider.TABLE_PDU + 109 " WHERE " + Mms.READ + " = 0" + 110 " AND " + Mms.THREAD_ID + " = threads._id)" + 111 " WHEN 0 THEN 1" + 112 " ELSE 0" + 113 " END" + 114 " WHERE threads._id = new." + Mms.THREAD_ID + "; "; 115 116 private static final String PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE = 117 "BEGIN" + 118 " UPDATE threads SET" + 119 " date = (strftime('%s','now') * 1000), " + 120 " snippet = new." + Mms.SUBJECT + ", " + 121 " snippet_cs = new." + Mms.SUBJECT_CHARSET + 122 " WHERE threads._id = new." + Mms.THREAD_ID + "; " + 123 UPDATE_THREAD_COUNT_ON_NEW + 124 PDU_UPDATE_THREAD_READ_BODY + 125 "END;"; 126 127 private static final String UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE = 128 " UPDATE threads SET snippet = " + 129 " (SELECT snippet FROM" + 130 " (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" + 131 " UNION SELECT date, body AS snippet, thread_id FROM sms)" + 132 " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " + 133 " WHERE threads._id = OLD.thread_id; " + 134 " UPDATE threads SET snippet_cs = " + 135 " (SELECT snippet_cs FROM" + 136 " (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" + 137 " UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" + 138 " WHERE thread_id = OLD.thread_id ORDER BY date DESC LIMIT 1) " + 139 " WHERE threads._id = OLD.thread_id; "; 140 141 142 // When a part is inserted, if it is not text/plain or application/smil 143 // (which both can exist with text-only MMSes), then there is an attachment. 144 // Set has_attachment=1 in the threads table for the thread in question. 145 private static final String PART_UPDATE_THREADS_ON_INSERT_TRIGGER = 146 "CREATE TRIGGER update_threads_on_insert_part " + 147 " AFTER INSERT ON part " + 148 " WHEN new.ct != 'text/plain' AND new.ct != 'application/smil' " + 149 " BEGIN " + 150 " UPDATE threads SET has_attachment=1 WHERE _id IN " + 151 " (SELECT pdu.thread_id FROM part JOIN pdu ON pdu._id=part.mid " + 152 " WHERE part._id=new._id LIMIT 1); " + 153 " END"; 154 155 // When the 'mid' column in the part table is updated, we need to run the trigger to update 156 // the threads table's has_attachment column, if the part is an attachment. 157 private static final String PART_UPDATE_THREADS_ON_UPDATE_TRIGGER = 158 "CREATE TRIGGER update_threads_on_update_part " + 159 " AFTER UPDATE of " + Part.MSG_ID + " 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 168 // When a part is deleted (with the same non-text/SMIL constraint as when 169 // we set has_attachment), update the threads table for all threads. 170 // Unfortunately we cannot update only the thread that the part was 171 // attached to, as it is possible that the part has been orphaned and 172 // the message it was attached to is already gone. 173 private static final String PART_UPDATE_THREADS_ON_DELETE_TRIGGER = 174 "CREATE TRIGGER update_threads_on_delete_part " + 175 " AFTER DELETE ON part " + 176 " WHEN old.ct != 'text/plain' AND old.ct != 'application/smil' " + 177 " BEGIN " + 178 " UPDATE threads SET has_attachment = " + 179 " CASE " + 180 " (SELECT COUNT(*) FROM part JOIN pdu ON pdu._id=part.mid " + 181 " WHERE pdu.thread_id = threads._id " + 182 " AND part.ct != 'text/plain' AND part.ct != 'application/smil')" + 183 " WHEN 0 THEN 0 " + 184 " ELSE 1 " + 185 " END; " + 186 " END"; 187 188 private static MmsSmsDatabaseHelper mInstance = null; 189 190 static final String DATABASE_NAME = "mmssms.db"; 191 static final int DATABASE_VERSION = 46; 192 193 private MmsSmsDatabaseHelper(Context context) { 194 super(context, DATABASE_NAME, null, DATABASE_VERSION); 195 } 196 197 /** 198 * Return a singleton helper for the combined MMS and SMS 199 * database. 200 */ 201 /* package */ static synchronized MmsSmsDatabaseHelper getInstance(Context context) { 202 if (mInstance == null) { 203 mInstance = new MmsSmsDatabaseHelper(context); 204 } 205 return mInstance; 206 } 207 208 public static void updateThread(SQLiteDatabase db, long thread_id) { 209 if (thread_id < 0) { 210 updateAllThreads(db, null, null); 211 return; 212 } 213 214 // Delete the row for this thread in the threads table if 215 // there are no more messages attached to it in either 216 // the sms or pdu tables. 217 int rows = db.delete("threads", 218 "_id = ? AND _id NOT IN" + 219 " (SELECT thread_id FROM sms " + 220 " UNION SELECT thread_id FROM pdu)", 221 new String[] { String.valueOf(thread_id) }); 222 if (rows > 0) { 223 // If this deleted a row, we have no more work to do. 224 return; 225 } 226 // Update the message count in the threads table as the sum 227 // of all messages in both the sms and pdu tables. 228 db.execSQL( 229 " UPDATE threads SET message_count = " + 230 " (SELECT COUNT(sms._id) FROM sms LEFT JOIN threads " + 231 " ON threads._id = " + Sms.THREAD_ID + 232 " WHERE " + Sms.THREAD_ID + " = " + thread_id + 233 " AND sms." + Sms.TYPE + " != 3) + " + 234 " (SELECT COUNT(pdu._id) FROM pdu LEFT JOIN threads " + 235 " ON threads._id = " + Mms.THREAD_ID + 236 " WHERE " + Mms.THREAD_ID + " = " + thread_id + 237 " AND (m_type=132 OR m_type=130 OR m_type=128)" + 238 " AND " + Mms.MESSAGE_BOX + " != 3) " + 239 " WHERE threads._id = " + thread_id + ";"); 240 241 // Update the date and the snippet (and its character set) in 242 // the threads table to be that of the most recent message in 243 // the thread. 244 db.execSQL( 245 " UPDATE threads" + 246 " SET" + 247 " date =" + 248 " (SELECT date FROM" + 249 " (SELECT date * 1000 AS date, thread_id FROM pdu" + 250 " UNION SELECT date, thread_id FROM sms)" + 251 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 252 " snippet =" + 253 " (SELECT snippet FROM" + 254 " (SELECT date * 1000 AS date, sub AS snippet, thread_id FROM pdu" + 255 " UNION SELECT date, body AS snippet, thread_id FROM sms)" + 256 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)," + 257 " snippet_cs =" + 258 " (SELECT snippet_cs FROM" + 259 " (SELECT date * 1000 AS date, sub_cs AS snippet_cs, thread_id FROM pdu" + 260 " UNION SELECT date, 0 AS snippet_cs, thread_id FROM sms)" + 261 " WHERE thread_id = " + thread_id + " ORDER BY date DESC LIMIT 1)" + 262 " WHERE threads._id = " + thread_id + ";"); 263 264 // Update the error column of the thread to indicate if there 265 // are any messages in it that have failed to send. 266 db.execSQL( 267 "UPDATE threads SET error =" + 268 " (SELECT COUNT(*) FROM sms WHERE type=5" + 269 " AND thread_id = " + thread_id + " LIMIT 1)" + 270 " WHERE threads._id = " + thread_id + ";"); 271 } 272 273 public static void updateAllThreads(SQLiteDatabase db, String where, String[] whereArgs) { 274 if (where == null) { 275 where = ""; 276 } else { 277 where = "WHERE (" + where + ")"; 278 } 279 String query = "SELECT _id FROM threads WHERE _id IN " + 280 "(SELECT DISTINCT thread_id FROM sms " + where + ")"; 281 Cursor c = db.rawQuery(query, whereArgs); 282 if (c != null) { 283 while (c.moveToNext()) { 284 updateThread(db, c.getInt(0)); 285 } 286 c.close(); 287 } 288 // remove orphaned threads 289 db.delete("threads", 290 "_id NOT IN (SELECT DISTINCT thread_id FROM sms " + 291 "UNION SELECT DISTINCT thread_id FROM pdu)", null); 292 } 293 294 public static int deleteOneSms(SQLiteDatabase db, int message_id) { 295 int thread_id = -1; 296 // Find the thread ID that the specified SMS belongs to. 297 Cursor c = db.query("sms", new String[] { "thread_id" }, 298 "_id=" + message_id, null, null, null, null); 299 if (c != null) { 300 if (c.moveToFirst()) { 301 thread_id = c.getInt(0); 302 } 303 c.close(); 304 } 305 306 // Delete the specified message. 307 int rows = db.delete("sms", "_id=" + message_id, null); 308 if (thread_id > 0) { 309 // Update its thread. 310 updateThread(db, thread_id); 311 } 312 return rows; 313 } 314 315 @Override 316 public void onCreate(SQLiteDatabase db) { 317 createMmsTables(db); 318 createSmsTables(db); 319 createCommonTables(db); 320 createCommonTriggers(db); 321 createMmsTriggers(db); 322 } 323 324 private void createMmsTables(SQLiteDatabase db) { 325 // N.B.: Whenever the columns here are changed, the columns in 326 // {@ref MmsSmsProvider} must be changed to match. 327 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" + 328 Mms._ID + " INTEGER PRIMARY KEY," + 329 Mms.THREAD_ID + " INTEGER," + 330 Mms.DATE + " INTEGER," + 331 Mms.MESSAGE_BOX + " INTEGER," + 332 Mms.READ + " INTEGER DEFAULT 0," + 333 Mms.MESSAGE_ID + " TEXT," + 334 Mms.SUBJECT + " TEXT," + 335 Mms.SUBJECT_CHARSET + " INTEGER," + 336 Mms.CONTENT_TYPE + " TEXT," + 337 Mms.CONTENT_LOCATION + " TEXT," + 338 Mms.EXPIRY + " INTEGER," + 339 Mms.MESSAGE_CLASS + " TEXT," + 340 Mms.MESSAGE_TYPE + " INTEGER," + 341 Mms.MMS_VERSION + " INTEGER," + 342 Mms.MESSAGE_SIZE + " INTEGER," + 343 Mms.PRIORITY + " INTEGER," + 344 Mms.READ_REPORT + " INTEGER," + 345 Mms.REPORT_ALLOWED + " INTEGER," + 346 Mms.RESPONSE_STATUS + " INTEGER," + 347 Mms.STATUS + " INTEGER," + 348 Mms.TRANSACTION_ID + " TEXT," + 349 Mms.RETRIEVE_STATUS + " INTEGER," + 350 Mms.RETRIEVE_TEXT + " TEXT," + 351 Mms.RETRIEVE_TEXT_CHARSET + " INTEGER," + 352 Mms.READ_STATUS + " INTEGER," + 353 Mms.CONTENT_CLASS + " INTEGER," + 354 Mms.RESPONSE_TEXT + " TEXT," + 355 Mms.DELIVERY_TIME + " INTEGER," + 356 Mms.DELIVERY_REPORT + " INTEGER," + 357 Mms.LOCKED + " INTEGER DEFAULT 0" + 358 ");"); 359 360 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_ADDR + " (" + 361 Addr._ID + " INTEGER PRIMARY KEY," + 362 Addr.MSG_ID + " INTEGER," + 363 Addr.CONTACT_ID + " INTEGER," + 364 Addr.ADDRESS + " TEXT," + 365 Addr.TYPE + " INTEGER," + 366 Addr.CHARSET + " INTEGER);"); 367 368 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PART + " (" + 369 Part._ID + " INTEGER PRIMARY KEY," + 370 Part.MSG_ID + " INTEGER," + 371 Part.SEQ + " INTEGER DEFAULT 0," + 372 Part.CONTENT_TYPE + " TEXT," + 373 Part.NAME + " TEXT," + 374 Part.CHARSET + " INTEGER," + 375 Part.CONTENT_DISPOSITION + " TEXT," + 376 Part.FILENAME + " TEXT," + 377 Part.CONTENT_ID + " TEXT," + 378 Part.CONTENT_LOCATION + " TEXT," + 379 Part.CT_START + " INTEGER," + 380 Part.CT_TYPE + " TEXT," + 381 Part._DATA + " TEXT," + 382 Part.TEXT + " TEXT);"); 383 384 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_RATE + " (" + 385 Rate.SENT_TIME + " INTEGER);"); 386 387 db.execSQL("CREATE TABLE " + MmsProvider.TABLE_DRM + " (" + 388 BaseColumns._ID + " INTEGER PRIMARY KEY," + 389 "_data TEXT);"); 390 } 391 392 private void createMmsTriggers(SQLiteDatabase db) { 393 // Cleans up parts when a MM is deleted. 394 db.execSQL("CREATE TRIGGER part_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 395 "BEGIN " + 396 " DELETE FROM " + MmsProvider.TABLE_PART + 397 " WHERE " + Part.MSG_ID + "=old._id;" + 398 "END;"); 399 400 // Cleans up address info when a MM is deleted. 401 db.execSQL("CREATE TRIGGER addr_cleanup DELETE ON " + MmsProvider.TABLE_PDU + " " + 402 "BEGIN " + 403 " DELETE FROM " + MmsProvider.TABLE_ADDR + 404 " WHERE " + Addr.MSG_ID + "=old._id;" + 405 "END;"); 406 407 // Delete obsolete delivery-report, read-report while deleting their 408 // associated Send.req. 409 db.execSQL("CREATE TRIGGER cleanup_delivery_and_read_report " + 410 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 411 "WHEN old." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_SEND_REQ + " " + 412 "BEGIN " + 413 " DELETE FROM " + MmsProvider.TABLE_PDU + 414 " WHERE (" + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_DELIVERY_IND + 415 " OR " + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_READ_ORIG_IND + ")" + 416 " AND " + Mms.MESSAGE_ID + "=old." + Mms.MESSAGE_ID + "; " + 417 "END;"); 418 419 // Update threads table to indicate whether attachments exist when 420 // parts are inserted or deleted. 421 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 422 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 423 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 424 } 425 426 private void createSmsTables(SQLiteDatabase db) { 427 // N.B.: Whenever the columns here are changed, the columns in 428 // {@ref MmsSmsProvider} must be changed to match. 429 db.execSQL("CREATE TABLE sms (" + 430 "_id INTEGER PRIMARY KEY," + 431 "thread_id INTEGER," + 432 "address TEXT," + 433 "person INTEGER," + 434 "date INTEGER," + 435 "protocol INTEGER," + 436 "read INTEGER DEFAULT 0," + 437 "status INTEGER DEFAULT -1," + // a TP-Status value 438 // or -1 if it 439 // status hasn't 440 // been received 441 "type INTEGER," + 442 "reply_path_present INTEGER," + 443 "subject TEXT," + 444 "body TEXT," + 445 "service_center TEXT," + 446 "locked INTEGER DEFAULT 0" + 447 ");"); 448 449 /** 450 * This table is used by the SMS dispatcher to hold 451 * incomplete partial messages until all the parts arrive. 452 */ 453 db.execSQL("CREATE TABLE raw (" + 454 "_id INTEGER PRIMARY KEY," + 455 "date INTEGER," + 456 "reference_number INTEGER," + // one per full message 457 "count INTEGER," + // the number of parts 458 "sequence INTEGER," + // the part number of this message 459 "destination_port INTEGER," + 460 "address TEXT," + 461 "pdu TEXT);"); // the raw PDU for this part 462 463 db.execSQL("CREATE TABLE attachments (" + 464 "sms_id INTEGER," + 465 "content_url TEXT," + 466 "offset INTEGER);"); 467 468 /** 469 * This table is used by the SMS dispatcher to hold pending 470 * delivery status report intents. 471 */ 472 db.execSQL("CREATE TABLE sr_pending (" + 473 "reference_number INTEGER," + 474 "action TEXT," + 475 "data TEXT);"); 476 } 477 478 private void createCommonTables(SQLiteDatabase db) { 479 // TODO Ensure that each entry is removed when the last use of 480 // any address equivalent to its address is removed. 481 482 /** 483 * This table maps the first instance seen of any particular 484 * MMS/SMS address to an ID, which is then used as its 485 * canonical representation. If the same address or an 486 * equivalent address (as determined by our Sqlite 487 * PHONE_NUMBERS_EQUAL extension) is seen later, this same ID 488 * will be used. 489 */ 490 db.execSQL("CREATE TABLE canonical_addresses (" + 491 "_id INTEGER PRIMARY KEY," + 492 "address TEXT);"); 493 494 /** 495 * This table maps the subject and an ordered set of recipient 496 * IDs, separated by spaces, to a unique thread ID. The IDs 497 * come from the canonical_addresses table. This works 498 * because messages are considered to be part of the same 499 * thread if they have the same subject (or a null subject) 500 * and the same set of recipients. 501 */ 502 db.execSQL("CREATE TABLE threads (" + 503 Threads._ID + " INTEGER PRIMARY KEY," + 504 Threads.DATE + " INTEGER DEFAULT 0," + 505 Threads.MESSAGE_COUNT + " INTEGER DEFAULT 0," + 506 Threads.RECIPIENT_IDS + " TEXT," + 507 Threads.SNIPPET + " TEXT," + 508 Threads.SNIPPET_CHARSET + " INTEGER DEFAULT 0," + 509 Threads.READ + " INTEGER DEFAULT 1," + 510 Threads.TYPE + " INTEGER DEFAULT 0," + 511 Threads.ERROR + " INTEGER DEFAULT 0," + 512 Threads.HAS_ATTACHMENT + " INTEGER DEFAULT 0);"); 513 514 /** 515 * This table stores the queue of messages to be sent/downloaded. 516 */ 517 db.execSQL("CREATE TABLE " + MmsSmsProvider.TABLE_PENDING_MSG +" (" + 518 PendingMessages._ID + " INTEGER PRIMARY KEY," + 519 PendingMessages.PROTO_TYPE + " INTEGER," + 520 PendingMessages.MSG_ID + " INTEGER," + 521 PendingMessages.MSG_TYPE + " INTEGER," + 522 PendingMessages.ERROR_TYPE + " INTEGER," + 523 PendingMessages.ERROR_CODE + " INTEGER," + 524 PendingMessages.RETRY_INDEX + " INTEGER NOT NULL DEFAULT 0," + 525 PendingMessages.DUE_TIME + " INTEGER," + 526 PendingMessages.LAST_TRY + " INTEGER);"); 527 528 } 529 530 // TODO Check the query plans for these triggers. 531 private void createCommonTriggers(SQLiteDatabase db) { 532 // Updates threads table whenever a message is added to pdu. 533 db.execSQL("CREATE TRIGGER pdu_update_thread_on_insert AFTER INSERT ON " + 534 MmsProvider.TABLE_PDU + " " + 535 PDU_UPDATE_THREAD_CONSTRAINTS + 536 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 537 538 // Updates threads table whenever a message is added to sms. 539 db.execSQL("CREATE TRIGGER sms_update_thread_on_insert AFTER INSERT ON sms " + 540 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 541 542 // Updates threads table whenever a message in pdu is updated. 543 db.execSQL("CREATE TRIGGER pdu_update_thread_date_subject_on_update AFTER" + 544 " UPDATE OF " + Mms.DATE + ", " + Mms.SUBJECT + ", " + Mms.MESSAGE_BOX + 545 " ON " + MmsProvider.TABLE_PDU + " " + 546 PDU_UPDATE_THREAD_CONSTRAINTS + 547 PDU_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 548 549 // Updates threads table whenever a message in sms is updated. 550 db.execSQL("CREATE TRIGGER sms_update_thread_date_subject_on_update AFTER" + 551 " UPDATE OF " + Sms.DATE + ", " + Sms.BODY + ", " + Sms.TYPE + 552 " ON sms " + 553 SMS_UPDATE_THREAD_DATE_SNIPPET_COUNT_ON_UPDATE); 554 555 // Updates threads table whenever a message in pdu is updated. 556 db.execSQL("CREATE TRIGGER pdu_update_thread_read_on_update AFTER" + 557 " UPDATE OF " + Mms.READ + 558 " ON " + MmsProvider.TABLE_PDU + " " + 559 PDU_UPDATE_THREAD_CONSTRAINTS + 560 "BEGIN " + 561 PDU_UPDATE_THREAD_READ_BODY + 562 "END;"); 563 564 // Updates threads table whenever a message in sms is updated. 565 db.execSQL("CREATE TRIGGER sms_update_thread_read_on_update AFTER" + 566 " UPDATE OF " + Sms.READ + 567 " ON sms " + 568 "BEGIN " + 569 SMS_UPDATE_THREAD_READ_BODY + 570 "END;"); 571 572 // Update threads table whenever a message in pdu is deleted 573 db.execSQL("CREATE TRIGGER pdu_update_thread_on_delete " + 574 "AFTER DELETE ON pdu " + 575 "BEGIN " + 576 " UPDATE threads SET " + 577 " date = (strftime('%s','now') * 1000)" + 578 " WHERE threads._id = old." + Mms.THREAD_ID + "; " + 579 UPDATE_THREAD_COUNT_ON_OLD + 580 UPDATE_THREAD_SNIPPET_SNIPPET_CS_ON_DELETE + 581 "END;"); 582 583 // When the last message in a thread is deleted, these 584 // triggers ensure that the entry for its thread ID is removed 585 // from the threads table. 586 db.execSQL("CREATE TRIGGER delete_obsolete_threads_pdu " + 587 "AFTER DELETE ON pdu " + 588 "BEGIN " + 589 " DELETE FROM threads " + 590 " WHERE " + 591 " _id = old.thread_id " + 592 " AND _id NOT IN " + 593 " (SELECT thread_id FROM sms " + 594 " UNION SELECT thread_id from pdu); " + 595 "END;"); 596 597 db.execSQL("CREATE TRIGGER delete_obsolete_threads_when_update_pdu " + 598 "AFTER UPDATE OF " + Mms.THREAD_ID + " ON pdu " + 599 "WHEN old." + Mms.THREAD_ID + " != new." + Mms.THREAD_ID + " " + 600 "BEGIN " + 601 " DELETE FROM threads " + 602 " WHERE " + 603 " _id = old.thread_id " + 604 " AND _id NOT IN " + 605 " (SELECT thread_id FROM sms " + 606 " UNION SELECT thread_id from pdu); " + 607 "END;"); 608 // Insert pending status for M-Notification.ind or M-ReadRec.ind 609 // when they are inserted into Inbox/Outbox. 610 db.execSQL("CREATE TRIGGER insert_mms_pending_on_insert " + 611 "AFTER INSERT ON pdu " + 612 "WHEN new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_NOTIFICATION_IND + 613 " OR new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_READ_REC_IND + " " + 614 "BEGIN " + 615 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 616 " (" + PendingMessages.PROTO_TYPE + "," + 617 " " + PendingMessages.MSG_ID + "," + 618 " " + PendingMessages.MSG_TYPE + "," + 619 " " + PendingMessages.ERROR_TYPE + "," + 620 " " + PendingMessages.ERROR_CODE + "," + 621 " " + PendingMessages.RETRY_INDEX + "," + 622 " " + PendingMessages.DUE_TIME + ") " + 623 " VALUES " + 624 " (" + MmsSms.MMS_PROTO + "," + 625 " new." + BaseColumns._ID + "," + 626 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 627 "END;"); 628 629 // Insert pending status for M-Send.req when it is moved into Outbox. 630 db.execSQL("CREATE TRIGGER insert_mms_pending_on_update " + 631 "AFTER UPDATE ON pdu " + 632 "WHEN new." + Mms.MESSAGE_TYPE + "=" + MESSAGE_TYPE_SEND_REQ + 633 " AND new." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 634 " AND old." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 635 "BEGIN " + 636 " INSERT INTO " + MmsSmsProvider.TABLE_PENDING_MSG + 637 " (" + PendingMessages.PROTO_TYPE + "," + 638 " " + PendingMessages.MSG_ID + "," + 639 " " + PendingMessages.MSG_TYPE + "," + 640 " " + PendingMessages.ERROR_TYPE + "," + 641 " " + PendingMessages.ERROR_CODE + "," + 642 " " + PendingMessages.RETRY_INDEX + "," + 643 " " + PendingMessages.DUE_TIME + ") " + 644 " VALUES " + 645 " (" + MmsSms.MMS_PROTO + "," + 646 " new." + BaseColumns._ID + "," + 647 " new." + Mms.MESSAGE_TYPE + ",0,0,0,0);" + 648 "END;"); 649 650 // When a message is moved out of Outbox, delete its pending status. 651 db.execSQL("CREATE TRIGGER delete_mms_pending_on_update " + 652 "AFTER UPDATE ON " + MmsProvider.TABLE_PDU + " " + 653 "WHEN old." + Mms.MESSAGE_BOX + "=" + Mms.MESSAGE_BOX_OUTBOX + 654 " AND new." + Mms.MESSAGE_BOX + "!=" + Mms.MESSAGE_BOX_OUTBOX + " " + 655 "BEGIN " + 656 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 657 " WHERE " + PendingMessages.MSG_ID + "=new._id; " + 658 "END;"); 659 660 // Delete pending status for a message when it is deleted. 661 db.execSQL("CREATE TRIGGER delete_mms_pending_on_delete " + 662 "AFTER DELETE ON " + MmsProvider.TABLE_PDU + " " + 663 "BEGIN " + 664 " DELETE FROM " + MmsSmsProvider.TABLE_PENDING_MSG + 665 " WHERE " + PendingMessages.MSG_ID + "=old._id; " + 666 "END;"); 667 668 // TODO Add triggers for SMS retry-status management. 669 670 // Update the error flag of threads when the error type of 671 // a pending MM is updated. 672 db.execSQL("CREATE TRIGGER update_threads_error_on_update_mms " + 673 " AFTER UPDATE OF err_type ON pending_msgs " + 674 " WHEN (OLD.err_type < 10 AND NEW.err_type >= 10)" + 675 " OR (OLD.err_type >= 10 AND NEW.err_type < 10) " + 676 "BEGIN" + 677 " UPDATE threads SET error = " + 678 " CASE" + 679 " WHEN NEW.err_type >= 10 THEN error + 1" + 680 " ELSE error - 1" + 681 " END " + 682 " WHERE _id =" + 683 " (SELECT DISTINCT thread_id" + 684 " FROM pdu" + 685 " WHERE _id = NEW.msg_id); " + 686 "END;"); 687 688 // Update the error flag of threads when delete pending message. 689 db.execSQL("CREATE TRIGGER update_threads_error_on_delete_mms " + 690 " BEFORE DELETE ON pdu" + 691 " WHEN OLD._id IN (SELECT DISTINCT msg_id" + 692 " FROM pending_msgs" + 693 " WHERE err_type >= 10) " + 694 "BEGIN " + 695 " UPDATE threads SET error = error - 1" + 696 " WHERE _id = OLD.thread_id; " + 697 "END;"); 698 699 // Update the error flag of threads while moving an MM out of Outbox, 700 // which was failed to be sent permanently. 701 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 702 " BEFORE UPDATE OF msg_box ON pdu " + 703 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 704 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 705 " FROM pending_msgs" + 706 " WHERE err_type >= 10)) " + 707 "BEGIN " + 708 " UPDATE threads SET error = error - 1" + 709 " WHERE _id = OLD.thread_id; " + 710 "END;"); 711 712 // Update the error flag of threads after a text message was 713 // failed to send/receive. 714 db.execSQL("CREATE TRIGGER update_threads_error_on_update_sms " + 715 " AFTER UPDATE OF type ON sms" + 716 " WHEN (OLD.type != 5 AND NEW.type = 5)" + 717 " OR (OLD.type = 5 AND NEW.type != 5) " + 718 "BEGIN " + 719 " UPDATE threads SET error = " + 720 " CASE" + 721 " WHEN NEW.type = 5 THEN error + 1" + 722 " ELSE error - 1" + 723 " END " + 724 " WHERE _id = NEW.thread_id; " + 725 "END;"); 726 } 727 728 @Override 729 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 730 Log.w(TAG, "Upgrading database from version " + oldVersion 731 + " to " + currentVersion + "."); 732 733 switch (oldVersion) { 734 case 40: 735 if (currentVersion <= 40) { 736 return; 737 } 738 739 db.beginTransaction(); 740 try { 741 upgradeDatabaseToVersion41(db); 742 db.setTransactionSuccessful(); 743 } catch (Throwable ex) { 744 Log.e(TAG, ex.getMessage(), ex); 745 break; 746 } finally { 747 db.endTransaction(); 748 } 749 // fall through 750 case 41: 751 if (currentVersion <= 41) { 752 return; 753 } 754 755 db.beginTransaction(); 756 try { 757 upgradeDatabaseToVersion42(db); 758 db.setTransactionSuccessful(); 759 } catch (Throwable ex) { 760 Log.e(TAG, ex.getMessage(), ex); 761 break; 762 } finally { 763 db.endTransaction(); 764 } 765 // fall through 766 case 42: 767 if (currentVersion <= 42) { 768 return; 769 } 770 771 db.beginTransaction(); 772 try { 773 upgradeDatabaseToVersion43(db); 774 db.setTransactionSuccessful(); 775 } catch (Throwable ex) { 776 Log.e(TAG, ex.getMessage(), ex); 777 break; 778 } finally { 779 db.endTransaction(); 780 } 781 // fall through 782 case 43: 783 if (currentVersion <= 43) { 784 return; 785 } 786 787 db.beginTransaction(); 788 try { 789 upgradeDatabaseToVersion44(db); 790 db.setTransactionSuccessful(); 791 } catch (Throwable ex) { 792 Log.e(TAG, ex.getMessage(), ex); 793 break; 794 } finally { 795 db.endTransaction(); 796 } 797 // fall through 798 case 44: 799 if (currentVersion <= 44) { 800 return; 801 } 802 803 db.beginTransaction(); 804 try { 805 upgradeDatabaseToVersion45(db); 806 db.setTransactionSuccessful(); 807 } catch (Throwable ex) { 808 Log.e(TAG, ex.getMessage(), ex); 809 break; 810 } finally { 811 db.endTransaction(); 812 } 813 // fall through 814 case 45: 815 if (currentVersion <= 45) { 816 return; 817 } 818 db.beginTransaction(); 819 try { 820 upgradeDatabaseToVersion46(db); 821 db.setTransactionSuccessful(); 822 } catch (Throwable ex) { 823 Log.e(TAG, ex.getMessage(), ex); 824 break; 825 } finally { 826 db.endTransaction(); 827 } 828 return; 829 } 830 831 832 Log.e(TAG, "Destroying all old data."); 833 dropAll(db); 834 onCreate(db); 835 } 836 837 private void dropAll(SQLiteDatabase db) { 838 // Clean the database out in order to start over from scratch. 839 // We don't need to drop our triggers here because SQLite automatically 840 // drops a trigger when its attached database is dropped. 841 db.execSQL("DROP TABLE IF EXISTS canonical_addresses"); 842 db.execSQL("DROP TABLE IF EXISTS threads"); 843 db.execSQL("DROP TABLE IF EXISTS " + MmsSmsProvider.TABLE_PENDING_MSG); 844 db.execSQL("DROP TABLE IF EXISTS sms"); 845 db.execSQL("DROP TABLE IF EXISTS raw"); 846 db.execSQL("DROP TABLE IF EXISTS attachments"); 847 db.execSQL("DROP TABLE IF EXISTS thread_ids"); 848 db.execSQL("DROP TABLE IF EXISTS sr_pending"); 849 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PDU + ";"); 850 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_ADDR + ";"); 851 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_PART + ";"); 852 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_RATE + ";"); 853 db.execSQL("DROP TABLE IF EXISTS " + MmsProvider.TABLE_DRM + ";"); 854 } 855 856 private void upgradeDatabaseToVersion41(SQLiteDatabase db) { 857 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_move_mms"); 858 db.execSQL("CREATE TRIGGER update_threads_error_on_move_mms " + 859 " BEFORE UPDATE OF msg_box ON pdu " + 860 " WHEN (OLD.msg_box = 4 AND NEW.msg_box != 4) " + 861 " AND (OLD._id IN (SELECT DISTINCT msg_id" + 862 " FROM pending_msgs" + 863 " WHERE err_type >= 10)) " + 864 "BEGIN " + 865 " UPDATE threads SET error = error - 1" + 866 " WHERE _id = OLD.thread_id; " + 867 "END;"); 868 } 869 870 private void upgradeDatabaseToVersion42(SQLiteDatabase db) { 871 db.execSQL("DROP TRIGGER IF EXISTS sms_update_thread_on_delete"); 872 db.execSQL("DROP TRIGGER IF EXISTS delete_obsolete_threads_sms"); 873 db.execSQL("DROP TRIGGER IF EXISTS update_threads_error_on_delete_sms"); 874 } 875 876 private void upgradeDatabaseToVersion43(SQLiteDatabase db) { 877 // Add 'has_attachment' column to threads table. 878 db.execSQL("ALTER TABLE threads ADD COLUMN has_attachment INTEGER DEFAULT 0"); 879 880 // Set the values of that column correctly based on the current 881 // contents of the database. 882 db.execSQL("UPDATE threads SET has_attachment=1 WHERE _id IN " + 883 " (SELECT DISTINCT pdu.thread_id FROM part " + 884 " JOIN pdu ON pdu._id=part.mid " + 885 " WHERE part.ct != 'text/plain' AND part.ct != 'application/smil')"); 886 887 // Add insert and delete triggers for keeping it up to date. 888 db.execSQL(PART_UPDATE_THREADS_ON_INSERT_TRIGGER); 889 db.execSQL(PART_UPDATE_THREADS_ON_DELETE_TRIGGER); 890 } 891 892 private void upgradeDatabaseToVersion44(SQLiteDatabase db) { 893 // Set the values of that column correctly based on the current 894 // contents of the database. 895 db.execSQL("UPDATE threads SET has_attachment=1 WHERE _id IN " + 896 " (SELECT DISTINCT pdu.thread_id FROM part " + 897 " JOIN pdu ON pdu._id=part.mid " + 898 " WHERE part.ct != 'text/plain' AND part.ct != 'application/smil')"); 899 900 // add the update trigger for keeping the threads up to date. 901 db.execSQL(PART_UPDATE_THREADS_ON_UPDATE_TRIGGER); 902 } 903 904 private void upgradeDatabaseToVersion45(SQLiteDatabase db) { 905 // Add 'locked' column to sms table. 906 db.execSQL("ALTER TABLE sms ADD COLUMN locked INTEGER DEFAULT 0"); 907 908 // Add 'locked' column to pdu table. 909 db.execSQL("ALTER TABLE pdu ADD COLUMN " + Mms.LOCKED + " INTEGER DEFAULT 0"); 910 } 911 912 private void upgradeDatabaseToVersion46(SQLiteDatabase db) { 913 // add the "text" column for caching inline text (e.g. strings) instead of 914 // putting them in an external file 915 db.execSQL("ALTER TABLE part ADD COLUMN " + Part.TEXT + " TEXT"); 916 917 Cursor textRows = db.query( 918 "part", 919 new String[] { Part._ID, Part._DATA, Part.TEXT}, 920 "ct = 'text/plain' OR ct == 'application/smil'", 921 null, 922 null, 923 null, 924 null); 925 ArrayList<String> filesToDelete = new ArrayList<String>(); 926 try { 927 if (textRows != null) { 928 int partIdColumn = textRows.getColumnIndex(Part._ID); 929 int partDataColumn = textRows.getColumnIndex(Part._DATA); 930 int partTextColumn = textRows.getColumnIndex(Part.TEXT); 931 932 // This code is imperfect in that we can't guarantee that all the 933 // backing files get deleted. For example if the system aborts after 934 // the database is updated but before we complete the process of 935 // deleting files. 936 while (textRows.moveToNext()) { 937 String path = textRows.getString(partDataColumn); 938 if (path != null) { 939 try { 940 InputStream is = new FileInputStream(path); 941 byte [] data = new byte[is.available()]; 942 is.read(data); 943 EncodedStringValue v = new EncodedStringValue(data); 944 textRows.updateString(partTextColumn, v.getString()); 945 textRows.updateToNull(partDataColumn); 946 is.close(); 947 filesToDelete.add(path); 948 } catch (IOException e) { 949 // TODO Auto-generated catch block 950 e.printStackTrace(); 951 } 952 } 953 } 954 } 955 } finally { 956 textRows.commitUpdates(); 957 for (String pathToDelete : filesToDelete) { 958 try { 959 (new File(pathToDelete)).delete(); 960 } catch (SecurityException ex) { 961 Log.e(TAG, "unable to clean up old mms file for " + pathToDelete, ex); 962 } 963 } 964 if (textRows != null) { 965 textRows.close(); 966 } 967 } 968 } 969} 970