1 /* 2 * Copyright (C) 2007 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 android.database.sqlite; 18 19 import android.annotation.IntRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.database.DatabaseErrorHandler; 24 import android.database.SQLException; 25 import android.database.sqlite.SQLiteDatabase.CursorFactory; 26 import android.os.FileUtils; 27 import android.util.Log; 28 29 import com.android.internal.util.Preconditions; 30 31 import java.io.File; 32 33 /** 34 * A helper class to manage database creation and version management. 35 * 36 * <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and 37 * optionally {@link #onOpen}, and this class takes care of opening the database 38 * if it exists, creating it if it does not, and upgrading it as necessary. 39 * Transactions are used to make sure the database is always in a sensible state. 40 * 41 * <p>This class makes it easy for {@link android.content.ContentProvider} 42 * implementations to defer opening and upgrading the database until first use, 43 * to avoid blocking application startup with long-running database upgrades. 44 * 45 * <p>For an example, see the NotePadProvider class in the NotePad sample application, 46 * in the <em>samples/</em> directory of the SDK.</p> 47 * 48 * <p class="note"><strong>Note:</strong> this class assumes 49 * monotonically increasing version numbers for upgrades.</p> 50 */ 51 public abstract class SQLiteOpenHelper { 52 private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); 53 54 private final Context mContext; 55 private final String mName; 56 private final int mNewVersion; 57 private final int mMinimumSupportedVersion; 58 59 private SQLiteDatabase mDatabase; 60 private boolean mIsInitializing; 61 private SQLiteDatabase.OpenParams.Builder mOpenParamsBuilder; 62 63 /** 64 * Create a helper object to create, open, and/or manage a database. 65 * This method always returns very quickly. The database is not actually 66 * created or opened until one of {@link #getWritableDatabase} or 67 * {@link #getReadableDatabase} is called. 68 * 69 * @param context to use for locating paths to the the database 70 * @param name of the database file, or null for an in-memory database 71 * @param factory to use for creating cursor objects, or null for the default 72 * @param version number of the database (starting at 1); if the database is older, 73 * {@link #onUpgrade} will be used to upgrade the database; if the database is 74 * newer, {@link #onDowngrade} will be used to downgrade the database 75 */ SQLiteOpenHelper(@ullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version)76 public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, 77 @Nullable CursorFactory factory, int version) { 78 this(context, name, factory, version, null); 79 } 80 81 /** 82 * Create a helper object to create, open, and/or manage a database. 83 * The database is not actually created or opened until one of 84 * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. 85 * 86 * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be 87 * used to handle corruption when sqlite reports database corruption.</p> 88 * 89 * @param context to use for locating paths to the the database 90 * @param name of the database file, or null for an in-memory database 91 * @param factory to use for creating cursor objects, or null for the default 92 * @param version number of the database (starting at 1); if the database is older, 93 * {@link #onUpgrade} will be used to upgrade the database; if the database is 94 * newer, {@link #onDowngrade} will be used to downgrade the database 95 * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database 96 * corruption, or null to use the default error handler. 97 */ SQLiteOpenHelper(@ullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version, @Nullable DatabaseErrorHandler errorHandler)98 public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, 99 @Nullable CursorFactory factory, int version, 100 @Nullable DatabaseErrorHandler errorHandler) { 101 this(context, name, factory, version, 0, errorHandler); 102 } 103 104 /** 105 * Create a helper object to create, open, and/or manage a database. 106 * This method always returns very quickly. The database is not actually 107 * created or opened until one of {@link #getWritableDatabase} or 108 * {@link #getReadableDatabase} is called. 109 * 110 * @param context to use for locating paths to the the database 111 * @param name of the database file, or null for an in-memory database 112 * @param version number of the database (starting at 1); if the database is older, 113 * {@link #onUpgrade} will be used to upgrade the database; if the database is 114 * newer, {@link #onDowngrade} will be used to downgrade the database 115 * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}. 116 * Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be 117 * set when the helper opens the database 118 */ SQLiteOpenHelper(@ullable Context context, @Nullable String name, int version, @NonNull SQLiteDatabase.OpenParams openParams)119 public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version, 120 @NonNull SQLiteDatabase.OpenParams openParams) { 121 this(context, name, version, 0, openParams.toBuilder()); 122 } 123 124 /** 125 * Same as {@link #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler)} 126 * but also accepts an integer minimumSupportedVersion as a convenience for upgrading very old 127 * versions of this database that are no longer supported. If a database with older version that 128 * minimumSupportedVersion is found, it is simply deleted and a new database is created with the 129 * given name and version 130 * 131 * @param context to use for locating paths to the the database 132 * @param name the name of the database file, null for a temporary in-memory database 133 * @param factory to use for creating cursor objects, null for default 134 * @param version the required version of the database 135 * @param minimumSupportedVersion the minimum version that is supported to be upgraded to 136 * {@code version} via {@link #onUpgrade}. If the current database version is lower 137 * than this, database is simply deleted and recreated with the version passed in 138 * {@code version}. {@link #onBeforeDelete} is called before deleting the database 139 * when this happens. This is 0 by default. 140 * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database 141 * corruption, or null to use the default error handler. 142 * @see #onBeforeDelete(SQLiteDatabase) 143 * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, DatabaseErrorHandler) 144 * @see #onUpgrade(SQLiteDatabase, int, int) 145 * @hide 146 */ SQLiteOpenHelper(@ullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version, int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler)147 public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, 148 @Nullable CursorFactory factory, int version, 149 int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler) { 150 this(context, name, version, minimumSupportedVersion, 151 new SQLiteDatabase.OpenParams.Builder()); 152 mOpenParamsBuilder.setCursorFactory(factory); 153 mOpenParamsBuilder.setErrorHandler(errorHandler); 154 } 155 SQLiteOpenHelper(@ullable Context context, @Nullable String name, int version, int minimumSupportedVersion, @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder)156 private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version, 157 int minimumSupportedVersion, 158 @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder) { 159 Preconditions.checkNotNull(openParamsBuilder); 160 if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); 161 162 mContext = context; 163 mName = name; 164 mNewVersion = version; 165 mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion); 166 setOpenParamsBuilder(openParamsBuilder); 167 } 168 169 /** 170 * Return the name of the SQLite database being opened, as given to 171 * the constructor. 172 */ getDatabaseName()173 public String getDatabaseName() { 174 return mName; 175 } 176 177 /** 178 * Enables or disables the use of write-ahead logging for the database. 179 * 180 * Write-ahead logging cannot be used with read-only databases so the value of 181 * this flag is ignored if the database is opened read-only. 182 * 183 * @param enabled True if write-ahead logging should be enabled, false if it 184 * should be disabled. 185 * 186 * @see SQLiteDatabase#enableWriteAheadLogging() 187 */ setWriteAheadLoggingEnabled(boolean enabled)188 public void setWriteAheadLoggingEnabled(boolean enabled) { 189 synchronized (this) { 190 if (mOpenParamsBuilder.isWriteAheadLoggingEnabled() != enabled) { 191 if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { 192 if (enabled) { 193 mDatabase.enableWriteAheadLogging(); 194 } else { 195 mDatabase.disableWriteAheadLogging(); 196 } 197 } 198 mOpenParamsBuilder.setWriteAheadLoggingEnabled(enabled); 199 } 200 // Compatibility WAL is disabled if an app disables or enables WAL 201 mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.DISABLE_COMPATIBILITY_WAL); 202 } 203 } 204 205 /** 206 * Configures <a href="https://sqlite.org/malloc.html#lookaside">lookaside memory allocator</a> 207 * 208 * <p>This method should be called from the constructor of the subclass, 209 * before opening the database, since lookaside memory configuration can only be changed 210 * when no connection is using it 211 * 212 * <p>SQLite default settings will be used, if this method isn't called. 213 * Use {@code setLookasideConfig(0,0)} to disable lookaside 214 * 215 * <p><strong>Note:</strong> Provided slotSize/slotCount configuration is just a recommendation. 216 * The system may choose different values depending on a device, e.g. lookaside allocations 217 * can be disabled on low-RAM devices 218 * 219 * @param slotSize The size in bytes of each lookaside slot. 220 * @param slotCount The total number of lookaside memory slots per database connection. 221 */ setLookasideConfig(@ntRangefrom = 0) final int slotSize, @IntRange(from = 0) final int slotCount)222 public void setLookasideConfig(@IntRange(from = 0) final int slotSize, 223 @IntRange(from = 0) final int slotCount) { 224 synchronized (this) { 225 if (mDatabase != null && mDatabase.isOpen()) { 226 throw new IllegalStateException( 227 "Lookaside memory config cannot be changed after opening the database"); 228 } 229 mOpenParamsBuilder.setLookasideConfig(slotSize, slotCount); 230 } 231 } 232 233 /** 234 * Sets configuration parameters that are used for opening {@link SQLiteDatabase}. 235 * <p>Please note that {@link SQLiteDatabase#CREATE_IF_NECESSARY} flag will always be set when 236 * opening the database 237 * 238 * @param openParams configuration parameters that are used for opening {@link SQLiteDatabase}. 239 * @throws IllegalStateException if the database is already open 240 */ setOpenParams(@onNull SQLiteDatabase.OpenParams openParams)241 public void setOpenParams(@NonNull SQLiteDatabase.OpenParams openParams) { 242 Preconditions.checkNotNull(openParams); 243 synchronized (this) { 244 if (mDatabase != null && mDatabase.isOpen()) { 245 throw new IllegalStateException( 246 "OpenParams cannot be set after opening the database"); 247 } 248 setOpenParamsBuilder(new SQLiteDatabase.OpenParams.Builder(openParams)); 249 } 250 } 251 setOpenParamsBuilder(SQLiteDatabase.OpenParams.Builder openParamsBuilder)252 private void setOpenParamsBuilder(SQLiteDatabase.OpenParams.Builder openParamsBuilder) { 253 mOpenParamsBuilder = openParamsBuilder; 254 mOpenParamsBuilder.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY); 255 } 256 257 /** 258 * Sets the maximum number of milliseconds that SQLite connection is allowed to be idle 259 * before it is closed and removed from the pool. 260 * 261 * <p>This method should be called from the constructor of the subclass, 262 * before opening the database 263 * 264 * @param idleConnectionTimeoutMs timeout in milliseconds. Use {@link Long#MAX_VALUE} value 265 * to allow unlimited idle connections. 266 */ setIdleConnectionTimeout(@ntRangefrom = 0) final long idleConnectionTimeoutMs)267 public void setIdleConnectionTimeout(@IntRange(from = 0) final long idleConnectionTimeoutMs) { 268 synchronized (this) { 269 if (mDatabase != null && mDatabase.isOpen()) { 270 throw new IllegalStateException( 271 "Connection timeout setting cannot be changed after opening the database"); 272 } 273 mOpenParamsBuilder.setIdleConnectionTimeout(idleConnectionTimeoutMs); 274 } 275 } 276 277 /** 278 * Create and/or open a database that will be used for reading and writing. 279 * The first time this is called, the database will be opened and 280 * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be 281 * called. 282 * 283 * <p>Once opened successfully, the database is cached, so you can 284 * call this method every time you need to write to the database. 285 * (Make sure to call {@link #close} when you no longer need the database.) 286 * Errors such as bad permissions or a full disk may cause this method 287 * to fail, but future attempts may succeed if the problem is fixed.</p> 288 * 289 * <p class="caution">Database upgrade may take a long time, you 290 * should not call this method from the application main thread, including 291 * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. 292 * 293 * @throws SQLiteException if the database cannot be opened for writing 294 * @return a read/write database object valid until {@link #close} is called 295 */ getWritableDatabase()296 public SQLiteDatabase getWritableDatabase() { 297 synchronized (this) { 298 return getDatabaseLocked(true); 299 } 300 } 301 302 /** 303 * Create and/or open a database. This will be the same object returned by 304 * {@link #getWritableDatabase} unless some problem, such as a full disk, 305 * requires the database to be opened read-only. In that case, a read-only 306 * database object will be returned. If the problem is fixed, a future call 307 * to {@link #getWritableDatabase} may succeed, in which case the read-only 308 * database object will be closed and the read/write object will be returned 309 * in the future. 310 * 311 * <p class="caution">Like {@link #getWritableDatabase}, this method may 312 * take a long time to return, so you should not call it from the 313 * application main thread, including from 314 * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. 315 * 316 * @throws SQLiteException if the database cannot be opened 317 * @return a database object valid until {@link #getWritableDatabase} 318 * or {@link #close} is called. 319 */ getReadableDatabase()320 public SQLiteDatabase getReadableDatabase() { 321 synchronized (this) { 322 return getDatabaseLocked(false); 323 } 324 } 325 getDatabaseLocked(boolean writable)326 private SQLiteDatabase getDatabaseLocked(boolean writable) { 327 if (mDatabase != null) { 328 if (!mDatabase.isOpen()) { 329 // Darn! The user closed the database by calling mDatabase.close(). 330 mDatabase = null; 331 } else if (!writable || !mDatabase.isReadOnly()) { 332 // The database is already open for business. 333 return mDatabase; 334 } 335 } 336 337 if (mIsInitializing) { 338 throw new IllegalStateException("getDatabase called recursively"); 339 } 340 341 SQLiteDatabase db = mDatabase; 342 try { 343 mIsInitializing = true; 344 345 if (db != null) { 346 if (writable && db.isReadOnly()) { 347 db.reopenReadWrite(); 348 } 349 } else if (mName == null) { 350 db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build()); 351 } else { 352 final File filePath = mContext.getDatabasePath(mName); 353 SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build(); 354 try { 355 db = SQLiteDatabase.openDatabase(filePath, params); 356 // Keep pre-O-MR1 behavior by resetting file permissions to 660 357 setFilePermissionsForDb(filePath.getPath()); 358 } catch (SQLException ex) { 359 if (writable) { 360 throw ex; 361 } 362 Log.e(TAG, "Couldn't open " + mName 363 + " for writing (will try read-only):", ex); 364 params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build(); 365 db = SQLiteDatabase.openDatabase(filePath, params); 366 } 367 } 368 369 onConfigure(db); 370 371 final int version = db.getVersion(); 372 if (version != mNewVersion) { 373 if (db.isReadOnly()) { 374 throw new SQLiteException("Can't upgrade read-only database from version " + 375 db.getVersion() + " to " + mNewVersion + ": " + mName); 376 } 377 378 if (version > 0 && version < mMinimumSupportedVersion) { 379 File databaseFile = new File(db.getPath()); 380 onBeforeDelete(db); 381 db.close(); 382 if (SQLiteDatabase.deleteDatabase(databaseFile)) { 383 mIsInitializing = false; 384 return getDatabaseLocked(writable); 385 } else { 386 throw new IllegalStateException("Unable to delete obsolete database " 387 + mName + " with version " + version); 388 } 389 } else { 390 db.beginTransaction(); 391 try { 392 if (version == 0) { 393 onCreate(db); 394 } else { 395 if (version > mNewVersion) { 396 onDowngrade(db, version, mNewVersion); 397 } else { 398 onUpgrade(db, version, mNewVersion); 399 } 400 } 401 db.setVersion(mNewVersion); 402 db.setTransactionSuccessful(); 403 } finally { 404 db.endTransaction(); 405 } 406 } 407 } 408 409 onOpen(db); 410 411 if (db.isReadOnly()) { 412 Log.w(TAG, "Opened " + mName + " in read-only mode"); 413 } 414 415 mDatabase = db; 416 return db; 417 } finally { 418 mIsInitializing = false; 419 if (db != null && db != mDatabase) { 420 db.close(); 421 } 422 } 423 } 424 setFilePermissionsForDb(String dbPath)425 private static void setFilePermissionsForDb(String dbPath) { 426 int perms = FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP; 427 FileUtils.setPermissions(dbPath, perms, -1, -1); 428 } 429 430 /** 431 * Close any open database object. 432 */ close()433 public synchronized void close() { 434 if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); 435 436 if (mDatabase != null && mDatabase.isOpen()) { 437 mDatabase.close(); 438 mDatabase = null; 439 } 440 } 441 442 /** 443 * Called when the database connection is being configured, to enable features such as 444 * write-ahead logging or foreign key support. 445 * <p> 446 * This method is called before {@link #onCreate}, {@link #onUpgrade}, {@link #onDowngrade}, or 447 * {@link #onOpen} are called. It should not modify the database except to configure the 448 * database connection as required. 449 * </p> 450 * <p> 451 * This method should only call methods that configure the parameters of the database 452 * connection, such as {@link SQLiteDatabase#enableWriteAheadLogging} 453 * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, {@link SQLiteDatabase#setLocale}, 454 * {@link SQLiteDatabase#setMaximumSize}, or executing PRAGMA statements. 455 * </p> 456 * 457 * @param db The database. 458 */ onConfigure(SQLiteDatabase db)459 public void onConfigure(SQLiteDatabase db) {} 460 461 /** 462 * Called before the database is deleted when the version returned by 463 * {@link SQLiteDatabase#getVersion()} is lower than the minimum supported version passed (if at 464 * all) while creating this helper. After the database is deleted, a fresh database with the 465 * given version is created. This will be followed by {@link #onConfigure(SQLiteDatabase)} and 466 * {@link #onCreate(SQLiteDatabase)} being called with a new SQLiteDatabase object 467 * 468 * @param db the database opened with this helper 469 * @see #SQLiteOpenHelper(Context, String, CursorFactory, int, int, DatabaseErrorHandler) 470 * @hide 471 */ onBeforeDelete(SQLiteDatabase db)472 public void onBeforeDelete(SQLiteDatabase db) { 473 } 474 475 /** 476 * Called when the database is created for the first time. This is where the 477 * creation of tables and the initial population of the tables should happen. 478 * 479 * @param db The database. 480 */ onCreate(SQLiteDatabase db)481 public abstract void onCreate(SQLiteDatabase db); 482 483 /** 484 * Called when the database needs to be upgraded. The implementation 485 * should use this method to drop tables, add tables, or do anything else it 486 * needs to upgrade to the new schema version. 487 * 488 * <p> 489 * The SQLite ALTER TABLE documentation can be found 490 * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns 491 * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns 492 * you can use ALTER TABLE to rename the old table, then create the new table and then 493 * populate the new table with the contents of the old table. 494 * </p><p> 495 * This method executes within a transaction. If an exception is thrown, all changes 496 * will automatically be rolled back. 497 * </p> 498 * 499 * @param db The database. 500 * @param oldVersion The old database version. 501 * @param newVersion The new database version. 502 */ onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)503 public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); 504 505 /** 506 * Called when the database needs to be downgraded. This is strictly similar to 507 * {@link #onUpgrade} method, but is called whenever current version is newer than requested one. 508 * However, this method is not abstract, so it is not mandatory for a customer to 509 * implement it. If not overridden, default implementation will reject downgrade and 510 * throws SQLiteException 511 * 512 * <p> 513 * This method executes within a transaction. If an exception is thrown, all changes 514 * will automatically be rolled back. 515 * </p> 516 * 517 * @param db The database. 518 * @param oldVersion The old database version. 519 * @param newVersion The new database version. 520 */ onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)521 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 522 throw new SQLiteException("Can't downgrade database from version " + 523 oldVersion + " to " + newVersion); 524 } 525 526 /** 527 * Called when the database has been opened. The implementation 528 * should check {@link SQLiteDatabase#isReadOnly} before updating the 529 * database. 530 * <p> 531 * This method is called after the database connection has been configured 532 * and after the database schema has been created, upgraded or downgraded as necessary. 533 * If the database connection must be configured in some way before the schema 534 * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead. 535 * </p> 536 * 537 * @param db The database. 538 */ onOpen(SQLiteDatabase db)539 public void onOpen(SQLiteDatabase db) {} 540 } 541