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