• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
20 import android.database.sqlite.SQLiteDatabase.CursorFactory;
21 import 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  */
32 public 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      */
SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version)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      */
getWritableDatabase()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      */
getReadableDatabase()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      */
close()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      */
onCreate(SQLiteDatabase db)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      */
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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      */
onOpen(SQLiteDatabase db)228     public void onOpen(SQLiteDatabase db) {}
229 }
230