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