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 17package android.database.sqlite; 18 19import android.content.Context; 20import android.database.sqlite.SQLiteDatabase.CursorFactory; 21import android.util.Log; 22 23/** 24 * A helper class to manage database creation and version management. 25 * You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and 26 * optionally {@link #onOpen}, and this class takes care of opening the database 27 * if it exists, creating it if it does not, and upgrading it as necessary. 28 * Transactions are used to make sure the database is always in a sensible state. 29 * <p>For an example, see the NotePadProvider class in the NotePad sample application, 30 * in the <em>samples/</em> directory of the SDK.</p> 31 */ 32public abstract class SQLiteOpenHelper { 33 private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); 34 35 private final Context mContext; 36 private final String mName; 37 private final CursorFactory mFactory; 38 private final int mNewVersion; 39 40 private SQLiteDatabase mDatabase = null; 41 private boolean mIsInitializing = false; 42 43 /** 44 * Create a helper object to create, open, and/or manage a database. 45 * The database is not actually created or opened until one of 46 * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. 47 * 48 * @param context to use to open or create the database 49 * @param name of the database file, or null for an in-memory database 50 * @param factory to use for creating cursor objects, or null for the default 51 * @param version number of the database (starting at 1); if the database is older, 52 * {@link #onUpgrade} will be used to upgrade the database 53 */ 54 public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { 55 if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); 56 57 mContext = context; 58 mName = name; 59 mFactory = factory; 60 mNewVersion = version; 61 } 62 63 /** 64 * Create and/or open a database that will be used for reading and writing. 65 * Once opened successfully, the database is cached, so you can call this 66 * method every time you need to write to the database. Make sure to call 67 * {@link #close} when you no longer need it. 68 * 69 * <p>Errors such as bad permissions or a full disk may cause this operation 70 * to fail, but future attempts may succeed if the problem is fixed.</p> 71 * 72 * @throws SQLiteException if the database cannot be opened for writing 73 * @return a read/write database object valid until {@link #close} is called 74 */ 75 public synchronized SQLiteDatabase getWritableDatabase() { 76 if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { 77 return mDatabase; // The database is already open for business 78 } 79 80 if (mIsInitializing) { 81 throw new IllegalStateException("getWritableDatabase called recursively"); 82 } 83 84 // If we have a read-only database open, someone could be using it 85 // (though they shouldn't), which would cause a lock to be held on 86 // the file, and our attempts to open the database read-write would 87 // fail waiting for the file lock. To prevent that, we acquire the 88 // lock on the read-only database, which shuts out other users. 89 90 boolean success = false; 91 SQLiteDatabase db = null; 92 if (mDatabase != null) mDatabase.lock(); 93 try { 94 mIsInitializing = true; 95 if (mName == null) { 96 db = SQLiteDatabase.create(null); 97 } else { 98 db = mContext.openOrCreateDatabase(mName, 0, mFactory); 99 } 100 101 int version = db.getVersion(); 102 if (version != mNewVersion) { 103 db.beginTransaction(); 104 try { 105 if (version == 0) { 106 onCreate(db); 107 } else { 108 onUpgrade(db, version, mNewVersion); 109 } 110 db.setVersion(mNewVersion); 111 db.setTransactionSuccessful(); 112 } finally { 113 db.endTransaction(); 114 } 115 } 116 117 onOpen(db); 118 success = true; 119 return db; 120 } finally { 121 mIsInitializing = false; 122 if (success) { 123 if (mDatabase != null) { 124 try { mDatabase.close(); } catch (Exception e) { } 125 mDatabase.unlock(); 126 } 127 mDatabase = db; 128 } else { 129 if (mDatabase != null) mDatabase.unlock(); 130 if (db != null) db.close(); 131 } 132 } 133 } 134 135 /** 136 * Create and/or open a database. This will be the same object returned by 137 * {@link #getWritableDatabase} unless some problem, such as a full disk, 138 * requires the database to be opened read-only. In that case, a read-only 139 * database object will be returned. If the problem is fixed, a future call 140 * to {@link #getWritableDatabase} may succeed, in which case the read-only 141 * database object will be closed and the read/write object will be returned 142 * in the future. 143 * 144 * @throws SQLiteException if the database cannot be opened 145 * @return a database object valid until {@link #getWritableDatabase} 146 * or {@link #close} is called. 147 */ 148 public synchronized SQLiteDatabase getReadableDatabase() { 149 if (mDatabase != null && mDatabase.isOpen()) { 150 return mDatabase; // The database is already open for business 151 } 152 153 if (mIsInitializing) { 154 throw new IllegalStateException("getReadableDatabase called recursively"); 155 } 156 157 try { 158 return getWritableDatabase(); 159 } catch (SQLiteException e) { 160 if (mName == null) throw e; // Can't open a temp database read-only! 161 Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e); 162 } 163 164 SQLiteDatabase db = null; 165 try { 166 mIsInitializing = true; 167 String path = mContext.getDatabasePath(mName).getPath(); 168 db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY); 169 if (db.getVersion() != mNewVersion) { 170 throw new SQLiteException("Can't upgrade read-only database from version " + 171 db.getVersion() + " to " + mNewVersion + ": " + path); 172 } 173 174 onOpen(db); 175 Log.w(TAG, "Opened " + mName + " in read-only mode"); 176 mDatabase = db; 177 return mDatabase; 178 } finally { 179 mIsInitializing = false; 180 if (db != null && db != mDatabase) db.close(); 181 } 182 } 183 184 /** 185 * Close any open database object. 186 */ 187 public synchronized void close() { 188 if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); 189 190 if (mDatabase != null && mDatabase.isOpen()) { 191 mDatabase.close(); 192 mDatabase = null; 193 } 194 } 195 196 /** 197 * Called when the database is created for the first time. This is where the 198 * creation of tables and the initial population of the tables should happen. 199 * 200 * @param db The database. 201 */ 202 public abstract void onCreate(SQLiteDatabase db); 203 204 /** 205 * Called when the database needs to be upgraded. The implementation 206 * should use this method to drop tables, add tables, or do anything else it 207 * needs to upgrade to the new schema version. 208 * 209 * <p>The SQLite ALTER TABLE documentation can be found 210 * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns 211 * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns 212 * you can use ALTER TABLE to rename the old table, then create the new table and then 213 * populate the new table with the contents of the old table. 214 * 215 * @param db The database. 216 * @param oldVersion The old database version. 217 * @param newVersion The new database version. 218 */ 219 public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); 220 221 /** 222 * Called when the database has been opened. 223 * Override method should check {@link SQLiteDatabase#isReadOnly} before 224 * updating the database. 225 * 226 * @param db The database. 227 */ 228 public void onOpen(SQLiteDatabase db) {} 229} 230