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