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