Rewrite SQLite database wrappers.

The main theme of this change is encapsulation.  This change
preserves all existing functionality but the implementation
is now much cleaner.

Instead of a "database lock", access to the database is treated
as a resource acquisition problem.  If a thread's owns a database
connection, then it can access the database; otherwise, it must
acquire a database connection first, and potentially wait for other
threads to give up theirs.  The SQLiteConnectionPool encapsulates
the details of how connections are created, configured, acquired,
released and disposed.

One new feature is that SQLiteConnectionPool can make scheduling
decisions about which thread should next acquire a database
connection when there is contention among threads.  The factors
considered include wait queue ordering (fairness among peers),
whether the connection is needed for an interactive operation
(unfairness on behalf of the UI), and whether the primary connection
is needed or if any old connection will do.  Thus one goal of the
new SQLiteConnectionPool is to improve the utilization of
database connections.

To emulate some quirks of the old "database lock," we introduce
the concept of the primary database connection.  The primary
database connection is the one that is typically used to perform
write operations to the database.  When a thread holds the primary
database connection, it effectively prevents other threads from
modifying the database (although they can still read).  What's
more, those threads will block when they try to acquire the primary
connection, which provides the same kind of mutual exclusion
features that the old "database lock" had.  (In truth, we
probably don't need to be requiring use of the primary database
connection in as many places as we do now, but we can seek to refine
that behavior in future patches.)

Another significant change is that native sqlite3_stmt objects
(prepared statements) are fully encapsulated by the SQLiteConnection
object that owns them.  This ensures that the connection can
finalize (destroy) all extant statements that belong to a database
connection when the connection is closed.  (In the original code,
this was very complicated because the sqlite3_stmt objects were
managed by SQLiteCompiledSql objects which had different lifetime
from the original SQLiteDatabase that created them.  Worse, the
SQLiteCompiledSql finalizer method couldn't actually destroy the
sqlite3_stmt objects because it ran on the finalizer thread and
therefore could not guarantee that it could acquire the database
lock in order to do the work.  This resulted in some rather
tortured logic involving a list of pending finalizable statements
and a high change of deadlocks or leaks.)

Because sqlite3_stmt objects never escape the confines of the
SQLiteConnection that owns them, we can also greatly simplify
the design of the SQLiteProgram, SQLiteQuery and SQLiteStatement
objects.  They no longer have to wrangle a native sqlite3_stmt
object pointer and manage its lifecycle.  So now all they do
is hold bind arguments and provide a fancy API.

All of the JNI glue related to managing database connections
and performing transactions is now bound to SQLiteConnection
(rather than being scattered everywhere).  This makes sense because
SQLiteConnection owns the native sqlite3 object, so it is the
only class in the system that can interact with the native
SQLite database directly.  Encapsulation for the win.

One particularly tricky part of this change is managing the
ownership of SQLiteConnection objects.  At any given time,
a SQLiteConnection is either owned by a SQLiteConnectionPool
or by a SQLiteSession.  SQLiteConnections should never be leaked,
but we handle that case too (and yell about it with CloseGuard).

A SQLiteSession object is responsible for acquiring and releasing
a SQLiteConnection object on behalf of a single thread as needed.
For example, the session acquires a connection when a transaction
begins and releases it when finished.  If the session cannot
acquire a connection immediately, then the requested operation
blocks until a connection becomes available.

SQLiteSessions are thread-local.  A SQLiteDatabase assigns a
distinct session to each thread that performs database operations.
This is very very important.  First, it prevents two threads
from trying to use the same SQLiteConnection at the same time
(because two threads can't share the same session).
Second, it prevents a single thread from trying to acquire two
SQLiteConnections simultaneously from the same database (because
a single thread can't have two sessions for the same database which,
in addition to being greedy, could result in a deadlock).

There is strict layering between the various database objects,
objects at lower layers are not aware of objects at higher layers.
Moreover, objects at higher layers generally own objects at lower
layers and are responsible for ensuring they are properly disposed
when no longer needed (good for the environment).

API layer: SQLiteDatabase, SQLiteProgram, SQLiteQuery, SQLiteStatement.
Session layer: SQLiteSession.
Connection layer: SQLiteConnectionPool, SQLiteConnection.
Native layer: JNI glue.

By avoiding cyclic dependencies between layers, we make the
architecture much more intelligible, maintainable and robust.

Finally, this change adds a great deal of new debugging information.
It is now possible to view a list of the most recent database
operations including how long they took to run using
"adb shell dumpsys dbinfo".  (Because most of the interesting
work happens in SQLiteConnection, it is easy to add debugging
instrumentation to track all database operations in one place.)

Change-Id: Iffb4ce72d8bcf20b4e087d911da6aa84d2f15297
diff --git a/core/java/android/database/sqlite/DatabaseConnectionPool.java b/core/java/android/database/sqlite/DatabaseConnectionPool.java
deleted file mode 100644
index 39a9d23..0000000
--- a/core/java/android/database/sqlite/DatabaseConnectionPool.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 20010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.res.Resources;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Random;
-
-/**
- * A connection pool to be used by readers.
- * Note that each connection can be used by only one reader at a time.
- */
-/* package */ class DatabaseConnectionPool {
-
-    private static final String TAG = "DatabaseConnectionPool";
-
-    /** The default connection pool size. */
-    private volatile int mMaxPoolSize =
-        Resources.getSystem().getInteger(com.android.internal.R.integer.db_connection_pool_size);
-
-    /** The connection pool objects are stored in this member.
-     * TODO: revisit this data struct as the number of pooled connections increase beyond
-     * single-digit values.
-     */
-    private final ArrayList<PoolObj> mPool = new ArrayList<PoolObj>(mMaxPoolSize);
-
-    /** the main database connection to which this connection pool is attached */
-    private final SQLiteDatabase mParentDbObj;
-
-    /** Random number generator used to pick a free connection out of the pool */
-    private Random rand; // lazily initialized
-
-    /* package */ DatabaseConnectionPool(SQLiteDatabase db) {
-        this.mParentDbObj = db;
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Max Pool Size: " + mMaxPoolSize);
-        }
-    }
-
-    /**
-     * close all database connections in the pool - even if they are in use!
-     */
-    /* package */ synchronized void close() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "Closing the connection pool on " + mParentDbObj.getPath() + toString());
-        }
-        for (int i = mPool.size() - 1; i >= 0; i--) {
-            mPool.get(i).mDb.close();
-        }
-        mPool.clear();
-    }
-
-    /**
-     * get a free connection from the pool
-     *
-     * @param sql if not null, try to find a connection inthe pool which already has cached
-     * the compiled statement for this sql.
-     * @return the Database connection that the caller can use
-     */
-    /* package */ synchronized SQLiteDatabase get(String sql) {
-        SQLiteDatabase db = null;
-        PoolObj poolObj = null;
-        int poolSize = mPool.size();
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            assert sql != null;
-            doAsserts();
-        }
-        if (getFreePoolSize() == 0) {
-            // no free ( = available) connections
-            if (mMaxPoolSize == poolSize) {
-                // maxed out. can't open any more connections.
-                // let the caller wait on one of the pooled connections
-                // preferably a connection caching the pre-compiled statement of the given SQL
-                if (mMaxPoolSize == 1) {
-                    poolObj = mPool.get(0);
-                } else {
-                    for (int i = 0; i < mMaxPoolSize; i++) {
-                        if (mPool.get(i).mDb.isInStatementCache(sql)) {
-                            poolObj = mPool.get(i);
-                            break;
-                        }
-                    }
-                    if (poolObj == null) {
-                        // there are no database connections with the given SQL pre-compiled.
-                        // ok to return any of the connections.
-                        if (rand == null) {
-                            rand = new Random(SystemClock.elapsedRealtime());
-                        }
-                        poolObj = mPool.get(rand.nextInt(mMaxPoolSize));
-                    }
-                }
-                db = poolObj.mDb;
-            } else {
-                // create a new connection and add it to the pool, since we haven't reached
-                // max pool size allowed
-                db = mParentDbObj.createPoolConnection((short)(poolSize + 1));
-                poolObj = new PoolObj(db);
-                mPool.add(poolSize, poolObj);
-            }
-        } else {
-            // there are free connections available. pick one
-            // preferably a connection caching the pre-compiled statement of the given SQL
-            for (int i = 0; i < poolSize; i++) {
-                if (mPool.get(i).isFree() && mPool.get(i).mDb.isInStatementCache(sql)) {
-                    poolObj = mPool.get(i);
-                    break;
-                }
-            }
-            if (poolObj == null) {
-                // didn't find a free database connection with the given SQL already
-                // pre-compiled. return a free connection (this means, the same SQL could be
-                // pre-compiled on more than one database connection. potential wasted memory.)
-                for (int i = 0; i < poolSize; i++) {
-                    if (mPool.get(i).isFree()) {
-                        poolObj = mPool.get(i);
-                        break;
-                    }
-                }
-            }
-            db = poolObj.mDb;
-        }
-
-        assert poolObj != null;
-        assert poolObj.mDb == db;
-
-        poolObj.acquire();
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "END get-connection: " + toString() + poolObj.toString());
-        }
-        return db;
-        // TODO if a thread acquires a connection and dies without releasing the connection, then
-        // there could be a connection leak.
-    }
-
-    /**
-     * release the given database connection back to the pool.
-     * @param db the connection to be released
-     */
-    /* package */ synchronized void release(SQLiteDatabase db) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            assert db.mConnectionNum > 0;
-            doAsserts();
-            assert mPool.get(db.mConnectionNum - 1).mDb == db;
-        }
-
-        PoolObj poolObj = mPool.get(db.mConnectionNum - 1);
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "BEGIN release-conn: " + toString() + poolObj.toString());
-        }
-
-        if (poolObj.isFree()) {
-            throw new IllegalStateException("Releasing object already freed: " +
-                    db.mConnectionNum);
-        }
-
-        poolObj.release();
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "END release-conn: " + toString() + poolObj.toString());
-        }
-    }
-
-    /**
-     * Returns a list of all database connections in the pool (both free and busy connections).
-     * This method is used when "adb bugreport" is done.
-     */
-    /* package */ synchronized ArrayList<SQLiteDatabase> getConnectionList() {
-        ArrayList<SQLiteDatabase> list = new ArrayList<SQLiteDatabase>();
-        for (int i = mPool.size() - 1; i >= 0; i--) {
-            list.add(mPool.get(i).mDb);
-        }
-        return list;
-    }
-
-    /**
-     * package level access for testing purposes only. otherwise, private should be sufficient.
-     */
-    /* package */ int getFreePoolSize() {
-        int count = 0;
-        for (int i = mPool.size() - 1; i >= 0; i--) {
-            if (mPool.get(i).isFree()) {
-                count++;
-            }
-        }
-        return count++;
-    }
-
-    /**
-     * only for testing purposes
-     */
-    /* package */ ArrayList<PoolObj> getPool() {
-        return mPool;
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder buff = new StringBuilder();
-        buff.append("db: ");
-        buff.append(mParentDbObj.getPath());
-        buff.append(", totalsize = ");
-        buff.append(mPool.size());
-        buff.append(", #free = ");
-        buff.append(getFreePoolSize());
-        buff.append(", maxpoolsize = ");
-        buff.append(mMaxPoolSize);
-        for (PoolObj p : mPool) {
-            buff.append("\n");
-            buff.append(p.toString());
-        }
-        return buff.toString();
-    }
-
-    private void doAsserts() {
-        for (int i = 0; i < mPool.size(); i++) {
-            mPool.get(i).verify();
-            assert mPool.get(i).mDb.mConnectionNum == (i + 1);
-        }
-    }
-
-    /** only used for testing purposes. */
-    /* package */ synchronized void setMaxPoolSize(int size) {
-        mMaxPoolSize = size;
-    }
-
-    /** only used for testing purposes. */
-    /* package */ synchronized int getMaxPoolSize() {
-        return mMaxPoolSize;
-    }
-
-    /** only used for testing purposes. */
-    /* package */ boolean isDatabaseObjFree(SQLiteDatabase db) {
-        return mPool.get(db.mConnectionNum - 1).isFree();
-    }
-
-    /** only used for testing purposes. */
-    /* package */ int getSize() {
-        return mPool.size();
-    }
-
-    /**
-     * represents objects in the connection pool.
-     * package-level access for testing purposes only.
-     */
-    /* package */ static class PoolObj {
-
-        private final SQLiteDatabase mDb;
-        private boolean mFreeBusyFlag = FREE;
-        private static final boolean FREE = true;
-        private static final boolean BUSY = false;
-
-        /** the number of threads holding this connection */
-        // @GuardedBy("this")
-        private int mNumHolders = 0;
-
-        /** contains the threadIds of the threads holding this connection.
-         * used for debugging purposes only.
-         */
-        // @GuardedBy("this")
-        private HashSet<Long> mHolderIds = new HashSet<Long>();
-
-        public PoolObj(SQLiteDatabase db) {
-            mDb = db;
-        }
-
-        private synchronized void acquire() {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                assert isFree();
-                long id = Thread.currentThread().getId();
-                assert !mHolderIds.contains(id);
-                mHolderIds.add(id);
-            }
-
-            mNumHolders++;
-            mFreeBusyFlag = BUSY;
-        }
-
-        private synchronized void release() {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                long id = Thread.currentThread().getId();
-                assert mHolderIds.size() == mNumHolders;
-                assert mHolderIds.contains(id);
-                mHolderIds.remove(id);
-            }
-
-            mNumHolders--;
-            if (mNumHolders == 0) {
-                mFreeBusyFlag = FREE;
-            }
-        }
-
-        private synchronized boolean isFree() {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                verify();
-            }
-            return (mFreeBusyFlag == FREE);
-        }
-
-        private synchronized void verify() {
-            if (mFreeBusyFlag == FREE) {
-                assert mNumHolders == 0;
-            } else {
-                assert mNumHolders > 0;
-            }
-        }
-
-        /**
-         * only for testing purposes
-         */
-        /* package */ synchronized int getNumHolders() {
-            return mNumHolders;
-        }
-
-        @Override
-        public String toString() {
-            StringBuilder buff = new StringBuilder();
-            buff.append(", conn # ");
-            buff.append(mDb.mConnectionNum);
-            buff.append(", mCountHolders = ");
-            synchronized(this) {
-                buff.append(mNumHolders);
-                buff.append(", freeBusyFlag = ");
-                buff.append(mFreeBusyFlag);
-                for (Long l : mHolderIds) {
-                    buff.append(", id = " + l);
-                }
-            }
-            return buff.toString();
-        }
-    }
-}
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index 01e9fb3..7e91a7b 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -16,8 +16,6 @@
 
 package android.database.sqlite;
 
-import android.database.CursorWindow;
-
 /**
  * An object created from a SQLiteDatabase that can be closed.
  */
@@ -31,7 +29,7 @@
         synchronized(this) {
             if (mReferenceCount <= 0) {
                 throw new IllegalStateException(
-                        "attempt to re-open an already-closed object: " + getObjInfo());
+                        "attempt to re-open an already-closed object: " + this);
             }
             mReferenceCount++;
         }
@@ -56,22 +54,4 @@
             onAllReferencesReleasedFromContainer();
         }
     }
-
-    private String getObjInfo() {
-        StringBuilder buff = new StringBuilder();
-        buff.append(this.getClass().getName());
-        buff.append(" (");
-        if (this instanceof SQLiteDatabase) {
-            buff.append("database = ");
-            buff.append(((SQLiteDatabase)this).getPath());
-        } else if (this instanceof SQLiteProgram) {
-            buff.append("mSql = ");
-            buff.append(((SQLiteProgram)this).mSql);
-        } else if (this instanceof CursorWindow) {
-            buff.append("mStartPos = ");
-            buff.append(((CursorWindow)this).getStartPosition());
-        }
-        buff.append(") ");
-        return buff.toString();
-    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
deleted file mode 100644
index dafbc79..0000000
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.os.StrictMode;
-import android.util.Log;
-
-/**
- * This class encapsulates compilation of sql statement and release of the compiled statement obj.
- * Once a sql statement is compiled, it is cached in {@link SQLiteDatabase}
- * and it is released in one of the 2 following ways
- * 1. when {@link SQLiteDatabase} object is closed.
- * 2. if this is not cached in {@link SQLiteDatabase}, {@link android.database.Cursor#close()}
- * releaases this obj.
- */
-/* package */ class SQLiteCompiledSql {
-
-    private static final String TAG = "SQLiteCompiledSql";
-
-    /** The database this program is compiled against. */
-    /* package */ final SQLiteDatabase mDatabase;
-
-    /**
-     * Native linkage, do not modify. This comes from the database.
-     */
-    /* package */ final int nHandle;
-
-    /**
-     * Native linkage, do not modify. When non-0 this holds a reference to a valid
-     * sqlite3_statement object. It is only updated by the native code, but may be
-     * checked in this class when the database lock is held to determine if there
-     * is a valid native-side program or not.
-     */
-    /* package */ int nStatement = 0;
-
-    /** the following are for debugging purposes */
-    private String mSqlStmt = null;
-    private final Throwable mStackTrace;
-
-    /** when in cache and is in use, this member is set */
-    private boolean mInUse = false;
-
-    /* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
-        db.verifyDbIsOpen();
-        db.verifyLockOwner();
-        mDatabase = db;
-        mSqlStmt = sql;
-        if (StrictMode.vmSqliteObjectLeaksEnabled()) {
-            mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
-        } else {
-            mStackTrace = null;
-        }
-        nHandle = db.mNativeHandle;
-        native_compile(sql);
-    }
-
-    /* package */ void releaseSqlStatement() {
-        // Note that native_finalize() checks to make sure that nStatement is
-        // non-null before destroying it.
-        if (nStatement != 0) {
-            mDatabase.finalizeStatementLater(nStatement);
-            nStatement = 0;
-        }
-    }
-
-    /**
-     * returns true if acquire() succeeds. false otherwise.
-     */
-    /* package */ synchronized boolean acquire() {
-        if (mInUse) {
-            // it is already in use.
-            return false;
-        }
-        mInUse = true;
-        return true;
-    }
-
-    /* package */ synchronized void release() {
-        mInUse = false;
-    }
-
-    /* package */ synchronized void releaseIfNotInUse() {
-        // if it is not in use, release its memory from the database
-        if (!mInUse) {
-            releaseSqlStatement();
-        }
-    }
-
-    /**
-     * Make sure that the native resource is cleaned up.
-     */
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (nStatement == 0) return;
-            // don't worry about finalizing this object if it is ALREADY in the
-            // queue of statements to be finalized later
-            if (mDatabase.isInQueueOfStatementsToBeFinalized(nStatement)) {
-                return;
-            }
-            // finalizer should NEVER get called
-            // but if the database itself is not closed and is GC'ed, then
-            // all sub-objects attached to the database could end up getting GC'ed too.
-            // in that case, don't print any warning.
-            if (mInUse && mStackTrace != null) {
-                int len = mSqlStmt.length();
-                StrictMode.onSqliteObjectLeaked(
-                    "Releasing statement in a finalizer. Please ensure " +
-                    "that you explicitly call close() on your cursor: " +
-                    mSqlStmt.substring(0, (len > 1000) ? 1000 : len),
-                    mStackTrace);
-            }
-            releaseSqlStatement();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    @Override public String toString() {
-        synchronized(this) {
-            StringBuilder buff = new StringBuilder();
-            buff.append(" nStatement=");
-            buff.append(nStatement);
-            buff.append(", mInUse=");
-            buff.append(mInUse);
-            buff.append(", db=");
-            buff.append(mDatabase.getPath());
-            buff.append(", db_connectionNum=");
-            buff.append(mDatabase.mConnectionNum);
-            buff.append(", sql=");
-            int len = mSqlStmt.length();
-            buff.append(mSqlStmt.substring(0, (len > 100) ? 100 : len));
-            return buff.toString();
-        }
-    }
-
-    /**
-     * Compiles SQL into a SQLite program.
-     *
-     * <P>The database lock must be held when calling this method.
-     * @param sql The SQL to compile.
-     */
-    private final native void native_compile(String sql);
-}
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
new file mode 100644
index 0000000..e45d66d
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -0,0 +1,1149 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import dalvik.system.BlockGuard;
+import dalvik.system.CloseGuard;
+
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDebug.DbStats;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.util.LruCache;
+import android.util.Printer;
+
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a SQLite database connection.
+ * Each connection wraps an instance of a native <code>sqlite3</code> object.
+ * <p>
+ * When database connection pooling is enabled, there can be multiple active
+ * connections to the same database.  Otherwise there is typically only one
+ * connection per database.
+ * </p><p>
+ * When the SQLite WAL feature is enabled, multiple readers and one writer
+ * can concurrently access the database.  Without WAL, readers and writers
+ * are mutually exclusive.
+ * </p>
+ *
+ * <h2>Ownership and concurrency guarantees</h2>
+ * <p>
+ * Connection objects are not thread-safe.  They are acquired as needed to
+ * perform a database operation and are then returned to the pool.  At any
+ * given time, a connection is either owned and used by a {@link SQLiteSession}
+ * object or the {@link SQLiteConnectionPool}.  Those classes are
+ * responsible for serializing operations to guard against concurrent
+ * use of a connection.
+ * </p><p>
+ * The guarantee of having a single owner allows this class to be implemented
+ * without locks and greatly simplifies resource management.
+ * </p>
+ *
+ * <h2>Encapsulation guarantees</h2>
+ * <p>
+ * The connection object object owns *all* of the SQLite related native
+ * objects that are associated with the connection.  What's more, there are
+ * no other objects in the system that are capable of obtaining handles to
+ * those native objects.  Consequently, when the connection is closed, we do
+ * not have to worry about what other components might have references to
+ * its associated SQLite state -- there are none.
+ * </p><p>
+ * Encapsulation is what ensures that the connection object's
+ * lifecycle does not become a tortured mess of finalizers and reference
+ * queues.
+ * </p>
+ *
+ * @hide
+ */
+public final class SQLiteConnection {
+    private static final String TAG = "SQLiteConnection";
+
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    private static final Pattern TRIM_SQL_PATTERN = Pattern.compile("[\\s]*\\n+[\\s]*");
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private final SQLiteConnectionPool mPool;
+    private final SQLiteDatabaseConfiguration mConfiguration;
+    private final int mConnectionId;
+    private final boolean mIsPrimaryConnection;
+    private final PreparedStatementCache mPreparedStatementCache;
+    private PreparedStatement mPreparedStatementPool;
+
+    // The recent operations log.
+    private final OperationLog mRecentOperations = new OperationLog();
+
+    // The native SQLiteConnection pointer.  (FOR INTERNAL USE ONLY)
+    private int mConnectionPtr;
+
+    private boolean mOnlyAllowReadOnlyOperations;
+
+    private static native int nativeOpen(String path, int openFlags, String label,
+            boolean enableTrace, boolean enableProfile);
+    private static native void nativeClose(int connectionPtr);
+    private static native void nativeRegisterCustomFunction(int connectionPtr,
+            SQLiteCustomFunction function);
+    private static native void nativeSetLocale(int connectionPtr, String locale);
+    private static native int nativePrepareStatement(int connectionPtr, String sql);
+    private static native void nativeFinalizeStatement(int connectionPtr, int statementPtr);
+    private static native int nativeGetParameterCount(int connectionPtr, int statementPtr);
+    private static native boolean nativeIsReadOnly(int connectionPtr, int statementPtr);
+    private static native int nativeGetColumnCount(int connectionPtr, int statementPtr);
+    private static native String nativeGetColumnName(int connectionPtr, int statementPtr,
+            int index);
+    private static native void nativeBindNull(int connectionPtr, int statementPtr,
+            int index);
+    private static native void nativeBindLong(int connectionPtr, int statementPtr,
+            int index, long value);
+    private static native void nativeBindDouble(int connectionPtr, int statementPtr,
+            int index, double value);
+    private static native void nativeBindString(int connectionPtr, int statementPtr,
+            int index, String value);
+    private static native void nativeBindBlob(int connectionPtr, int statementPtr,
+            int index, byte[] value);
+    private static native void nativeResetStatementAndClearBindings(
+            int connectionPtr, int statementPtr);
+    private static native void nativeExecute(int connectionPtr, int statementPtr);
+    private static native long nativeExecuteForLong(int connectionPtr, int statementPtr);
+    private static native String nativeExecuteForString(int connectionPtr, int statementPtr);
+    private static native int nativeExecuteForBlobFileDescriptor(
+            int connectionPtr, int statementPtr);
+    private static native int nativeExecuteForChangedRowCount(int connectionPtr, int statementPtr);
+    private static native long nativeExecuteForLastInsertedRowId(
+            int connectionPtr, int statementPtr);
+    private static native long nativeExecuteForCursorWindow(
+            int connectionPtr, int statementPtr, int windowPtr,
+            int startPos, int requiredPos, boolean countAllRows);
+    private static native int nativeGetDbLookaside(int connectionPtr);
+
+    private SQLiteConnection(SQLiteConnectionPool pool,
+            SQLiteDatabaseConfiguration configuration,
+            int connectionId, boolean primaryConnection) {
+        mPool = pool;
+        mConfiguration = new SQLiteDatabaseConfiguration(configuration);
+        mConnectionId = connectionId;
+        mIsPrimaryConnection = primaryConnection;
+        mPreparedStatementCache = new PreparedStatementCache(
+                mConfiguration.maxSqlCacheSize);
+        mCloseGuard.open("close");
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mPool != null && mConnectionPtr != 0) {
+                mPool.onConnectionLeaked();
+            }
+
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    // Called by SQLiteConnectionPool only.
+    static SQLiteConnection open(SQLiteConnectionPool pool,
+            SQLiteDatabaseConfiguration configuration,
+            int connectionId, boolean primaryConnection) {
+        SQLiteConnection connection = new SQLiteConnection(pool, configuration,
+                connectionId, primaryConnection);
+        try {
+            connection.open();
+            return connection;
+        } catch (SQLiteException ex) {
+            connection.dispose(false);
+            throw ex;
+        }
+    }
+
+    // Called by SQLiteConnectionPool only.
+    // Closes the database closes and releases all of its associated resources.
+    // Do not call methods on the connection after it is closed.  It will probably crash.
+    void close() {
+        dispose(false);
+    }
+
+    private void open() {
+        SQLiteGlobal.initializeOnce();
+
+        mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
+                mConfiguration.label,
+                SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME);
+
+        setLocaleFromConfiguration();
+    }
+
+    private void dispose(boolean finalized) {
+        if (mCloseGuard != null) {
+            if (finalized) {
+                mCloseGuard.warnIfOpen();
+            }
+            mCloseGuard.close();
+        }
+
+        if (mConnectionPtr != 0) {
+            mRecentOperations.beginOperation("close", null, null);
+            try {
+                mPreparedStatementCache.evictAll();
+                nativeClose(mConnectionPtr);
+                mConnectionPtr = 0;
+            } finally {
+                mRecentOperations.endOperation();
+            }
+        }
+    }
+
+    private void setLocaleFromConfiguration() {
+        nativeSetLocale(mConnectionPtr, mConfiguration.locale.toString());
+    }
+
+    // Called by SQLiteConnectionPool only.
+    void reconfigure(SQLiteDatabaseConfiguration configuration) {
+        // Register custom functions.
+        final int functionCount = configuration.customFunctions.size();
+        for (int i = 0; i < functionCount; i++) {
+            SQLiteCustomFunction function = configuration.customFunctions.get(i);
+            if (!mConfiguration.customFunctions.contains(function)) {
+                nativeRegisterCustomFunction(mConnectionPtr, function);
+            }
+        }
+
+        // Remember whether locale has changed.
+        boolean localeChanged = !configuration.locale.equals(mConfiguration.locale);
+
+        // Update configuration parameters.
+        mConfiguration.updateParametersFrom(configuration);
+
+        // Update prepared statement cache size.
+        mPreparedStatementCache.resize(configuration.maxSqlCacheSize);
+
+        // Update locale.
+        if (localeChanged) {
+            setLocaleFromConfiguration();
+        }
+    }
+
+    // Called by SQLiteConnectionPool only.
+    // When set to true, executing write operations will throw SQLiteException.
+    // Preparing statements that might write is ok, just don't execute them.
+    void setOnlyAllowReadOnlyOperations(boolean readOnly) {
+        mOnlyAllowReadOnlyOperations = readOnly;
+    }
+
+    // Called by SQLiteConnectionPool only.
+    // Returns true if the prepared statement cache contains the specified SQL.
+    boolean isPreparedStatementInCache(String sql) {
+        return mPreparedStatementCache.get(sql) != null;
+    }
+
+    /**
+     * Gets the unique id of this connection.
+     * @return The connection id.
+     */
+    public int getConnectionId() {
+        return mConnectionId;
+    }
+
+    /**
+     * Returns true if this is the primary database connection.
+     * @return True if this is the primary database connection.
+     */
+    public boolean isPrimaryConnection() {
+        return mIsPrimaryConnection;
+    }
+
+    /**
+     * Prepares a statement for execution but does not bind its parameters or execute it.
+     * <p>
+     * This method can be used to check for syntax errors during compilation
+     * prior to execution of the statement.  If the {@code outStatementInfo} argument
+     * is not null, the provided {@link SQLiteStatementInfo} object is populated
+     * with information about the statement.
+     * </p><p>
+     * A prepared statement makes no reference to the arguments that may eventually
+     * be bound to it, consequently it it possible to cache certain prepared statements
+     * such as SELECT or INSERT/UPDATE statements.  If the statement is cacheable,
+     * then it will be stored in the cache for later.
+     * </p><p>
+     * To take advantage of this behavior as an optimization, the connection pool
+     * provides a method to acquire a connection that already has a given SQL statement
+     * in its prepared statement cache so that it is ready for execution.
+     * </p>
+     *
+     * @param sql The SQL statement to prepare.
+     * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
+     * with information about the statement, or null if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error.
+     */
+    public void prepare(String sql, SQLiteStatementInfo outStatementInfo) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        mRecentOperations.beginOperation("prepare", sql, null);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                if (outStatementInfo != null) {
+                    outStatementInfo.numParameters = statement.mNumParameters;
+                    outStatementInfo.readOnly = statement.mReadOnly;
+
+                    final int columnCount = nativeGetColumnCount(
+                            mConnectionPtr, statement.mStatementPtr);
+                    if (columnCount == 0) {
+                        outStatementInfo.columnNames = EMPTY_STRING_ARRAY;
+                    } else {
+                        outStatementInfo.columnNames = new String[columnCount];
+                        for (int i = 0; i < columnCount; i++) {
+                            outStatementInfo.columnNames[i] = nativeGetColumnName(
+                                    mConnectionPtr, statement.mStatementPtr, i);
+                        }
+                    }
+                }
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            mRecentOperations.endOperation();
+        }
+    }
+
+    /**
+     * Executes a statement that does not return a result.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public void execute(String sql, Object[] bindArgs) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        mRecentOperations.beginOperation("execute", sql, bindArgs);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                throwIfStatementForbidden(statement);
+                bindArguments(statement, bindArgs);
+                applyBlockGuardPolicy(statement);
+                nativeExecute(mConnectionPtr, statement.mStatementPtr);
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            mRecentOperations.endOperation();
+        }
+    }
+
+    /**
+     * Executes a statement that returns a single <code>long</code> result.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @return The value of the first column in the first row of the result set
+     * as a <code>long</code>, or zero if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public long executeForLong(String sql, Object[] bindArgs) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        mRecentOperations.beginOperation("executeForLong", sql, bindArgs);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                throwIfStatementForbidden(statement);
+                bindArguments(statement, bindArgs);
+                applyBlockGuardPolicy(statement);
+                return nativeExecuteForLong(mConnectionPtr, statement.mStatementPtr);
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            mRecentOperations.endOperation();
+        }
+    }
+
+    /**
+     * Executes a statement that returns a single {@link String} result.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @return The value of the first column in the first row of the result set
+     * as a <code>String</code>, or null if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public String executeForString(String sql, Object[] bindArgs) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        mRecentOperations.beginOperation("executeForString", sql, bindArgs);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                throwIfStatementForbidden(statement);
+                bindArguments(statement, bindArgs);
+                applyBlockGuardPolicy(statement);
+                return nativeExecuteForString(mConnectionPtr, statement.mStatementPtr);
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            mRecentOperations.endOperation();
+        }
+    }
+
+    /**
+     * Executes a statement that returns a single BLOB result as a
+     * file descriptor to a shared memory region.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @return The file descriptor for a shared memory region that contains
+     * the value of the first column in the first row of the result set as a BLOB,
+     * or null if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        mRecentOperations.beginOperation("executeForBlobFileDescriptor", sql, bindArgs);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                throwIfStatementForbidden(statement);
+                bindArguments(statement, bindArgs);
+                applyBlockGuardPolicy(statement);
+                int fd = nativeExecuteForBlobFileDescriptor(
+                        mConnectionPtr, statement.mStatementPtr);
+                return fd >= 0 ? ParcelFileDescriptor.adoptFd(fd) : null;
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            mRecentOperations.endOperation();
+        }
+    }
+
+    /**
+     * Executes a statement that returns a count of the number of rows
+     * that were changed.  Use for UPDATE or DELETE SQL statements.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @return The number of rows that were changed.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public int executeForChangedRowCount(String sql, Object[] bindArgs) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        mRecentOperations.beginOperation("executeForChangedRowCount", sql, bindArgs);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                throwIfStatementForbidden(statement);
+                bindArguments(statement, bindArgs);
+                applyBlockGuardPolicy(statement);
+                return nativeExecuteForChangedRowCount(
+                        mConnectionPtr, statement.mStatementPtr);
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            mRecentOperations.endOperation();
+        }
+    }
+
+    /**
+     * Executes a statement that returns the row id of the last row inserted
+     * by the statement.  Use for INSERT SQL statements.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @return The row id of the last row that was inserted, or 0 if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public long executeForLastInsertedRowId(String sql, Object[] bindArgs) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        mRecentOperations.beginOperation("executeForLastInsertedRowId", sql, bindArgs);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                throwIfStatementForbidden(statement);
+                bindArguments(statement, bindArgs);
+                applyBlockGuardPolicy(statement);
+                return nativeExecuteForLastInsertedRowId(
+                        mConnectionPtr, statement.mStatementPtr);
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            mRecentOperations.endOperation();
+        }
+    }
+
+    /**
+     * Executes a statement and populates the specified {@link CursorWindow}
+     * with a range of results.  Returns the number of rows that were counted
+     * during query execution.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param window The cursor window to clear and fill.
+     * @param startPos The start position for filling the window.
+     * @param requiredPos The position of a row that MUST be in the window.
+     * If it won't fit, then the query should discard part of what it filled
+     * so that it does.  Must be greater than or equal to <code>startPos</code>.
+     * @param countAllRows True to count all rows that the query would return
+     * regagless of whether they fit in the window.
+     * @return The number of rows that were counted during query execution.  Might
+     * not be all rows in the result set unless <code>countAllRows</code> is true.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public int executeForCursorWindow(String sql, Object[] bindArgs,
+            CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+        if (window == null) {
+            throw new IllegalArgumentException("window must not be null.");
+        }
+
+        int actualPos = -1;
+        int countedRows = -1;
+        int filledRows = -1;
+        mRecentOperations.beginOperation("executeForCursorWindow", sql, bindArgs);
+        try {
+            PreparedStatement statement = acquirePreparedStatement(sql);
+            try {
+                throwIfStatementForbidden(statement);
+                bindArguments(statement, bindArgs);
+                applyBlockGuardPolicy(statement);
+                final long result = nativeExecuteForCursorWindow(
+                        mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
+                        startPos, requiredPos, countAllRows);
+                actualPos = (int)(result >> 32);
+                countedRows = (int)result;
+                filledRows = window.getNumRows();
+                window.setStartPosition(actualPos);
+                return countedRows;
+            } finally {
+                releasePreparedStatement(statement);
+            }
+        } catch (RuntimeException ex) {
+            mRecentOperations.failOperation(ex);
+            throw ex;
+        } finally {
+            if (mRecentOperations.endOperationDeferLog()) {
+                mRecentOperations.logOperation("window='" + window
+                        + "', startPos=" + startPos
+                        + ", actualPos=" + actualPos
+                        + ", filledRows=" + filledRows
+                        + ", countedRows=" + countedRows);
+            }
+        }
+    }
+
+    private PreparedStatement acquirePreparedStatement(String sql) {
+        PreparedStatement statement = mPreparedStatementCache.get(sql);
+        if (statement != null) {
+            return statement;
+        }
+
+        final int statementPtr = nativePrepareStatement(mConnectionPtr, sql);
+        try {
+            final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
+            final int type = DatabaseUtils.getSqlStatementType(sql);
+            final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
+            statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly);
+            if (isCacheable(type)) {
+                mPreparedStatementCache.put(sql, statement);
+                statement.mInCache = true;
+            }
+        } catch (RuntimeException ex) {
+            // Finalize the statement if an exception occurred and we did not add
+            // it to the cache.  If it is already in the cache, then leave it there.
+            if (statement == null || !statement.mInCache) {
+                nativeFinalizeStatement(mConnectionPtr, statementPtr);
+            }
+            throw ex;
+        }
+        return statement;
+    }
+
+    private void releasePreparedStatement(PreparedStatement statement) {
+        if (statement.mInCache) {
+            try {
+                nativeResetStatementAndClearBindings(mConnectionPtr, statement.mStatementPtr);
+            } catch (SQLiteException ex) {
+                // The statement could not be reset due to an error.
+                // The entryRemoved() callback for the cache will recursively call
+                // releasePreparedStatement() again, but this time mInCache will be false
+                // so the statement will be finalized and recycled.
+                if (SQLiteDebug.DEBUG_SQL_CACHE) {
+                    Log.v(TAG, "Could not reset prepared statement due to an exception.  "
+                            + "Removing it from the cache.  SQL: "
+                            + trimSqlForDisplay(statement.mSql), ex);
+                }
+                mPreparedStatementCache.remove(statement.mSql);
+            }
+        } else {
+            nativeFinalizeStatement(mConnectionPtr, statement.mStatementPtr);
+            recyclePreparedStatement(statement);
+        }
+    }
+
+    private void bindArguments(PreparedStatement statement, Object[] bindArgs) {
+        final int count = bindArgs != null ? bindArgs.length : 0;
+        if (count != statement.mNumParameters) {
+            throw new SQLiteBindOrColumnIndexOutOfRangeException(
+                    "Expected " + statement.mNumParameters + " bind arguments but "
+                    + bindArgs.length + " were provided.");
+        }
+        if (count == 0) {
+            return;
+        }
+
+        final int statementPtr = statement.mStatementPtr;
+        for (int i = 0; i < count; i++) {
+            final Object arg = bindArgs[i];
+            switch (DatabaseUtils.getTypeOfObject(arg)) {
+                case Cursor.FIELD_TYPE_NULL:
+                    nativeBindNull(mConnectionPtr, statementPtr, i + 1);
+                    break;
+                case Cursor.FIELD_TYPE_INTEGER:
+                    nativeBindLong(mConnectionPtr, statementPtr, i + 1,
+                            ((Number)arg).longValue());
+                    break;
+                case Cursor.FIELD_TYPE_FLOAT:
+                    nativeBindDouble(mConnectionPtr, statementPtr, i + 1,
+                            ((Number)arg).doubleValue());
+                    break;
+                case Cursor.FIELD_TYPE_BLOB:
+                    nativeBindBlob(mConnectionPtr, statementPtr, i + 1, (byte[])arg);
+                    break;
+                case Cursor.FIELD_TYPE_STRING:
+                default:
+                    if (arg instanceof Boolean) {
+                        // Provide compatibility with legacy applications which may pass
+                        // Boolean values in bind args.
+                        nativeBindLong(mConnectionPtr, statementPtr, i + 1,
+                                ((Boolean)arg).booleanValue() ? 1 : 0);
+                    } else {
+                        nativeBindString(mConnectionPtr, statementPtr, i + 1, arg.toString());
+                    }
+                    break;
+            }
+        }
+    }
+
+    private void throwIfStatementForbidden(PreparedStatement statement) {
+        if (mOnlyAllowReadOnlyOperations && !statement.mReadOnly) {
+            throw new SQLiteException("Cannot execute this statement because it "
+                    + "might modify the database but the connection is read-only.");
+        }
+    }
+
+    private static boolean isCacheable(int statementType) {
+        if (statementType == DatabaseUtils.STATEMENT_UPDATE
+                || statementType == DatabaseUtils.STATEMENT_SELECT) {
+            return true;
+        }
+        return false;
+    }
+
+    private void applyBlockGuardPolicy(PreparedStatement statement) {
+        if (!mConfiguration.isInMemoryDb()) {
+            if (statement.mReadOnly) {
+                BlockGuard.getThreadPolicy().onReadFromDisk();
+            } else {
+                BlockGuard.getThreadPolicy().onWriteToDisk();
+            }
+        }
+    }
+
+    /**
+     * Dumps debugging information about this connection.
+     *
+     * @param printer The printer to receive the dump, not null.
+     */
+    public void dump(Printer printer) {
+        dumpUnsafe(printer);
+    }
+
+    /**
+     * Dumps debugging information about this connection, in the case where the
+     * caller might not actually own the connection.
+     *
+     * This function is written so that it may be called by a thread that does not
+     * own the connection.  We need to be very careful because the connection state is
+     * not synchronized.
+     *
+     * At worst, the method may return stale or slightly wrong data, however
+     * it should not crash.  This is ok as it is only used for diagnostic purposes.
+     *
+     * @param printer The printer to receive the dump, not null.
+     */
+    void dumpUnsafe(Printer printer) {
+        printer.println("Connection #" + mConnectionId + ":");
+        printer.println("  isPrimaryConnection: " + mIsPrimaryConnection);
+        printer.println("  connectionPtr: 0x" + Integer.toHexString(mConnectionPtr));
+        printer.println("  onlyAllowReadOnlyOperations: " + mOnlyAllowReadOnlyOperations);
+
+        mRecentOperations.dump(printer);
+        mPreparedStatementCache.dump(printer);
+    }
+
+    /**
+     * Describes the currently executing operation, in the case where the
+     * caller might not actually own the connection.
+     *
+     * This function is written so that it may be called by a thread that does not
+     * own the connection.  We need to be very careful because the connection state is
+     * not synchronized.
+     *
+     * At worst, the method may return stale or slightly wrong data, however
+     * it should not crash.  This is ok as it is only used for diagnostic purposes.
+     *
+     * @return A description of the current operation including how long it has been running,
+     * or null if none.
+     */
+    String describeCurrentOperationUnsafe() {
+        return mRecentOperations.describeCurrentOperation();
+    }
+
+    /**
+     * Collects statistics about database connection memory usage.
+     *
+     * @param dbStatsList The list to populate.
+     */
+    void collectDbStats(ArrayList<DbStats> dbStatsList) {
+        // Get information about the main database.
+        int lookaside = nativeGetDbLookaside(mConnectionPtr);
+        long pageCount = 0;
+        long pageSize = 0;
+        try {
+            pageCount = executeForLong("PRAGMA page_count;", null);
+            pageSize = executeForLong("PRAGMA page_size;", null);
+        } catch (SQLiteException ex) {
+            // Ignore.
+        }
+        dbStatsList.add(getMainDbStatsUnsafe(lookaside, pageCount, pageSize));
+
+        // Get information about attached databases.
+        // We ignore the first row in the database list because it corresponds to
+        // the main database which we have already described.
+        CursorWindow window = new CursorWindow("collectDbStats");
+        try {
+            executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false);
+            for (int i = 1; i < window.getNumRows(); i++) {
+                String name = window.getString(i, 1);
+                String path = window.getString(i, 2);
+                pageCount = 0;
+                pageSize = 0;
+                try {
+                    pageCount = executeForLong("PRAGMA " + name + ".page_count;", null);
+                    pageSize = executeForLong("PRAGMA " + name + ".page_size;", null);
+                } catch (SQLiteException ex) {
+                    // Ignore.
+                }
+                String label = "  (attached) " + name;
+                if (!path.isEmpty()) {
+                    label += ": " + path;
+                }
+                dbStatsList.add(new DbStats(label, pageCount, pageSize, 0, 0, 0, 0));
+            }
+        } catch (SQLiteException ex) {
+            // Ignore.
+        } finally {
+            window.close();
+        }
+    }
+
+    /**
+     * Collects statistics about database connection memory usage, in the case where the
+     * caller might not actually own the connection.
+     *
+     * @return The statistics object, never null.
+     */
+    void collectDbStatsUnsafe(ArrayList<DbStats> dbStatsList) {
+        dbStatsList.add(getMainDbStatsUnsafe(0, 0, 0));
+    }
+
+    private DbStats getMainDbStatsUnsafe(int lookaside, long pageCount, long pageSize) {
+        // The prepared statement cache is thread-safe so we can access its statistics
+        // even if we do not own the database connection.
+        String label = mConfiguration.path;
+        if (!mIsPrimaryConnection) {
+            label += " (" + mConnectionId + ")";
+        }
+        return new DbStats(label, pageCount, pageSize, lookaside,
+                mPreparedStatementCache.hitCount(),
+                mPreparedStatementCache.missCount(),
+                mPreparedStatementCache.size());
+    }
+
+    @Override
+    public String toString() {
+        return "SQLiteConnection: " + mConfiguration.path + " (" + mConnectionId + ")";
+    }
+
+    private PreparedStatement obtainPreparedStatement(String sql, int statementPtr,
+            int numParameters, int type, boolean readOnly) {
+        PreparedStatement statement = mPreparedStatementPool;
+        if (statement != null) {
+            mPreparedStatementPool = statement.mPoolNext;
+            statement.mPoolNext = null;
+            statement.mInCache = false;
+        } else {
+            statement = new PreparedStatement();
+        }
+        statement.mSql = sql;
+        statement.mStatementPtr = statementPtr;
+        statement.mNumParameters = numParameters;
+        statement.mType = type;
+        statement.mReadOnly = readOnly;
+        return statement;
+    }
+
+    private void recyclePreparedStatement(PreparedStatement statement) {
+        statement.mSql = null;
+        statement.mPoolNext = mPreparedStatementPool;
+        mPreparedStatementPool = statement;
+    }
+
+    private static String trimSqlForDisplay(String sql) {
+        return TRIM_SQL_PATTERN.matcher(sql).replaceAll(" ");
+    }
+
+    /**
+     * Holder type for a prepared statement.
+     *
+     * Although this object holds a pointer to a native statement object, it
+     * does not have a finalizer.  This is deliberate.  The {@link SQLiteConnection}
+     * owns the statement object and will take care of freeing it when needed.
+     * In particular, closing the connection requires a guarantee of deterministic
+     * resource disposal because all native statement objects must be freed before
+     * the native database object can be closed.  So no finalizers here.
+     */
+    private static final class PreparedStatement {
+        // Next item in pool.
+        public PreparedStatement mPoolNext;
+
+        // The SQL from which the statement was prepared.
+        public String mSql;
+
+        // The native sqlite3_stmt object pointer.
+        // Lifetime is managed explicitly by the connection.
+        public int mStatementPtr;
+
+        // The number of parameters that the prepared statement has.
+        public int mNumParameters;
+
+        // The statement type.
+        public int mType;
+
+        // True if the statement is read-only.
+        public boolean mReadOnly;
+
+        // True if the statement is in the cache.
+        public boolean mInCache;
+    }
+
+    private final class PreparedStatementCache
+            extends LruCache<String, PreparedStatement> {
+        public PreparedStatementCache(int size) {
+            super(size);
+        }
+
+        @Override
+        protected void entryRemoved(boolean evicted, String key,
+                PreparedStatement oldValue, PreparedStatement newValue) {
+            oldValue.mInCache = false;
+            releasePreparedStatement(oldValue);
+        }
+
+        public void dump(Printer printer) {
+            printer.println("  Prepared statement cache:");
+            Map<String, PreparedStatement> cache = snapshot();
+            if (!cache.isEmpty()) {
+                int i = 0;
+                for (Map.Entry<String, PreparedStatement> entry : cache.entrySet()) {
+                    PreparedStatement statement = entry.getValue();
+                    if (statement.mInCache) { // might be false due to a race with entryRemoved
+                        String sql = entry.getKey();
+                        printer.println("    " + i + ": statementPtr=0x"
+                                + Integer.toHexString(statement.mStatementPtr)
+                                + ", numParameters=" + statement.mNumParameters
+                                + ", type=" + statement.mType
+                                + ", readOnly=" + statement.mReadOnly
+                                + ", sql=\"" + trimSqlForDisplay(sql) + "\"");
+                    }
+                    i += 1;
+                }
+            } else {
+                printer.println("    <none>");
+            }
+        }
+    }
+
+    private static final class OperationLog {
+        private static final int MAX_RECENT_OPERATIONS = 10;
+
+        private final Operation[] mOperations = new Operation[MAX_RECENT_OPERATIONS];
+        private int mIndex;
+
+        public void beginOperation(String kind, String sql, Object[] bindArgs) {
+            synchronized (mOperations) {
+                final int index = (mIndex + 1) % MAX_RECENT_OPERATIONS;
+                Operation operation = mOperations[index];
+                if (operation == null) {
+                    operation = new Operation();
+                    mOperations[index] = operation;
+                } else {
+                    operation.mFinished = false;
+                    operation.mException = null;
+                    if (operation.mBindArgs != null) {
+                        operation.mBindArgs.clear();
+                    }
+                }
+                operation.mStartTime = System.currentTimeMillis();
+                operation.mKind = kind;
+                operation.mSql = sql;
+                if (bindArgs != null) {
+                    if (operation.mBindArgs == null) {
+                        operation.mBindArgs = new ArrayList<Object>();
+                    } else {
+                        operation.mBindArgs.clear();
+                    }
+                    for (int i = 0; i < bindArgs.length; i++) {
+                        final Object arg = bindArgs[i];
+                        if (arg != null && arg instanceof byte[]) {
+                            // Don't hold onto the real byte array longer than necessary.
+                            operation.mBindArgs.add(EMPTY_BYTE_ARRAY);
+                        } else {
+                            operation.mBindArgs.add(arg);
+                        }
+                    }
+                }
+                mIndex = index;
+            }
+        }
+
+        public void failOperation(Exception ex) {
+            synchronized (mOperations) {
+                final Operation operation =  mOperations[mIndex];
+                operation.mException = ex;
+            }
+        }
+
+        public boolean endOperationDeferLog() {
+            synchronized (mOperations) {
+                return endOperationDeferLogLocked();
+            }
+        }
+
+        private boolean endOperationDeferLogLocked() {
+            final Operation operation =  mOperations[mIndex];
+            operation.mEndTime = System.currentTimeMillis();
+            operation.mFinished = true;
+            return SQLiteDebug.DEBUG_LOG_SLOW_QUERIES && SQLiteDebug.shouldLogSlowQuery(
+                            operation.mEndTime - operation.mStartTime);
+        }
+
+        public void endOperation() {
+            synchronized (mOperations) {
+                if (endOperationDeferLogLocked()) {
+                    logOperationLocked(null);
+                }
+            }
+        }
+
+        public void logOperation(String detail) {
+            synchronized (mOperations) {
+                logOperationLocked(detail);
+            }
+        }
+
+        private void logOperationLocked(String detail) {
+            final Operation operation =  mOperations[mIndex];
+            StringBuilder msg = new StringBuilder();
+            operation.describe(msg);
+            if (detail != null) {
+                msg.append(", ").append(detail);
+            }
+            Log.d(TAG, msg.toString());
+        }
+
+        public String describeCurrentOperation() {
+            synchronized (mOperations) {
+                final Operation operation = mOperations[mIndex];
+                if (operation != null && !operation.mFinished) {
+                    StringBuilder msg = new StringBuilder();
+                    operation.describe(msg);
+                    return msg.toString();
+                }
+                return null;
+            }
+        }
+
+        public void dump(Printer printer) {
+            synchronized (mOperations) {
+                printer.println("  Most recently executed operations:");
+                int index = mIndex;
+                Operation operation = mOperations[index];
+                if (operation != null) {
+                    int n = 0;
+                    do {
+                        StringBuilder msg = new StringBuilder();
+                        msg.append("    ").append(n).append(": [");
+                        msg.append(operation.getFormattedStartTime());
+                        msg.append("] ");
+                        operation.describe(msg);
+                        printer.println(msg.toString());
+
+                        if (index > 0) {
+                            index -= 1;
+                        } else {
+                            index = MAX_RECENT_OPERATIONS - 1;
+                        }
+                        n += 1;
+                        operation = mOperations[index];
+                    } while (operation != null && n < MAX_RECENT_OPERATIONS);
+                } else {
+                    printer.println("    <none>");
+                }
+            }
+        }
+    }
+
+    private static final class Operation {
+        private static final SimpleDateFormat sDateFormat =
+                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+
+        public long mStartTime;
+        public long mEndTime;
+        public String mKind;
+        public String mSql;
+        public ArrayList<Object> mBindArgs;
+        public boolean mFinished;
+        public Exception mException;
+
+        public void describe(StringBuilder msg) {
+            msg.append(mKind);
+            if (mFinished) {
+                msg.append(" took ").append(mEndTime - mStartTime).append("ms");
+            } else {
+                msg.append(" started ").append(System.currentTimeMillis() - mStartTime)
+                        .append("ms ago");
+            }
+            msg.append(" - ").append(getStatus());
+            if (mSql != null) {
+                msg.append(", sql=\"").append(trimSqlForDisplay(mSql)).append("\"");
+            }
+            if (mBindArgs != null && mBindArgs.size() != 0) {
+                msg.append(", bindArgs=[");
+                final int count = mBindArgs.size();
+                for (int i = 0; i < count; i++) {
+                    final Object arg = mBindArgs.get(i);
+                    if (i != 0) {
+                        msg.append(", ");
+                    }
+                    if (arg == null) {
+                        msg.append("null");
+                    } else if (arg instanceof byte[]) {
+                        msg.append("<byte[]>");
+                    } else if (arg instanceof String) {
+                        msg.append("\"").append((String)arg).append("\"");
+                    } else {
+                        msg.append(arg);
+                    }
+                }
+                msg.append("]");
+            }
+            if (mException != null) {
+                msg.append(", exception=\"").append(mException.getMessage()).append("\"");
+            }
+        }
+
+        private String getStatus() {
+            if (!mFinished) {
+                return "running";
+            }
+            return mException != null ? "failed" : "succeeded";
+        }
+
+        private String getFormattedStartTime() {
+            return sDateFormat.format(new Date(mStartTime));
+        }
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
new file mode 100644
index 0000000..b88bfee
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -0,0 +1,907 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import dalvik.system.CloseGuard;
+
+import android.database.sqlite.SQLiteDebug.DbStats;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.PrefixPrinter;
+import android.util.Printer;
+
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.locks.LockSupport;
+
+/**
+ * Maintains a pool of active SQLite database connections.
+ * <p>
+ * At any given time, a connection is either owned by the pool, or it has been
+ * acquired by a {@link SQLiteSession}.  When the {@link SQLiteSession} is
+ * finished with the connection it is using, it must return the connection
+ * back to the pool.
+ * </p><p>
+ * The pool holds strong references to the connections it owns.  However,
+ * it only holds <em>weak references</em> to the connections that sessions
+ * have acquired from it.  Using weak references in the latter case ensures
+ * that the connection pool can detect when connections have been improperly
+ * abandoned so that it can create new connections to replace them if needed.
+ * </p><p>
+ * The connection pool is thread-safe (but the connections themselves are not).
+ * </p>
+ *
+ * <h2>Exception safety</h2>
+ * <p>
+ * This code attempts to maintain the invariant that opened connections are
+ * always owned.  Unfortunately that means it needs to handle exceptions
+ * all over to ensure that broken connections get cleaned up.  Most
+ * operations invokving SQLite can throw {@link SQLiteException} or other
+ * runtime exceptions.  This is a bit of a pain to deal with because the compiler
+ * cannot help us catch missing exception handling code.
+ * </p><p>
+ * The general rule for this file: If we are making calls out to
+ * {@link SQLiteConnection} then we must be prepared to handle any
+ * runtime exceptions it might throw at us.  Note that out-of-memory
+ * is an {@link Error}, not a {@link RuntimeException}.  We don't trouble ourselves
+ * handling out of memory because it is hard to do anything at all sensible then
+ * and most likely the VM is about to crash.
+ * </p>
+ *
+ * @hide
+ */
+public final class SQLiteConnectionPool implements Closeable {
+    private static final String TAG = "SQLiteConnectionPool";
+
+    // Amount of time to wait in milliseconds before unblocking acquireConnection
+    // and logging a message about the connection pool being busy.
+    private static final long CONNECTION_POOL_BUSY_MILLIS = 30 * 1000; // 30 seconds
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private final Object mLock = new Object();
+    private final AtomicBoolean mConnectionLeaked = new AtomicBoolean();
+    private final SQLiteDatabaseConfiguration mConfiguration;
+    private boolean mIsOpen;
+    private int mNextConnectionId;
+
+    private ConnectionWaiter mConnectionWaiterPool;
+    private ConnectionWaiter mConnectionWaiterQueue;
+
+    // Strong references to all available connections.
+    private final ArrayList<SQLiteConnection> mAvailableNonPrimaryConnections =
+            new ArrayList<SQLiteConnection>();
+    private SQLiteConnection mAvailablePrimaryConnection;
+
+    // Weak references to all acquired connections.  The associated value
+    // is a boolean that indicates whether the connection must be reconfigured
+    // before being returned to the available connection list.
+    // For example, the prepared statement cache size may have changed and
+    // need to be updated.
+    private final WeakHashMap<SQLiteConnection, Boolean> mAcquiredConnections =
+            new WeakHashMap<SQLiteConnection, Boolean>();
+
+    /**
+     * Connection flag: Read-only.
+     * <p>
+     * This flag indicates that the connection will only be used to
+     * perform read-only operations.
+     * </p>
+     */
+    public static final int CONNECTION_FLAG_READ_ONLY = 1 << 0;
+
+    /**
+     * Connection flag: Primary connection affinity.
+     * <p>
+     * This flag indicates that the primary connection is required.
+     * This flag helps support legacy applications that expect most data modifying
+     * operations to be serialized by locking the primary database connection.
+     * Setting this flag essentially implements the old "db lock" concept by preventing
+     * an operation from being performed until it can obtain exclusive access to
+     * the primary connection.
+     * </p>
+     */
+    public static final int CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY = 1 << 1;
+
+    /**
+     * Connection flag: Connection is being used interactively.
+     * <p>
+     * This flag indicates that the connection is needed by the UI thread.
+     * The connection pool can use this flag to elevate the priority
+     * of the database connection request.
+     * </p>
+     */
+    public static final int CONNECTION_FLAG_INTERACTIVE = 1 << 2;
+
+    private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) {
+        mConfiguration = new SQLiteDatabaseConfiguration(configuration);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Opens a connection pool for the specified database.
+     *
+     * @param configuration The database configuration.
+     * @return The connection pool.
+     *
+     * @throws SQLiteException if a database error occurs.
+     */
+    public static SQLiteConnectionPool open(SQLiteDatabaseConfiguration configuration) {
+        if (configuration == null) {
+            throw new IllegalArgumentException("configuration must not be null.");
+        }
+
+        // Create the pool.
+        SQLiteConnectionPool pool = new SQLiteConnectionPool(configuration);
+        pool.open(); // might throw
+        return pool;
+    }
+
+    // Might throw
+    private void open() {
+        // Open the primary connection.
+        // This might throw if the database is corrupt.
+        mAvailablePrimaryConnection = openConnectionLocked(
+                true /*primaryConnection*/); // might throw
+
+        // Mark the pool as being open for business.
+        mIsOpen = true;
+        mCloseGuard.open("close");
+    }
+
+    /**
+     * Closes the connection pool.
+     * <p>
+     * When the connection pool is closed, it will refuse all further requests
+     * to acquire connections.  All connections that are currently available in
+     * the pool are closed immediately.  Any connections that are still in use
+     * will be closed as soon as they are returned to the pool.
+     * </p>
+     *
+     * @throws IllegalStateException if the pool has been closed.
+     */
+    public void close() {
+        dispose(false);
+    }
+
+    private void dispose(boolean finalized) {
+        if (mCloseGuard != null) {
+            if (finalized) {
+                mCloseGuard.warnIfOpen();
+            }
+            mCloseGuard.close();
+        }
+
+        if (!finalized) {
+            // Close all connections.  We don't need (or want) to do this
+            // when finalized because we don't know what state the connections
+            // themselves will be in.  The finalizer is really just here for CloseGuard.
+            // The connections will take care of themselves when their own finalizers run.
+            synchronized (mLock) {
+                throwIfClosedLocked();
+
+                mIsOpen = false;
+
+                final int count = mAvailableNonPrimaryConnections.size();
+                for (int i = 0; i < count; i++) {
+                    closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i));
+                }
+                mAvailableNonPrimaryConnections.clear();
+
+                if (mAvailablePrimaryConnection != null) {
+                    closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
+                    mAvailablePrimaryConnection = null;
+                }
+
+                final int pendingCount = mAcquiredConnections.size();
+                if (pendingCount != 0) {
+                    Log.i(TAG, "The connection pool for " + mConfiguration.label
+                            + " has been closed but there are still "
+                            + pendingCount + " connections in use.  They will be closed "
+                            + "as they are released back to the pool.");
+                }
+
+                wakeConnectionWaitersLocked();
+            }
+        }
+    }
+
+    /**
+     * Reconfigures the database configuration of the connection pool and all of its
+     * connections.
+     * <p>
+     * Configuration changes are propagated down to connections immediately if
+     * they are available or as soon as they are released.  This includes changes
+     * that affect the size of the pool.
+     * </p>
+     *
+     * @param configuration The new configuration.
+     *
+     * @throws IllegalStateException if the pool has been closed.
+     */
+    public void reconfigure(SQLiteDatabaseConfiguration configuration) {
+        if (configuration == null) {
+            throw new IllegalArgumentException("configuration must not be null.");
+        }
+
+        synchronized (mLock) {
+            throwIfClosedLocked();
+
+            final boolean poolSizeChanged = mConfiguration.maxConnectionPoolSize
+                    != configuration.maxConnectionPoolSize;
+            mConfiguration.updateParametersFrom(configuration);
+
+            if (poolSizeChanged) {
+                int availableCount = mAvailableNonPrimaryConnections.size();
+                while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) {
+                    SQLiteConnection connection =
+                            mAvailableNonPrimaryConnections.remove(availableCount);
+                    closeConnectionAndLogExceptionsLocked(connection);
+                }
+            }
+
+            reconfigureAllConnectionsLocked();
+
+            wakeConnectionWaitersLocked();
+        }
+    }
+
+    /**
+     * Acquires a connection from the pool.
+     * <p>
+     * The caller must call {@link #releaseConnection} to release the connection
+     * back to the pool when it is finished.  Failure to do so will result
+     * in much unpleasantness.
+     * </p>
+     *
+     * @param sql If not null, try to find a connection that already has
+     * the specified SQL statement in its prepared statement cache.
+     * @param connectionFlags The connection request flags.
+     * @return The connection that was acquired, never null.
+     *
+     * @throws IllegalStateException if the pool has been closed.
+     * @throws SQLiteException if a database error occurs.
+     */
+    public SQLiteConnection acquireConnection(String sql, int connectionFlags) {
+        return waitForConnection(sql, connectionFlags);
+    }
+
+    /**
+     * Releases a connection back to the pool.
+     * <p>
+     * It is ok to call this method after the pool has closed, to release
+     * connections that were still in use at the time of closure.
+     * </p>
+     *
+     * @param connection The connection to release.  Must not be null.
+     *
+     * @throws IllegalStateException if the connection was not acquired
+     * from this pool or if it has already been released.
+     */
+    public void releaseConnection(SQLiteConnection connection) {
+        synchronized (mLock) {
+            Boolean mustReconfigure = mAcquiredConnections.remove(connection);
+            if (mustReconfigure == null) {
+                throw new IllegalStateException("Cannot perform this operation "
+                        + "because the specified connection was not acquired "
+                        + "from this pool or has already been released.");
+            }
+
+            if (!mIsOpen) {
+                closeConnectionAndLogExceptionsLocked(connection);
+            } else if (connection.isPrimaryConnection()) {
+                assert mAvailablePrimaryConnection == null;
+                try {
+                    if (mustReconfigure == Boolean.TRUE) {
+                        connection.reconfigure(mConfiguration); // might throw
+                    }
+                } catch (RuntimeException ex) {
+                    Log.e(TAG, "Failed to reconfigure released primary connection, closing it: "
+                            + connection, ex);
+                    closeConnectionAndLogExceptionsLocked(connection);
+                    connection = null;
+                }
+                if (connection != null) {
+                    mAvailablePrimaryConnection = connection;
+                }
+                wakeConnectionWaitersLocked();
+            } else if (mAvailableNonPrimaryConnections.size() >=
+                    mConfiguration.maxConnectionPoolSize - 1) {
+                closeConnectionAndLogExceptionsLocked(connection);
+            } else {
+                try {
+                    if (mustReconfigure == Boolean.TRUE) {
+                        connection.reconfigure(mConfiguration); // might throw
+                    }
+                } catch (RuntimeException ex) {
+                    Log.e(TAG, "Failed to reconfigure released non-primary connection, "
+                            + "closing it: " + connection, ex);
+                    closeConnectionAndLogExceptionsLocked(connection);
+                    connection = null;
+                }
+                if (connection != null) {
+                    mAvailableNonPrimaryConnections.add(connection);
+                }
+                wakeConnectionWaitersLocked();
+            }
+        }
+    }
+
+    /**
+     * Returns true if the session should yield the connection due to
+     * contention over available database connections.
+     *
+     * @param connection The connection owned by the session.
+     * @param connectionFlags The connection request flags.
+     * @return True if the session should yield its connection.
+     *
+     * @throws IllegalStateException if the connection was not acquired
+     * from this pool or if it has already been released.
+     */
+    public boolean shouldYieldConnection(SQLiteConnection connection, int connectionFlags) {
+        synchronized (mLock) {
+            if (!mAcquiredConnections.containsKey(connection)) {
+                throw new IllegalStateException("Cannot perform this operation "
+                        + "because the specified connection was not acquired "
+                        + "from this pool or has already been released.");
+            }
+
+            if (!mIsOpen) {
+                return false;
+            }
+
+            return isSessionBlockingImportantConnectionWaitersLocked(
+                    connection.isPrimaryConnection(), connectionFlags);
+        }
+    }
+
+    /**
+     * Collects statistics about database connection memory usage.
+     *
+     * @param dbStatsList The list to populate.
+     */
+    public void collectDbStats(ArrayList<DbStats> dbStatsList) {
+        synchronized (mLock) {
+            if (mAvailablePrimaryConnection != null) {
+                mAvailablePrimaryConnection.collectDbStats(dbStatsList);
+            }
+
+            for (SQLiteConnection connection : mAvailableNonPrimaryConnections) {
+                connection.collectDbStats(dbStatsList);
+            }
+
+            for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
+                connection.collectDbStatsUnsafe(dbStatsList);
+            }
+        }
+    }
+
+    // Might throw.
+    private SQLiteConnection openConnectionLocked(boolean primaryConnection) {
+        final int connectionId = mNextConnectionId++;
+        return SQLiteConnection.open(this, mConfiguration,
+                connectionId, primaryConnection); // might throw
+    }
+
+    void onConnectionLeaked() {
+        // This code is running inside of the SQLiteConnection finalizer.
+        //
+        // We don't know whether it is just the connection that has been finalized (and leaked)
+        // or whether the connection pool has also been or is about to be finalized.
+        // Consequently, it would be a bad idea to try to grab any locks or to
+        // do any significant work here.  So we do the simplest possible thing and
+        // set a flag.  waitForConnection() periodically checks this flag (when it
+        // times out) so that it can recover from leaked connections and wake
+        // itself or other threads up if necessary.
+        //
+        // You might still wonder why we don't try to do more to wake up the waiters
+        // immediately.  First, as explained above, it would be hard to do safely
+        // unless we started an extra Thread to function as a reference queue.  Second,
+        // this is never supposed to happen in normal operation.  Third, there is no
+        // guarantee that the GC will actually detect the leak in a timely manner so
+        // it's not all that important that we recover from the leak in a timely manner
+        // either.  Fourth, if a badly behaved application finds itself hung waiting for
+        // several seconds while waiting for a leaked connection to be detected and recreated,
+        // then perhaps its authors will have added incentive to fix the problem!
+
+        Log.w(TAG, "A SQLiteConnection object for database '"
+                + mConfiguration.label + "' was leaked!  Please fix your application "
+                + "to end transactions in progress properly and to close the database "
+                + "when it is no longer needed.");
+
+        mConnectionLeaked.set(true);
+    }
+
+    // Can't throw.
+    private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection) {
+        try {
+            connection.close(); // might throw
+        } catch (RuntimeException ex) {
+            Log.e(TAG, "Failed to close connection, its fate is now in the hands "
+                    + "of the merciful GC: " + connection, ex);
+        }
+    }
+
+    // Can't throw.
+    private void reconfigureAllConnectionsLocked() {
+        boolean wake = false;
+        if (mAvailablePrimaryConnection != null) {
+            try {
+                mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw
+            } catch (RuntimeException ex) {
+                Log.e(TAG, "Failed to reconfigure available primary connection, closing it: "
+                        + mAvailablePrimaryConnection, ex);
+                closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
+                mAvailablePrimaryConnection = null;
+                wake = true;
+            }
+        }
+
+        int count = mAvailableNonPrimaryConnections.size();
+        for (int i = 0; i < count; i++) {
+            final SQLiteConnection connection = mAvailableNonPrimaryConnections.get(i);
+            try {
+                connection.reconfigure(mConfiguration); // might throw
+            } catch (RuntimeException ex) {
+                Log.e(TAG, "Failed to reconfigure available non-primary connection, closing it: "
+                        + connection, ex);
+                closeConnectionAndLogExceptionsLocked(connection);
+                mAvailableNonPrimaryConnections.remove(i--);
+                count -= 1;
+                wake = true;
+            }
+        }
+
+        if (!mAcquiredConnections.isEmpty()) {
+            ArrayList<SQLiteConnection> keysToUpdate = new ArrayList<SQLiteConnection>(
+                    mAcquiredConnections.size());
+            for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) {
+                if (entry.getValue() != Boolean.TRUE) {
+                    keysToUpdate.add(entry.getKey());
+                }
+            }
+            final int updateCount = keysToUpdate.size();
+            for (int i = 0; i < updateCount; i++) {
+                mAcquiredConnections.put(keysToUpdate.get(i), Boolean.TRUE);
+            }
+        }
+
+        if (wake) {
+            wakeConnectionWaitersLocked();
+        }
+    }
+
+    // Might throw.
+    private SQLiteConnection waitForConnection(String sql, int connectionFlags) {
+        final boolean wantPrimaryConnection =
+                (connectionFlags & CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY) != 0;
+
+        final ConnectionWaiter waiter;
+        synchronized (mLock) {
+            throwIfClosedLocked();
+
+            // Try to acquire a connection.
+            SQLiteConnection connection = null;
+            if (!wantPrimaryConnection) {
+                connection = tryAcquireNonPrimaryConnectionLocked(
+                        sql, connectionFlags); // might throw
+            }
+            if (connection == null) {
+                connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
+            }
+            if (connection != null) {
+                return connection;
+            }
+
+            // No connections available.  Enqueue a waiter in priority order.
+            final int priority = getPriority(connectionFlags);
+            final long startTime = SystemClock.uptimeMillis();
+            waiter = obtainConnectionWaiterLocked(Thread.currentThread(), startTime,
+                    priority, wantPrimaryConnection, sql, connectionFlags);
+            ConnectionWaiter predecessor = null;
+            ConnectionWaiter successor = mConnectionWaiterQueue;
+            while (successor != null) {
+                if (priority > successor.mPriority) {
+                    waiter.mNext = successor;
+                    break;
+                }
+                predecessor = successor;
+                successor = successor.mNext;
+            }
+            if (predecessor != null) {
+                predecessor.mNext = waiter;
+            } else {
+                mConnectionWaiterQueue = waiter;
+            }
+        }
+
+        // Park the thread until a connection is assigned or the pool is closed.
+        // Rethrow an exception from the wait, if we got one.
+        long busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
+        long nextBusyTimeoutTime = waiter.mStartTime + busyTimeoutMillis;
+        for (;;) {
+            // Detect and recover from connection leaks.
+            if (mConnectionLeaked.compareAndSet(true, false)) {
+                wakeConnectionWaitersLocked();
+            }
+
+            // Wait to be unparked (may already have happened), a timeout, or interruption.
+            LockSupport.parkNanos(this, busyTimeoutMillis * 1000000L);
+
+            // Clear the interrupted flag, just in case.
+            Thread.interrupted();
+
+            // Check whether we are done waiting yet.
+            synchronized (mLock) {
+                throwIfClosedLocked();
+
+                SQLiteConnection connection = waiter.mAssignedConnection;
+                if (connection != null) {
+                    recycleConnectionWaiterLocked(waiter);
+                    return connection;
+                }
+
+                RuntimeException ex = waiter.mException;
+                if (ex != null) {
+                    recycleConnectionWaiterLocked(waiter);
+                    throw ex; // rethrow!
+                }
+
+                final long now = SystemClock.uptimeMillis();
+                if (now < nextBusyTimeoutTime) {
+                    busyTimeoutMillis = now - nextBusyTimeoutTime;
+                } else {
+                    logConnectionPoolBusyLocked(now - waiter.mStartTime, connectionFlags);
+                    busyTimeoutMillis = CONNECTION_POOL_BUSY_MILLIS;
+                    nextBusyTimeoutTime = now + busyTimeoutMillis;
+                }
+            }
+        }
+    }
+
+    // Can't throw.
+    private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) {
+        final Thread thread = Thread.currentThread();
+        StringBuilder msg = new StringBuilder();
+        msg.append("The connection pool for database '").append(mConfiguration.label);
+        msg.append("' has been unable to grant a connection to thread ");
+        msg.append(thread.getId()).append(" (").append(thread.getName()).append(") ");
+        msg.append("with flags 0x").append(Integer.toHexString(connectionFlags));
+        msg.append(" for ").append(waitMillis * 0.001f).append(" seconds.\n");
+
+        ArrayList<String> requests = new ArrayList<String>();
+        int activeConnections = 0;
+        int idleConnections = 0;
+        if (!mAcquiredConnections.isEmpty()) {
+            for (Map.Entry<SQLiteConnection, Boolean> entry : mAcquiredConnections.entrySet()) {
+                final SQLiteConnection connection = entry.getKey();
+                String description = connection.describeCurrentOperationUnsafe();
+                if (description != null) {
+                    requests.add(description);
+                    activeConnections += 1;
+                } else {
+                    idleConnections += 1;
+                }
+            }
+        }
+        int availableConnections = mAvailableNonPrimaryConnections.size();
+        if (mAvailablePrimaryConnection != null) {
+            availableConnections += 1;
+        }
+
+        msg.append("Connections: ").append(activeConnections).append(" active, ");
+        msg.append(idleConnections).append(" idle, ");
+        msg.append(availableConnections).append(" available.\n");
+
+        if (!requests.isEmpty()) {
+            msg.append("\nRequests in progress:\n");
+            for (String request : requests) {
+                msg.append("  ").append(request).append("\n");
+            }
+        }
+
+        Log.w(TAG, msg.toString());
+    }
+
+    // Can't throw.
+    private void wakeConnectionWaitersLocked() {
+        // Unpark all waiters that have requests that we can fulfill.
+        // This method is designed to not throw runtime exceptions, although we might send
+        // a waiter an exception for it to rethrow.
+        ConnectionWaiter predecessor = null;
+        ConnectionWaiter waiter = mConnectionWaiterQueue;
+        boolean primaryConnectionNotAvailable = false;
+        boolean nonPrimaryConnectionNotAvailable = false;
+        while (waiter != null) {
+            boolean unpark = false;
+            if (!mIsOpen) {
+                unpark = true;
+            } else {
+                try {
+                    SQLiteConnection connection = null;
+                    if (!waiter.mWantPrimaryConnection && !nonPrimaryConnectionNotAvailable) {
+                        connection = tryAcquireNonPrimaryConnectionLocked(
+                                waiter.mSql, waiter.mConnectionFlags); // might throw
+                        if (connection == null) {
+                            nonPrimaryConnectionNotAvailable = true;
+                        }
+                    }
+                    if (connection == null && !primaryConnectionNotAvailable) {
+                        connection = tryAcquirePrimaryConnectionLocked(
+                                waiter.mConnectionFlags); // might throw
+                        if (connection == null) {
+                            primaryConnectionNotAvailable = true;
+                        }
+                    }
+                    if (connection != null) {
+                        waiter.mAssignedConnection = connection;
+                        unpark = true;
+                    } else if (nonPrimaryConnectionNotAvailable && primaryConnectionNotAvailable) {
+                        // There are no connections available and the pool is still open.
+                        // We cannot fulfill any more connection requests, so stop here.
+                        break;
+                    }
+                } catch (RuntimeException ex) {
+                    // Let the waiter handle the exception from acquiring a connection.
+                    waiter.mException = ex;
+                    unpark = true;
+                }
+            }
+
+            final ConnectionWaiter successor = waiter.mNext;
+            if (unpark) {
+                if (predecessor != null) {
+                    predecessor.mNext = successor;
+                } else {
+                    mConnectionWaiterQueue = successor;
+                }
+                waiter.mNext = null;
+
+                LockSupport.unpark(waiter.mThread);
+            } else {
+                predecessor = waiter;
+            }
+            waiter = successor;
+        }
+    }
+
+    // Might throw.
+    private SQLiteConnection tryAcquirePrimaryConnectionLocked(int connectionFlags) {
+        // If the primary connection is available, acquire it now.
+        SQLiteConnection connection = mAvailablePrimaryConnection;
+        if (connection != null) {
+            mAvailablePrimaryConnection = null;
+            finishAcquireConnectionLocked(connection, connectionFlags); // might throw
+            return connection;
+        }
+
+        // Make sure that the primary connection actually exists and has just been acquired.
+        for (SQLiteConnection acquiredConnection : mAcquiredConnections.keySet()) {
+            if (acquiredConnection.isPrimaryConnection()) {
+                return null;
+            }
+        }
+
+        // Uhoh.  No primary connection!  Either this is the first time we asked
+        // for it, or maybe it leaked?
+        connection = openConnectionLocked(true /*primaryConnection*/); // might throw
+        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
+        return connection;
+    }
+
+    // Might throw.
+    private SQLiteConnection tryAcquireNonPrimaryConnectionLocked(
+            String sql, int connectionFlags) {
+        // Try to acquire the next connection in the queue.
+        SQLiteConnection connection;
+        final int availableCount = mAvailableNonPrimaryConnections.size();
+        if (availableCount > 1 && sql != null) {
+            // If we have a choice, then prefer a connection that has the
+            // prepared statement in its cache.
+            for (int i = 0; i < availableCount; i++) {
+                connection = mAvailableNonPrimaryConnections.get(i);
+                if (connection.isPreparedStatementInCache(sql)) {
+                    mAvailableNonPrimaryConnections.remove(i);
+                    finishAcquireConnectionLocked(connection, connectionFlags); // might throw
+                    return connection;
+                }
+            }
+        }
+        if (availableCount > 0) {
+            // Otherwise, just grab the next one.
+            connection = mAvailableNonPrimaryConnections.remove(availableCount - 1);
+            finishAcquireConnectionLocked(connection, connectionFlags); // might throw
+            return connection;
+        }
+
+        // Expand the pool if needed.
+        int openConnections = mAcquiredConnections.size();
+        if (mAvailablePrimaryConnection != null) {
+            openConnections += 1;
+        }
+        if (openConnections >= mConfiguration.maxConnectionPoolSize) {
+            return null;
+        }
+        connection = openConnectionLocked(false /*primaryConnection*/); // might throw
+        finishAcquireConnectionLocked(connection, connectionFlags); // might throw
+        return connection;
+    }
+
+    // Might throw.
+    private void finishAcquireConnectionLocked(SQLiteConnection connection, int connectionFlags) {
+        try {
+            final boolean readOnly = (connectionFlags & CONNECTION_FLAG_READ_ONLY) != 0;
+            connection.setOnlyAllowReadOnlyOperations(readOnly);
+
+            mAcquiredConnections.put(connection, Boolean.FALSE);
+        } catch (RuntimeException ex) {
+            Log.e(TAG, "Failed to prepare acquired connection for session, closing it: "
+                    + connection +", connectionFlags=" + connectionFlags);
+            closeConnectionAndLogExceptionsLocked(connection);
+            throw ex; // rethrow!
+        }
+    }
+
+    private boolean isSessionBlockingImportantConnectionWaitersLocked(
+            boolean holdingPrimaryConnection, int connectionFlags) {
+        ConnectionWaiter waiter = mConnectionWaiterQueue;
+        if (waiter != null) {
+            final int priority = getPriority(connectionFlags);
+            do {
+                // Only worry about blocked connections that have same or lower priority.
+                if (priority > waiter.mPriority) {
+                    break;
+                }
+
+                // If we are holding the primary connection then we are blocking the waiter.
+                // Likewise, if we are holding a non-primary connection and the waiter
+                // would accept a non-primary connection, then we are blocking the waier.
+                if (holdingPrimaryConnection || !waiter.mWantPrimaryConnection) {
+                    return true;
+                }
+
+                waiter = waiter.mNext;
+            } while (waiter != null);
+        }
+        return false;
+    }
+
+    private static int getPriority(int connectionFlags) {
+        return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0;
+    }
+
+    private void throwIfClosedLocked() {
+        if (!mIsOpen) {
+            throw new IllegalStateException("Cannot perform this operation "
+                    + "because the connection pool have been closed.");
+        }
+    }
+
+    private ConnectionWaiter obtainConnectionWaiterLocked(Thread thread, long startTime,
+            int priority, boolean wantPrimaryConnection, String sql, int connectionFlags) {
+        ConnectionWaiter waiter = mConnectionWaiterPool;
+        if (waiter != null) {
+            mConnectionWaiterPool = waiter.mNext;
+            waiter.mNext = null;
+        } else {
+            waiter = new ConnectionWaiter();
+        }
+        waiter.mThread = thread;
+        waiter.mStartTime = startTime;
+        waiter.mPriority = priority;
+        waiter.mWantPrimaryConnection = wantPrimaryConnection;
+        waiter.mSql = sql;
+        waiter.mConnectionFlags = connectionFlags;
+        return waiter;
+    }
+
+    private void recycleConnectionWaiterLocked(ConnectionWaiter waiter) {
+        waiter.mNext = mConnectionWaiterPool;
+        waiter.mThread = null;
+        waiter.mSql = null;
+        waiter.mAssignedConnection = null;
+        waiter.mException = null;
+        mConnectionWaiterPool = waiter;
+    }
+
+    /**
+     * Dumps debugging information about this connection pool.
+     *
+     * @param printer The printer to receive the dump, not null.
+     */
+    public void dump(Printer printer) {
+        Printer indentedPrinter = PrefixPrinter.create(printer, "    ");
+        synchronized (mLock) {
+            printer.println("Connection pool for " + mConfiguration.path + ":");
+            printer.println("  Open: " + mIsOpen);
+            printer.println("  Max connections: " + mConfiguration.maxConnectionPoolSize);
+
+            printer.println("  Available primary connection:");
+            if (mAvailablePrimaryConnection != null) {
+                mAvailablePrimaryConnection.dump(indentedPrinter);
+            } else {
+                indentedPrinter.println("<none>");
+            }
+
+            printer.println("  Available non-primary connections:");
+            if (!mAvailableNonPrimaryConnections.isEmpty()) {
+                final int count = mAvailableNonPrimaryConnections.size();
+                for (int i = 0; i < count; i++) {
+                    mAvailableNonPrimaryConnections.get(i).dump(indentedPrinter);
+                }
+            } else {
+                indentedPrinter.println("<none>");
+            }
+
+            printer.println("  Acquired connections:");
+            if (!mAcquiredConnections.isEmpty()) {
+                for (Map.Entry<SQLiteConnection, Boolean> entry :
+                        mAcquiredConnections.entrySet()) {
+                    final SQLiteConnection connection = entry.getKey();
+                    connection.dumpUnsafe(indentedPrinter);
+                    indentedPrinter.println("  Pending reconfiguration: " + entry.getValue());
+                }
+            } else {
+                indentedPrinter.println("<none>");
+            }
+
+            printer.println("  Connection waiters:");
+            if (mConnectionWaiterQueue != null) {
+                int i = 0;
+                final long now = SystemClock.uptimeMillis();
+                for (ConnectionWaiter waiter = mConnectionWaiterQueue; waiter != null;
+                        waiter = waiter.mNext, i++) {
+                    indentedPrinter.println(i + ": waited for "
+                            + ((now - waiter.mStartTime) * 0.001f)
+                            + " ms - thread=" + waiter.mThread
+                            + ", priority=" + waiter.mPriority
+                            + ", sql='" + waiter.mSql + "'");
+                }
+            } else {
+                indentedPrinter.println("<none>");
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "SQLiteConnectionPool: " + mConfiguration.path;
+    }
+
+    private static final class ConnectionWaiter {
+        public ConnectionWaiter mNext;
+        public Thread mThread;
+        public long mStartTime;
+        public int mPriority;
+        public boolean mWantPrimaryConnection;
+        public String mSql;
+        public int mConnectionFlags;
+        public SQLiteConnection mAssignedConnection;
+        public RuntimeException mException;
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index 8dcedf2..9dcb498 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -43,7 +43,7 @@
     private final String[] mColumns;
 
     /** The query object for the cursor */
-    private SQLiteQuery mQuery;
+    private final SQLiteQuery mQuery;
 
     /** The compiled query this cursor came from */
     private final SQLiteCursorDriver mDriver;
@@ -96,9 +96,6 @@
         if (query == null) {
             throw new IllegalArgumentException("query object cannot be null");
         }
-        if (query.mDatabase == null) {
-            throw new IllegalArgumentException("query.mDatabase cannot be null");
-        }
         if (StrictMode.vmSqliteObjectLeaksEnabled()) {
             mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
         } else {
@@ -109,38 +106,21 @@
         mColumnNameMap = null;
         mQuery = query;
 
-        query.mDatabase.lock(query.mSql);
-        try {
-            // Setup the list of columns
-            int columnCount = mQuery.columnCountLocked();
-            mColumns = new String[columnCount];
-
-            // Read in all column names
-            for (int i = 0; i < columnCount; i++) {
-                String columnName = mQuery.columnNameLocked(i);
-                mColumns[i] = columnName;
-                if (false) {
-                    Log.v("DatabaseWindow", "mColumns[" + i + "] is "
-                            + mColumns[i]);
-                }
-    
-                // Make note of the row ID column index for quick access to it
-                if ("_id".equals(columnName)) {
-                    mRowIdColumnIndex = i;
-                }
+        mColumns = query.getColumnNames();
+        for (int i = 0; i < mColumns.length; i++) {
+            // Make note of the row ID column index for quick access to it
+            if ("_id".equals(mColumns[i])) {
+                mRowIdColumnIndex = i;
             }
-        } finally {
-            query.mDatabase.unlock();
         }
     }
 
     /**
+     * Get the database that this cursor is associated with.
      * @return the SQLiteDatabase that this cursor is associated with.
      */
     public SQLiteDatabase getDatabase() {
-        synchronized (this) {
-            return mQuery.mDatabase;
-        }
+        return mQuery.getDatabase();
     }
 
     @Override
@@ -167,7 +147,7 @@
 
         if (mCount == NO_COUNT) {
             int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
-            mCount = getQuery().fillWindow(mWindow, startPos, requiredPos, true);
+            mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
             mCursorWindowCapacity = mWindow.getNumRows();
             if (Log.isLoggable(TAG, Log.DEBUG)) {
                 Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
@@ -175,14 +155,10 @@
         } else {
             int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
                     mCursorWindowCapacity);
-            getQuery().fillWindow(mWindow, startPos, requiredPos, false);
+            mQuery.fillWindow(mWindow, startPos, requiredPos, false);
         }
     }
 
-    private synchronized SQLiteQuery getQuery() {
-        return mQuery;
-    }
-
     @Override
     public int getColumnIndex(String columnName) {
         // Create mColumnNameMap on demand
@@ -237,75 +213,28 @@
         if (isClosed()) {
             return false;
         }
-        long timeStart = 0;
-        if (false) {
-            timeStart = System.currentTimeMillis();
-        }
 
         synchronized (this) {
+            if (!mQuery.getDatabase().isOpen()) {
+                return false;
+            }
+
             if (mWindow != null) {
                 mWindow.clear();
             }
             mPos = -1;
-            SQLiteDatabase db = null;
-            try {
-                db = mQuery.mDatabase.getDatabaseHandle(mQuery.mSql);
-            } catch (IllegalStateException e) {
-                // for backwards compatibility, just return false
-                Log.w(TAG, "requery() failed " + e.getMessage(), e);
-                return false;
-            }
-            if (!db.equals(mQuery.mDatabase)) {
-                // since we need to use a different database connection handle,
-                // re-compile the query
-                try {
-                    db.lock(mQuery.mSql);
-                } catch (IllegalStateException e) {
-                    // for backwards compatibility, just return false
-                    Log.w(TAG, "requery() failed " + e.getMessage(), e);
-                    return false;
-                }
-                try {
-                    // close the old mQuery object and open a new one
-                    mQuery.close();
-                    mQuery = new SQLiteQuery(db, mQuery);
-                } catch (IllegalStateException e) {
-                    // for backwards compatibility, just return false
-                    Log.w(TAG, "requery() failed " + e.getMessage(), e);
-                    return false;
-                } finally {
-                    db.unlock();
-                }
-            }
-            // This one will recreate the temp table, and get its count
-            mDriver.cursorRequeried(this);
             mCount = NO_COUNT;
-            try {
-                mQuery.requery();
-            } catch (IllegalStateException e) {
-                // for backwards compatibility, just return false
-                Log.w(TAG, "requery() failed " + e.getMessage(), e);
-                return false;
-            }
+
+            mDriver.cursorRequeried(this);
         }
 
-        if (false) {
-            Log.v("DatabaseWindow", "closing window in requery()");
-            Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
-        }
-
-        boolean result = false;
         try {
-            result = super.requery();
+            return super.requery();
         } catch (IllegalStateException e) {
             // for backwards compatibility, just return false
             Log.w(TAG, "requery() failed " + e.getMessage(), e);
+            return false;
         }
-        if (false) {
-            long timeEnd = System.currentTimeMillis();
-            Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
-        }
-        return result;
     }
 
     @Override
@@ -330,20 +259,17 @@
             // if the cursor hasn't been closed yet, close it first
             if (mWindow != null) {
                 if (mStackTrace != null) {
-                    int len = mQuery.mSql.length();
+                    String sql = mQuery.getSql();
+                    int len = sql.length();
                     StrictMode.onSqliteObjectLeaked(
                         "Finalizing a Cursor that has not been deactivated or closed. " +
-                        "database = " + mQuery.mDatabase.getPath() + ", table = " + mEditTable +
-                        ", query = " + mQuery.mSql.substring(0, (len > 1000) ? 1000 : len),
+                        "database = " + mQuery.getDatabase().getLabel() +
+                        ", table = " + mEditTable +
+                        ", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
                         mStackTrace);
                 }
                 close();
                 SQLiteDebug.notifyActiveCursorFinalized();
-            } else {
-                if (false) {
-                    Log.v(TAG, "Finalizing cursor on database = " + mQuery.mDatabase.getPath() +
-                            ", table = " + mEditTable + ", query = " + mQuery.mSql);
-                }
             }
         } finally {
             super.finalize();
diff --git a/core/java/android/database/sqlite/SQLiteCursorDriver.java b/core/java/android/database/sqlite/SQLiteCursorDriver.java
index b3963f9..ad2cdd2 100644
--- a/core/java/android/database/sqlite/SQLiteCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteCursorDriver.java
@@ -39,7 +39,7 @@
     void cursorDeactivated();
 
     /**
-     * Called by a SQLiteCursor when it is requeryed.
+     * Called by a SQLiteCursor when it is requeried.
      */
     void cursorRequeried(Cursor cursor);
 
diff --git a/core/java/android/database/sqlite/SQLiteCustomFunction.java b/core/java/android/database/sqlite/SQLiteCustomFunction.java
new file mode 100644
index 0000000..02f3284
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteCustomFunction.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+/**
+ * Describes a custom SQL function.
+ *
+ * @hide
+ */
+public final class SQLiteCustomFunction {
+    public final String name;
+    public final int numArgs;
+    public final SQLiteDatabase.CustomFunction callback;
+
+    /**
+     * Create custom function.
+     *
+     * @param name The name of the sqlite3 function.
+     * @param numArgs The number of arguments for the function, or -1 to
+     * support any number of arguments.
+     * @param callback The callback to invoke when the function is executed.
+     */
+    public SQLiteCustomFunction(String name, int numArgs,
+            SQLiteDatabase.CustomFunction callback) {
+        if (name == null) {
+            throw new IllegalArgumentException("name must not be null.");
+        }
+
+        this.name = name;
+        this.numArgs = numArgs;
+        this.callback = callback;
+    }
+
+    // Called from native.
+    @SuppressWarnings("unused")
+    private void dispatchCallback(String[] args) {
+        callback.callback(args);
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index f990be6..377a680 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -16,7 +16,6 @@
 
 package android.database.sqlite;
 
-import android.app.AppGlobals;
 import android.content.ContentValues;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -25,61 +24,117 @@
 import android.database.DefaultDatabaseErrorHandler;
 import android.database.SQLException;
 import android.database.sqlite.SQLiteDebug.DbStats;
-import android.os.Debug;
-import android.os.StatFs;
-import android.os.SystemClock;
-import android.os.SystemProperties;
+import android.os.Looper;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
-import android.util.LruCache;
 import android.util.Pair;
-import dalvik.system.BlockGuard;
+import android.util.Printer;
+
+import dalvik.system.CloseGuard;
+
 import java.io.File;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Random;
 import java.util.WeakHashMap;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.regex.Pattern;
 
 /**
  * Exposes methods to manage a SQLite database.
- * <p>SQLiteDatabase has methods to create, delete, execute SQL commands, and
+ *
+ * <p>
+ * SQLiteDatabase has methods to create, delete, execute SQL commands, and
  * perform other common database management tasks.
- * <p>See the Notepad sample application in the SDK for an example of creating
+ * </p><p>
+ * See the Notepad sample application in the SDK for an example of creating
  * and managing a database.
- * <p> Database names must be unique within an application, not across all
- * applications.
+ * </p><p>
+ * Database names must be unique within an application, not across all applications.
+ * </p>
  *
  * <h3>Localized Collation - ORDER BY</h3>
- * <p>In addition to SQLite's default <code>BINARY</code> collator, Android supplies
- * two more, <code>LOCALIZED</code>, which changes with the system's current locale
- * if you wire it up correctly (XXX a link needed!), and <code>UNICODE</code>, which
- * is the Unicode Collation Algorithm and not tailored to the current locale.
+ * <p>
+ * In addition to SQLite's default <code>BINARY</code> collator, Android supplies
+ * two more, <code>LOCALIZED</code>, which changes with the system's current locale,
+ * and <code>UNICODE</code>, which is the Unicode Collation Algorithm and not tailored
+ * to the current locale.
+ * </p>
  */
 public class SQLiteDatabase extends SQLiteClosable {
     private static final String TAG = "SQLiteDatabase";
-    private static final boolean ENABLE_DB_SAMPLE = false; // true to enable stats in event log
-    private static final int EVENT_DB_OPERATION = 52000;
+
     private static final int EVENT_DB_CORRUPT = 75004;
 
+    // Stores reference to all databases opened in the current process.
+    // (The referent Object is not used at this time.)
+    // INVARIANT: Guarded by sActiveDatabases.
+    private static WeakHashMap<SQLiteDatabase, Object> sActiveDatabases =
+            new WeakHashMap<SQLiteDatabase, Object>();
+
+    // Thread-local for database sessions that belong to this database.
+    // Each thread has its own database session.
+    // INVARIANT: Immutable.
+    private final ThreadLocal<SQLiteSession> mThreadSession = new ThreadLocal<SQLiteSession>() {
+        @Override
+        protected SQLiteSession initialValue() {
+            return createSession();
+        }
+    };
+
+    // The optional factory to use when creating new Cursors.  May be null.
+    // INVARIANT: Immutable.
+    private final CursorFactory mCursorFactory;
+
+    // Error handler to be used when SQLite returns corruption errors.
+    // INVARIANT: Immutable.
+    private final DatabaseErrorHandler mErrorHandler;
+
+    // Shared database state lock.
+    // This lock guards all of the shared state of the database, such as its
+    // configuration, whether it is open or closed, and so on.  This lock should
+    // be held for as little time as possible.
+    //
+    // The lock MUST NOT be held while attempting to acquire database connections or
+    // while executing SQL statements on behalf of the client as it can lead to deadlock.
+    //
+    // It is ok to hold the lock while reconfiguring the connection pool or dumping
+    // statistics because those operations are non-reentrant and do not try to acquire
+    // connections that might be held by other threads.
+    //
+    // Basic rule: grab the lock, access or modify global state, release the lock, then
+    // do the required SQL work.
+    private final Object mLock = new Object();
+
+    // Warns if the database is finalized without being closed properly.
+    // INVARIANT: Guarded by mLock.
+    private final CloseGuard mCloseGuardLocked = CloseGuard.get();
+
+    // The database configuration.
+    // INVARIANT: Guarded by mLock.
+    private final SQLiteDatabaseConfiguration mConfigurationLocked;
+
+    // The connection pool for the database, null when closed.
+    // The pool itself is thread-safe, but the reference to it can only be acquired
+    // when the lock is held.
+    // INVARIANT: Guarded by mLock.
+    private SQLiteConnectionPool mConnectionPoolLocked;
+
+    // True if the database has attached databases.
+    // INVARIANT: Guarded by mLock.
+    private boolean mHasAttachedDbsLocked;
+
+    // True if the database is in WAL mode.
+    // INVARIANT: Guarded by mLock.
+    private boolean mIsWALEnabledLocked;
+
     /**
-     * Algorithms used in ON CONFLICT clause
-     * http://www.sqlite.org/lang_conflict.html
-     */
-    /**
-     *  When a constraint violation occurs, an immediate ROLLBACK occurs,
+     * When a constraint violation occurs, an immediate ROLLBACK occurs,
      * thus ending the current transaction, and the command aborts with a
      * return code of SQLITE_CONSTRAINT. If no transaction is active
      * (other than the implied transaction that is created on every command)
-     *  then this algorithm works the same as ABORT.
+     * then this algorithm works the same as ABORT.
      */
     public static final int CONFLICT_ROLLBACK = 1;
 
@@ -118,14 +173,15 @@
      * violation occurs then the IGNORE algorithm is used. When this conflict
      * resolution strategy deletes rows in order to satisfy a constraint,
      * it does not invoke delete triggers on those rows.
-     *  This behavior might change in a future release.
+     * This behavior might change in a future release.
      */
     public static final int CONFLICT_REPLACE = 5;
 
     /**
-     * use the following when no conflict action is specified.
+     * Use the following when no conflict action is specified.
      */
     public static final int CONFLICT_NONE = 0;
+
     private static final String[] CONFLICT_VALUES = new String[]
             {"", " OR ROLLBACK ", " OR ABORT ", " OR FAIL ", " OR IGNORE ", " OR REPLACE "};
 
@@ -146,7 +202,7 @@
     public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000;
 
     /**
-     * Flag for {@link #openDatabase} to open the database for reading and writing.
+     * Open flag: Flag for {@link #openDatabase} to open the database for reading and writing.
      * If the disk is full, this may fail even before you actually write anything.
      *
      * {@more} Note that the value of this flag is 0, so it is the default.
@@ -154,7 +210,7 @@
     public static final int OPEN_READWRITE = 0x00000000;          // update native code if changing
 
     /**
-     * Flag for {@link #openDatabase} to open the database for reading only.
+     * Open flag: Flag for {@link #openDatabase} to open the database for reading only.
      * This is the only reliable way to open a database if the disk may be full.
      */
     public static final int OPEN_READONLY = 0x00000001;           // update native code if changing
@@ -162,7 +218,8 @@
     private static final int OPEN_READ_MASK = 0x00000001;         // update native code if changing
 
     /**
-     * Flag for {@link #openDatabase} to open the database without support for localized collators.
+     * Open flag: Flag for {@link #openDatabase} to open the database without support for
+     * localized collators.
      *
      * {@more} This causes the collator <code>LOCALIZED</code> not to be created.
      * You must be consistent when using this flag to use the setting the database was
@@ -171,190 +228,62 @@
     public static final int NO_LOCALIZED_COLLATORS = 0x00000010;  // update native code if changing
 
     /**
-     * Flag for {@link #openDatabase} to create the database file if it does not already exist.
+     * Open flag: Flag for {@link #openDatabase} to create the database file if it does not
+     * already exist.
      */
     public static final int CREATE_IF_NECESSARY = 0x10000000;     // update native code if changing
 
     /**
-     * Indicates whether the most-recently started transaction has been marked as successful.
-     */
-    private boolean mInnerTransactionIsSuccessful;
-
-    /**
-     * Valid during the life of a transaction, and indicates whether the entire transaction (the
-     * outer one and all of the inner ones) so far has been successful.
-     */
-    private boolean mTransactionIsSuccessful;
-
-    /**
-     * Valid during the life of a transaction.
-     */
-    private SQLiteTransactionListener mTransactionListener;
-
-    /**
-     * this member is set if {@link #execSQL(String)} is used to begin and end transactions.
-     */
-    private boolean mTransactionUsingExecSql;
-
-    /** Synchronize on this when accessing the database */
-    private final DatabaseReentrantLock mLock = new DatabaseReentrantLock(true);
-
-    private long mLockAcquiredWallTime = 0L;
-    private long mLockAcquiredThreadTime = 0L;
-
-    // limit the frequency of complaints about each database to one within 20 sec
-    // unless run command adb shell setprop log.tag.Database VERBOSE
-    private static final int LOCK_WARNING_WINDOW_IN_MS = 20000;
-    /** If the lock is held this long then a warning will be printed when it is released. */
-    private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS = 300;
-    private static final int LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS = 100;
-    private static final int LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT = 2000;
-
-    private static final int SLEEP_AFTER_YIELD_QUANTUM = 1000;
-
-    // The pattern we remove from database filenames before
-    // potentially logging them.
-    private static final Pattern EMAIL_IN_DB_PATTERN = Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
-
-    private long mLastLockMessageTime = 0L;
-
-    // Things related to query logging/sampling for debugging
-    // slow/frequent queries during development.  Always log queries
-    // which take (by default) 500ms+; shorter queries are sampled
-    // accordingly.  Commit statements, which are typically slow, are
-    // logged together with the most recently executed SQL statement,
-    // for disambiguation.  The 500ms value is configurable via a
-    // SystemProperty, but developers actively debugging database I/O
-    // should probably use the regular log tunable,
-    // LOG_SLOW_QUERIES_PROPERTY, defined below.
-    private static int sQueryLogTimeInMillis = 0;  // lazily initialized
-    private static final int QUERY_LOG_SQL_LENGTH = 64;
-    private static final String COMMIT_SQL = "COMMIT;";
-    private static final String BEGIN_SQL = "BEGIN;";
-    private final Random mRandom = new Random();
-    /** the last non-commit/rollback sql statement in a transaction */
-    // guarded by 'this'
-    private String mLastSqlStatement = null;
-
-    synchronized String getLastSqlStatement() {
-        return mLastSqlStatement;
-    }
-
-    synchronized void setLastSqlStatement(String sql) {
-        mLastSqlStatement = sql;
-    }
-
-    /** guarded by {@link #mLock} */
-    private long mTransStartTime;
-
-    // String prefix for slow database query EventLog records that show
-    // lock acquistions of the database.
-    /* package */ static final String GET_LOCK_LOG_PREFIX = "GETLOCK:";
-
-    /** Used by native code, do not rename. make it volatile, so it is thread-safe. */
-    /* package */ volatile int mNativeHandle = 0;
-
-    /**
-     * The size, in bytes, of a block on "/data". This corresponds to the Unix
-     * statfs.f_bsize field. note that this field is lazily initialized.
-     */
-    private static int sBlockSize = 0;
-
-    /** The path for the database file */
-    private final String mPath;
-
-    /** The anonymized path for the database file for logging purposes */
-    private String mPathForLogs = null;  // lazily populated
-
-    /** The flags passed to open/create */
-    private final int mFlags;
-
-    /** The optional factory to use when creating new Cursors */
-    private final CursorFactory mFactory;
-
-    private final WeakHashMap<SQLiteClosable, Object> mPrograms;
-
-    /** Default statement-cache size per database connection ( = instance of this class) */
-    private static final int DEFAULT_SQL_CACHE_SIZE = 25;
-
-    /**
-     * for each instance of this class, a LRU cache is maintained to store
-     * the compiled query statement ids returned by sqlite database.
-     *     key = SQL statement with "?" for bind args
-     *     value = {@link SQLiteCompiledSql}
-     * If an application opens the database and keeps it open during its entire life, then
-     * there will not be an overhead of compilation of SQL statements by sqlite.
+     * Absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}.
      *
-     * why is this cache NOT static? because sqlite attaches compiledsql statements to the
-     * struct created when {@link SQLiteDatabase#openDatabase(String, CursorFactory, int)} is
-     * invoked.
-     *
-     * this cache's max size is settable by calling the method
-     * (@link #setMaxSqlCacheSize(int)}.
-     */
-    // guarded by this
-    private LruCache<String, SQLiteCompiledSql> mCompiledQueries;
-
-    /**
-     * absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}
-     * size of each prepared-statement is between 1K - 6K, depending on the complexity of the
-     * SQL statement & schema.
+     * Each prepared-statement is between 1K - 6K, depending on the complexity of the
+     * SQL statement & schema.  A large SQL cache may use a significant amount of memory.
      */
     public static final int MAX_SQL_CACHE_SIZE = 100;
-    private boolean mCacheFullWarning;
 
-    /** Used to find out where this object was created in case it never got closed. */
-    private final Throwable mStackTrace;
-
-    /** stores the list of statement ids that need to be finalized by sqlite */
-    private final ArrayList<Integer> mClosedStatementIds = new ArrayList<Integer>();
-
-    /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors
-     *    Corruption
-     * */
-    private final DatabaseErrorHandler mErrorHandler;
-
-    /** The Database connection pool {@link DatabaseConnectionPool}.
-     * Visibility is package-private for testing purposes. otherwise, private visibility is enough.
-     */
-    /* package */ volatile DatabaseConnectionPool mConnectionPool = null;
-
-    /** Each database connection handle in the pool is assigned a number 1..N, where N is the
-     * size of the connection pool.
-     * The main connection handle to which the pool is attached is assigned a value of 0.
-     */
-    /* package */ final short mConnectionNum;
-
-    /** on pooled database connections, this member points to the parent ( = main)
-     * database connection handle.
-     * package visibility only for testing purposes
-     */
-    /* package */ SQLiteDatabase mParentConnObj = null;
-
-    private static final String MEMORY_DB_PATH = ":memory:";
-
-    /** set to true if the database has attached databases */
-    private volatile boolean mHasAttachedDbs = false;
-
-    /** stores reference to all databases opened in the current process. */
-    private static ArrayList<WeakReference<SQLiteDatabase>> mActiveDatabases =
-            new ArrayList<WeakReference<SQLiteDatabase>>();
-
-    synchronized void addSQLiteClosable(SQLiteClosable closable) {
-        // mPrograms is per instance of SQLiteDatabase and it doesn't actually touch the database
-        // itself. so, there is no need to lock().
-        mPrograms.put(closable, null);
+    private SQLiteDatabase(String path, int openFlags, CursorFactory cursorFactory,
+            DatabaseErrorHandler errorHandler) {
+        mCursorFactory = cursorFactory;
+        mErrorHandler = errorHandler != null ? errorHandler : new DefaultDatabaseErrorHandler();
+        mConfigurationLocked = new SQLiteDatabaseConfiguration(path, openFlags);
     }
 
-    synchronized void removeSQLiteClosable(SQLiteClosable closable) {
-        mPrograms.remove(closable);
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
     }
 
     @Override
     protected void onAllReferencesReleased() {
-        if (isOpen()) {
-            // close the database which will close all pending statements to be finalized also
-            close();
+        dispose(false);
+    }
+
+    private void dispose(boolean finalized) {
+        final SQLiteConnectionPool pool;
+        synchronized (mLock) {
+            if (mCloseGuardLocked != null) {
+                if (finalized) {
+                    mCloseGuardLocked.warnIfOpen();
+                }
+                mCloseGuardLocked.close();
+            }
+
+            pool = mConnectionPoolLocked;
+            mConnectionPoolLocked = null;
+        }
+
+        if (!finalized) {
+            synchronized (sActiveDatabases) {
+                sActiveDatabases.remove(this);
+            }
+
+            if (pool != null) {
+                pool.close();
+            }
         }
     }
 
@@ -364,7 +293,9 @@
      *
      * @return the number of bytes actually released
      */
-    static public native int releaseMemory();
+    public static int releaseMemory() {
+        return SQLiteGlobal.releaseMemory();
+    }
 
     /**
      * Control whether or not the SQLiteDatabase is made thread-safe by using locks
@@ -372,159 +303,82 @@
      * DB will only be used by a single thread then you should set this to false.
      * The default is true.
      * @param lockingEnabled set to true to enable locks, false otherwise
+     *
+     * @deprecated This method now does nothing.  Do not use.
      */
+    @Deprecated
     public void setLockingEnabled(boolean lockingEnabled) {
-        mLockingEnabled = lockingEnabled;
     }
 
     /**
-     * If set then the SQLiteDatabase is made thread-safe by using locks
-     * around critical sections
+     * Gets a label to use when describing the database in log messages.
+     * @return The label.
      */
-    private boolean mLockingEnabled = true;
+    String getLabel() {
+        synchronized (mLock) {
+            return mConfigurationLocked.label;
+        }
+    }
 
-    /* package */ void onCorruption() {
-        EventLog.writeEvent(EVENT_DB_CORRUPT, mPath);
+    /**
+     * Sends a corruption message to the database error handler.
+     */
+    void onCorruption() {
+        EventLog.writeEvent(EVENT_DB_CORRUPT, getLabel());
         mErrorHandler.onCorruption(this);
     }
 
     /**
-     * Locks the database for exclusive access. The database lock must be held when
-     * touch the native sqlite3* object since it is single threaded and uses
-     * a polling lock contention algorithm. The lock is recursive, and may be acquired
-     * multiple times by the same thread. This is a no-op if mLockingEnabled is false.
+     * Gets the {@link SQLiteSession} that belongs to this thread for this database.
+     * Once a thread has obtained a session, it will continue to obtain the same
+     * session even after the database has been closed (although the session will not
+     * be usable).  However, a thread that does not already have a session cannot
+     * obtain one after the database has been closed.
      *
-     * @see #unlock()
+     * The idea is that threads that have active connections to the database may still
+     * have work to complete even after the call to {@link #close}.  Active database
+     * connections are not actually disposed until they are released by the threads
+     * that own them.
+     *
+     * @return The session, never null.
+     *
+     * @throws IllegalStateException if the thread does not yet have a session and
+     * the database is not open.
      */
-    /* package */ void lock(String sql) {
-        lock(sql, false);
+    SQLiteSession getThreadSession() {
+        return mThreadSession.get(); // initialValue() throws if database closed
     }
 
-    /* pachage */ void lock() {
-        lock(null, false);
-    }
-
-    private static final long LOCK_WAIT_PERIOD = 30L;
-    private void lock(String sql, boolean forced) {
-        // make sure this method is NOT being called from a 'synchronized' method
-        if (Thread.holdsLock(this)) {
-            Log.w(TAG, "don't lock() while in a synchronized method");
+    SQLiteSession createSession() {
+        final SQLiteConnectionPool pool;
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+            pool = mConnectionPoolLocked;
         }
-        verifyDbIsOpen();
-        if (!forced && !mLockingEnabled) return;
-        boolean done = false;
-        long timeStart = SystemClock.uptimeMillis();
-        while (!done) {
-            try {
-                // wait for 30sec to acquire the lock
-                done = mLock.tryLock(LOCK_WAIT_PERIOD, TimeUnit.SECONDS);
-                if (!done) {
-                    // lock not acquired in NSec. print a message and stacktrace saying the lock
-                    // has not been available for 30sec.
-                    Log.w(TAG, "database lock has not been available for " + LOCK_WAIT_PERIOD +
-                            " sec. Current Owner of the lock is " + mLock.getOwnerDescription() +
-                            ". Continuing to wait in thread: " + Thread.currentThread().getId());
-                }
-            } catch (InterruptedException e) {
-                // ignore the interruption
-            }
-        }
-        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
-            if (mLock.getHoldCount() == 1) {
-                // Use elapsed real-time since the CPU may sleep when waiting for IO
-                mLockAcquiredWallTime = SystemClock.elapsedRealtime();
-                mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
-            }
-        }
-        if (sql != null) {
-            if (ENABLE_DB_SAMPLE)  {
-                logTimeStat(sql, timeStart, GET_LOCK_LOG_PREFIX);
-            }
-        }
-    }
-    private static class DatabaseReentrantLock extends ReentrantLock {
-        DatabaseReentrantLock(boolean fair) {
-            super(fair);
-        }
-        @Override
-        public Thread getOwner() {
-            return super.getOwner();
-        }
-        public String getOwnerDescription() {
-            Thread t = getOwner();
-            return (t== null) ? "none" : String.valueOf(t.getId());
-        }
+        return new SQLiteSession(pool);
     }
 
     /**
-     * Locks the database for exclusive access. The database lock must be held when
-     * touch the native sqlite3* object since it is single threaded and uses
-     * a polling lock contention algorithm. The lock is recursive, and may be acquired
-     * multiple times by the same thread.
+     * Gets default connection flags that are appropriate for this thread, taking into
+     * account whether the thread is acting on behalf of the UI.
      *
-     * @see #unlockForced()
+     * @param readOnly True if the connection should be read-only.
+     * @return The connection flags.
      */
-    private void lockForced() {
-        lock(null, true);
+    int getThreadDefaultConnectionFlags(boolean readOnly) {
+        int flags = readOnly ? SQLiteConnectionPool.CONNECTION_FLAG_READ_ONLY :
+                SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY;
+        if (isMainThread()) {
+            flags |= SQLiteConnectionPool.CONNECTION_FLAG_INTERACTIVE;
+        }
+        return flags;
     }
 
-    private void lockForced(String sql) {
-        lock(sql, true);
-    }
-
-    /**
-     * Releases the database lock. This is a no-op if mLockingEnabled is false.
-     *
-     * @see #unlock()
-     */
-    /* package */ void unlock() {
-        if (!mLockingEnabled) return;
-        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
-            if (mLock.getHoldCount() == 1) {
-                checkLockHoldTime();
-            }
-        }
-        mLock.unlock();
-    }
-
-    /**
-     * Releases the database lock.
-     *
-     * @see #unlockForced()
-     */
-    private void unlockForced() {
-        if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING) {
-            if (mLock.getHoldCount() == 1) {
-                checkLockHoldTime();
-            }
-        }
-        mLock.unlock();
-    }
-
-    private void checkLockHoldTime() {
-        // Use elapsed real-time since the CPU may sleep when waiting for IO
-        long elapsedTime = SystemClock.elapsedRealtime();
-        long lockedTime = elapsedTime - mLockAcquiredWallTime;
-        if (lockedTime < LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT &&
-                !Log.isLoggable(TAG, Log.VERBOSE) &&
-                (elapsedTime - mLastLockMessageTime) < LOCK_WARNING_WINDOW_IN_MS) {
-            return;
-        }
-        if (lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS) {
-            int threadTime = (int)
-                    ((Debug.threadCpuTimeNanos() - mLockAcquiredThreadTime) / 1000000);
-            if (threadTime > LOCK_ACQUIRED_WARNING_THREAD_TIME_IN_MS ||
-                    lockedTime > LOCK_ACQUIRED_WARNING_TIME_IN_MS_ALWAYS_PRINT) {
-                mLastLockMessageTime = elapsedTime;
-                String msg = "lock held on " + mPath + " for " + lockedTime + "ms. Thread time was "
-                        + threadTime + "ms";
-                if (SQLiteDebug.DEBUG_LOCK_TIME_TRACKING_STACK_TRACE) {
-                    Log.d(TAG, msg, new Exception());
-                } else {
-                    Log.d(TAG, msg);
-                }
-            }
-        }
+    private static boolean isMainThread() {
+        // FIXME: There should be a better way to do this.
+        // Would also be nice to have something that would work across Binder calls.
+        Looper looper = Looper.myLooper();
+        return looper != null && looper == Looper.getMainLooper();
     }
 
     /**
@@ -636,50 +490,9 @@
 
     private void beginTransaction(SQLiteTransactionListener transactionListener,
             boolean exclusive) {
-        verifyDbIsOpen();
-        lockForced(BEGIN_SQL);
-        boolean ok = false;
-        try {
-            // If this thread already had the lock then get out
-            if (mLock.getHoldCount() > 1) {
-                if (mInnerTransactionIsSuccessful) {
-                    String msg = "Cannot call beginTransaction between "
-                            + "calling setTransactionSuccessful and endTransaction";
-                    IllegalStateException e = new IllegalStateException(msg);
-                    Log.e(TAG, "beginTransaction() failed", e);
-                    throw e;
-                }
-                ok = true;
-                return;
-            }
-
-            // This thread didn't already have the lock, so begin a database
-            // transaction now.
-            if (exclusive && mConnectionPool == null) {
-                execSQL("BEGIN EXCLUSIVE;");
-            } else {
-                execSQL("BEGIN IMMEDIATE;");
-            }
-            mTransStartTime = SystemClock.uptimeMillis();
-            mTransactionListener = transactionListener;
-            mTransactionIsSuccessful = true;
-            mInnerTransactionIsSuccessful = false;
-            if (transactionListener != null) {
-                try {
-                    transactionListener.onBegin();
-                } catch (RuntimeException e) {
-                    execSQL("ROLLBACK;");
-                    throw e;
-                }
-            }
-            ok = true;
-        } finally {
-            if (!ok) {
-                // beginTransaction is called before the try block so we must release the lock in
-                // the case of failure.
-                unlockForced();
-            }
-        }
+        getThreadSession().beginTransaction(exclusive ? SQLiteSession.TRANSACTION_MODE_EXCLUSIVE :
+                SQLiteSession.TRANSACTION_MODE_IMMEDIATE, transactionListener,
+                getThreadDefaultConnectionFlags(false /*readOnly*/));
     }
 
     /**
@@ -687,68 +500,7 @@
      * are committed and rolled back.
      */
     public void endTransaction() {
-        verifyLockOwner();
-        try {
-            if (mInnerTransactionIsSuccessful) {
-                mInnerTransactionIsSuccessful = false;
-            } else {
-                mTransactionIsSuccessful = false;
-            }
-            if (mLock.getHoldCount() != 1) {
-                return;
-            }
-            RuntimeException savedException = null;
-            if (mTransactionListener != null) {
-                try {
-                    if (mTransactionIsSuccessful) {
-                        mTransactionListener.onCommit();
-                    } else {
-                        mTransactionListener.onRollback();
-                    }
-                } catch (RuntimeException e) {
-                    savedException = e;
-                    mTransactionIsSuccessful = false;
-                }
-            }
-            if (mTransactionIsSuccessful) {
-                execSQL(COMMIT_SQL);
-                // if write-ahead logging is used, we have to take care of checkpoint.
-                // TODO: should applications be given the flexibility of choosing when to
-                // trigger checkpoint?
-                // for now, do checkpoint after every COMMIT because that is the fastest
-                // way to guarantee that readers will see latest data.
-                // but this is the slowest way to run sqlite with in write-ahead logging mode.
-                if (this.mConnectionPool != null) {
-                    execSQL("PRAGMA wal_checkpoint;");
-                    if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
-                        Log.i(TAG, "PRAGMA wal_Checkpoint done");
-                    }
-                }
-                // log the transaction time to the Eventlog.
-                if (ENABLE_DB_SAMPLE) {
-                    logTimeStat(getLastSqlStatement(), mTransStartTime, COMMIT_SQL);
-                }
-            } else {
-                try {
-                    execSQL("ROLLBACK;");
-                    if (savedException != null) {
-                        throw savedException;
-                    }
-                } catch (SQLException e) {
-                    if (false) {
-                        Log.d(TAG, "exception during rollback, maybe the DB previously "
-                                + "performed an auto-rollback");
-                    }
-                }
-            }
-        } finally {
-            mTransactionListener = null;
-            unlockForced();
-            if (false) {
-                Log.v(TAG, "unlocked " + Thread.currentThread()
-                        + ", holdCount is " + mLock.getHoldCount());
-            }
-        }
+        getThreadSession().endTransaction();
     }
 
     /**
@@ -761,86 +513,46 @@
      * transaction is already marked as successful.
      */
     public void setTransactionSuccessful() {
-        verifyDbIsOpen();
-        if (!mLock.isHeldByCurrentThread()) {
-            throw new IllegalStateException("no transaction pending");
-        }
-        if (mInnerTransactionIsSuccessful) {
-            throw new IllegalStateException(
-                    "setTransactionSuccessful may only be called once per call to beginTransaction");
-        }
-        mInnerTransactionIsSuccessful = true;
+        getThreadSession().setTransactionSuccessful();
     }
 
     /**
-     * return true if there is a transaction pending
+     * Returns true if the current thread has a transaction pending.
+     *
+     * @return True if the current thread is in a transaction.
      */
     public boolean inTransaction() {
-        return mLock.getHoldCount() > 0 || mTransactionUsingExecSql;
-    }
-
-    /* package */ synchronized void setTransactionUsingExecSqlFlag() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.i(TAG, "found execSQL('begin transaction')");
-        }
-        mTransactionUsingExecSql = true;
-    }
-
-    /* package */ synchronized void resetTransactionUsingExecSqlFlag() {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            if (mTransactionUsingExecSql) {
-                Log.i(TAG, "found execSQL('commit or end or rollback')");
-            }
-        }
-        mTransactionUsingExecSql = false;
+        return getThreadSession().hasTransaction();
     }
 
     /**
-     * Returns true if the caller is considered part of the current transaction, if any.
+     * Returns true if the current thread is holding an active connection to the database.
      * <p>
-     * Caller is part of the current transaction if either of the following is true
-     * <ol>
-     *   <li>If transaction is started by calling beginTransaction() methods AND if the caller is
-     *   in the same thread as the thread that started the transaction.
-     *   </li>
-     *   <li>If the transaction is started by calling {@link #execSQL(String)} like this:
-     *   execSQL("BEGIN transaction"). In this case, every thread in the process is considered
-     *   part of the current transaction.</li>
-     * </ol>
+     * The name of this method comes from a time when having an active connection
+     * to the database meant that the thread was holding an actual lock on the
+     * database.  Nowadays, there is no longer a true "database lock" although threads
+     * may block if they cannot acquire a database connection to perform a
+     * particular operation.
+     * </p>
      *
-     * @return true if the caller is considered part of the current transaction, if any.
-     */
-    /* package */ synchronized boolean amIInTransaction() {
-        // always do this test on the main database connection - NOT on pooled database connection
-        // since transactions always occur on the main database connections only.
-        SQLiteDatabase db = (isPooledConnection()) ? mParentConnObj : this;
-        boolean b = (!db.inTransaction()) ? false :
-                db.mTransactionUsingExecSql || db.mLock.isHeldByCurrentThread();
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.i(TAG, "amIinTransaction: " + b);
-        }
-        return b;
-    }
-
-    /**
-     * Checks if the database lock is held by this thread.
-     *
-     * @return true, if this thread is holding the database lock.
+     * @return True if the current thread is holding an active connection to the database.
      */
     public boolean isDbLockedByCurrentThread() {
-        return mLock.isHeldByCurrentThread();
+        return getThreadSession().hasConnection();
     }
 
     /**
-     * Checks if the database is locked by another thread. This is
-     * just an estimate, since this status can change at any time,
-     * including after the call is made but before the result has
-     * been acted upon.
+     * Always returns false.
+     * <p>
+     * There is no longer the concept of a database lock, so this method always returns false.
+     * </p>
      *
-     * @return true, if the database is locked by another thread
+     * @return False.
+     * @deprecated Always returns false.  Do not use this method.
      */
+    @Deprecated
     public boolean isDbLockedByOtherThreads() {
-        return !mLock.isHeldByCurrentThread() && mLock.isLocked();
+        return false;
     }
 
     /**
@@ -884,46 +596,12 @@
         return yieldIfContendedHelper(true /* check yielding */, sleepAfterYieldDelay);
     }
 
-    private boolean yieldIfContendedHelper(boolean checkFullyYielded, long sleepAfterYieldDelay) {
-        if (mLock.getQueueLength() == 0) {
-            // Reset the lock acquire time since we know that the thread was willing to yield
-            // the lock at this time.
-            mLockAcquiredWallTime = SystemClock.elapsedRealtime();
-            mLockAcquiredThreadTime = Debug.threadCpuTimeNanos();
-            return false;
-        }
-        setTransactionSuccessful();
-        SQLiteTransactionListener transactionListener = mTransactionListener;
-        endTransaction();
-        if (checkFullyYielded) {
-            if (this.isDbLockedByCurrentThread()) {
-                throw new IllegalStateException(
-                        "Db locked more than once. yielfIfContended cannot yield");
-            }
-        }
-        if (sleepAfterYieldDelay > 0) {
-            // Sleep for up to sleepAfterYieldDelay milliseconds, waking up periodically to
-            // check if anyone is using the database.  If the database is not contended,
-            // retake the lock and return.
-            long remainingDelay = sleepAfterYieldDelay;
-            while (remainingDelay > 0) {
-                try {
-                    Thread.sleep(remainingDelay < SLEEP_AFTER_YIELD_QUANTUM ?
-                            remainingDelay : SLEEP_AFTER_YIELD_QUANTUM);
-                } catch (InterruptedException e) {
-                    Thread.interrupted();
-                }
-                remainingDelay -= SLEEP_AFTER_YIELD_QUANTUM;
-                if (mLock.getQueueLength() == 0) {
-                    break;
-                }
-            }
-        }
-        beginTransactionWithListener(transactionListener);
-        return true;
+    private boolean yieldIfContendedHelper(boolean throwIfUnsafe, long sleepAfterYieldDelay) {
+        return getThreadSession().yieldTransaction(sleepAfterYieldDelay, throwIfUnsafe);
     }
 
     /**
+     * Deprecated.
      * @deprecated This method no longer serves any useful purpose and has been deprecated.
      */
     @Deprecated
@@ -932,19 +610,6 @@
     }
 
     /**
-     * Used to allow returning sub-classes of {@link Cursor} when calling query.
-     */
-    public interface CursorFactory {
-        /**
-         * See
-         * {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}.
-         */
-        public Cursor newCursor(SQLiteDatabase db,
-                SQLiteCursorDriver masterQuery, String editTable,
-                SQLiteQuery query);
-    }
-
-    /**
      * Open the database according to the flags {@link #OPEN_READWRITE}
      * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
      *
@@ -983,50 +648,9 @@
      */
     public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
             DatabaseErrorHandler errorHandler) {
-        SQLiteDatabase sqliteDatabase = openDatabase(path, factory, flags, errorHandler,
-                (short) 0 /* the main connection handle */);
-
-        // set sqlite pagesize to mBlockSize
-        if (sBlockSize == 0) {
-            // TODO: "/data" should be a static final String constant somewhere. it is hardcoded
-            // in several places right now.
-            sBlockSize = new StatFs("/data").getBlockSize();
-        }
-        sqliteDatabase.setPageSize(sBlockSize);
-        sqliteDatabase.setJournalMode(path, "TRUNCATE");
-
-        // add this database to the list of databases opened in this process
-        synchronized(mActiveDatabases) {
-            mActiveDatabases.add(new WeakReference<SQLiteDatabase>(sqliteDatabase));
-        }
-        return sqliteDatabase;
-    }
-
-    private static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
-            DatabaseErrorHandler errorHandler, short connectionNum) {
-        SQLiteDatabase db = new SQLiteDatabase(path, factory, flags, errorHandler, connectionNum);
-        try {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.i(TAG, "opening the db : " + path);
-            }
-            // Open the database.
-            db.dbopen(path, flags);
-            db.setLocale(Locale.getDefault());
-            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
-                db.enableSqlTracing(path, connectionNum);
-            }
-            if (SQLiteDebug.DEBUG_SQL_TIME) {
-                db.enableSqlProfiling(path, connectionNum);
-            }
-            return db;
-        } catch (SQLiteDatabaseCorruptException e) {
-            db.mErrorHandler.onCorruption(db);
-            return SQLiteDatabase.openDatabase(path, factory, flags, errorHandler);
-        } catch (SQLiteException e) {
-            Log.e(TAG, "Failed to open the database. closing it.", e);
-            db.close();
-            throw e;
-        }
+        SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
+        db.open();
+        return db;
     }
 
     /**
@@ -1051,16 +675,46 @@
         return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
     }
 
-    private void setJournalMode(final String dbPath, final String mode) {
-        // journal mode can be set only for non-memory databases
+    private void open() {
+        try {
+            try {
+                openInner();
+            } catch (SQLiteDatabaseCorruptException ex) {
+                onCorruption();
+                openInner();
+            }
+
+            // Disable WAL if it was previously enabled.
+            setJournalMode("TRUNCATE");
+        } catch (SQLiteException ex) {
+            Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
+            close();
+            throw ex;
+        }
+    }
+
+    private void openInner() {
+        synchronized (mLock) {
+            assert mConnectionPoolLocked == null;
+            mConnectionPoolLocked = SQLiteConnectionPool.open(mConfigurationLocked);
+            mCloseGuardLocked.open("close");
+        }
+
+        synchronized (sActiveDatabases) {
+            sActiveDatabases.put(this, null);
+        }
+    }
+
+    private void setJournalMode(String mode) {
+        // Journal mode can be set only for non-memory databases
         // AND can't be set for readonly databases
-        if (dbPath.equalsIgnoreCase(MEMORY_DB_PATH) || isReadOnly()) {
+        if (isInMemoryDatabase() || isReadOnly()) {
             return;
         }
         String s = DatabaseUtils.stringForQuery(this, "PRAGMA journal_mode=" + mode, null);
         if (!s.equalsIgnoreCase(mode)) {
-            Log.e(TAG, "setting journal_mode to " + mode + " failed for db: " + dbPath +
-                    " (on pragma set journal_mode, sqlite returned:" + s);
+            Log.e(TAG, "setting journal_mode to " + mode + " failed for db: " + getLabel()
+                    + " (on pragma set journal_mode, sqlite returned:" + s);
         }
     }
 
@@ -1077,159 +731,37 @@
      */
     public static SQLiteDatabase create(CursorFactory factory) {
         // This is a magic string with special meaning for SQLite.
-        return openDatabase(MEMORY_DB_PATH, factory, CREATE_IF_NECESSARY);
+        return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
+                factory, CREATE_IF_NECESSARY);
     }
 
     /**
      * Close the database.
      */
     public void close() {
-        if (!isOpen()) {
-            return;
-        }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.i(TAG, "closing db: " + mPath + " (connection # " + mConnectionNum);
-        }
-        lock();
-        try {
-            // some other thread could have closed this database while I was waiting for lock.
-            // check the database state
-            if (!isOpen()) {
-                return;
-            }
-            closeClosable();
-            // finalize ALL statements queued up so far
-            closePendingStatements();
-            releaseCustomFunctions();
-            // close this database instance - regardless of its reference count value
-            closeDatabase();
-            if (mConnectionPool != null) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    assert mConnectionPool != null;
-                    Log.i(TAG, mConnectionPool.toString());
-                }
-                mConnectionPool.close();
-            }
-        } finally {
-            unlock();
-        }
-    }
-
-    private void closeClosable() {
-        /* deallocate all compiled SQL statement objects from mCompiledQueries cache.
-         * this should be done before de-referencing all {@link SQLiteClosable} objects
-         * from this database object because calling
-         * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database
-         * to be closed. sqlite doesn't let a database close if there are
-         * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries.
-         */
-        deallocCachedSqlStatements();
-
-        Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
-        while (iter.hasNext()) {
-            Map.Entry<SQLiteClosable, Object> entry = iter.next();
-            SQLiteClosable program = entry.getKey();
-            if (program != null) {
-                program.onAllReferencesReleasedFromContainer();
-            }
-        }
-    }
-
-    /**
-     * package level access for testing purposes
-     */
-    /* package */ void closeDatabase() throws SQLiteException {
-        try {
-            dbclose();
-        } catch (SQLiteUnfinalizedObjectsException e)  {
-            String msg = e.getMessage();
-            String[] tokens = msg.split(",", 2);
-            int stmtId = Integer.parseInt(tokens[0]);
-            // get extra info about this statement, if it is still to be released by closeClosable()
-            Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator();
-            boolean found = false;
-            while (iter.hasNext()) {
-                Map.Entry<SQLiteClosable, Object> entry = iter.next();
-                SQLiteClosable program = entry.getKey();
-                if (program != null && program instanceof SQLiteProgram) {
-                    SQLiteCompiledSql compiledSql = ((SQLiteProgram)program).mCompiledSql;
-                    if (compiledSql.nStatement == stmtId) {
-                        msg = compiledSql.toString();
-                        found = true;
-                    }
-                }
-            }
-            if (!found) {
-                // the statement is already released by closeClosable(). is it waiting to be
-                // finalized?
-                if (mClosedStatementIds.contains(stmtId)) {
-                    Log.w(TAG, "this shouldn't happen. finalizing the statement now: ");
-                    closePendingStatements();
-                    // try to close the database again
-                    closeDatabase();
-                }
-            } else {
-                // the statement is not yet closed. most probably programming error in the app.
-                throw new SQLiteUnfinalizedObjectsException(
-                        "close() on database: " + getPath() +
-                        " failed due to un-close()d SQL statements: " + msg);
-            }
-        }
-    }
-
-    /**
-     * Native call to close the database.
-     */
-    private native void dbclose();
-
-    /**
-     * A callback interface for a custom sqlite3 function.
-     * This can be used to create a function that can be called from
-     * sqlite3 database triggers.
-     * @hide
-     */
-    public interface CustomFunction {
-        public void callback(String[] args);
+        dispose(false);
     }
 
     /**
      * Registers a CustomFunction callback as a function that can be called from
-     * sqlite3 database triggers.
+     * SQLite database triggers.
+     *
      * @param name the name of the sqlite3 function
      * @param numArgs the number of arguments for the function
      * @param function callback to call when the function is executed
      * @hide
      */
     public void addCustomFunction(String name, int numArgs, CustomFunction function) {
-        verifyDbIsOpen();
-        synchronized (mCustomFunctions) {
-            int ref = native_addCustomFunction(name, numArgs, function);
-            if (ref != 0) {
-                // save a reference to the function for cleanup later
-                mCustomFunctions.add(new Integer(ref));
-            } else {
-                throw new SQLiteException("failed to add custom function " + name);
-            }
+        // Create wrapper (also validates arguments).
+        SQLiteCustomFunction wrapper = new SQLiteCustomFunction(name, numArgs, function);
+
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+            mConfigurationLocked.customFunctions.add(wrapper);
+            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
         }
     }
 
-    private void releaseCustomFunctions() {
-        synchronized (mCustomFunctions) {
-            for (int i = 0; i < mCustomFunctions.size(); i++) {
-                Integer function = mCustomFunctions.get(i);
-                native_releaseCustomFunction(function.intValue());
-            }
-            mCustomFunctions.clear();
-        }
-    }
-
-    // list of CustomFunction references so we can clean up when the database closes
-    private final ArrayList<Integer> mCustomFunctions =
-            new ArrayList<Integer>();
-
-    private native int native_addCustomFunction(String name, int numArgs, CustomFunction function);
-    private native void native_releaseCustomFunction(int function);
-
     /**
      * Gets the database version.
      *
@@ -1364,7 +896,7 @@
      * {@link SQLiteStatement}s are not synchronized, see the documentation for more details.
      */
     public SQLiteStatement compileStatement(String sql) throws SQLException {
-        verifyDbIsOpen();
+        throwIfNotOpen(); // fail fast
         return new SQLiteStatement(this, sql, null);
     }
 
@@ -1442,7 +974,7 @@
             boolean distinct, String table, String[] columns,
             String selection, String[] selectionArgs, String groupBy,
             String having, String orderBy, String limit) {
-        verifyDbIsOpen();
+        throwIfNotOpen(); // fail fast
         String sql = SQLiteQueryBuilder.buildQueryString(
                 distinct, table, columns, selection, groupBy, having, orderBy, limit);
 
@@ -1553,21 +1085,11 @@
     public Cursor rawQueryWithFactory(
             CursorFactory cursorFactory, String sql, String[] selectionArgs,
             String editTable) {
-        verifyDbIsOpen();
-        BlockGuard.getThreadPolicy().onReadFromDisk();
+        throwIfNotOpen(); // fail fast
 
-        SQLiteDatabase db = getDbConnection(sql);
-        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(db, sql, editTable);
-
-        Cursor cursor = null;
-        try {
-            cursor = driver.query(
-                    cursorFactory != null ? cursorFactory : mFactory,
-                    selectionArgs);
-        } finally {
-            releaseDbConnection(db);
-        }
-        return cursor;
+        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable);
+        return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
+                selectionArgs);
     }
 
     /**
@@ -1716,9 +1238,6 @@
         SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
         try {
             return statement.executeInsert();
-        } catch (SQLiteDatabaseCorruptException e) {
-            onCorruption();
-            throw e;
         } finally {
             statement.close();
         }
@@ -1739,9 +1258,6 @@
                 (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
         try {
             return statement.executeUpdateDelete();
-        } catch (SQLiteDatabaseCorruptException e) {
-            onCorruption();
-            throw e;
         } finally {
             statement.close();
         }
@@ -1808,9 +1324,6 @@
         SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
         try {
             return statement.executeUpdateDelete();
-        } catch (SQLiteDatabaseCorruptException e) {
-            onCorruption();
-            throw e;
         } finally {
             statement.close();
         }
@@ -1891,264 +1404,105 @@
 
     private int executeSql(String sql, Object[] bindArgs) throws SQLException {
         if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
-            disableWriteAheadLogging();
-            mHasAttachedDbs = true;
+            boolean disableWal = false;
+            synchronized (mLock) {
+                if (!mHasAttachedDbsLocked) {
+                    mHasAttachedDbsLocked = true;
+                    disableWal = true;
+                }
+            }
+            if (disableWal) {
+                disableWriteAheadLogging();
+            }
         }
+
         SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
         try {
             return statement.executeUpdateDelete();
-        } catch (SQLiteDatabaseCorruptException e) {
-            onCorruption();
-            throw e;
         } finally {
             statement.close();
         }
     }
 
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            if (isOpen()) {
-                Log.e(TAG, "close() was never explicitly called on database '" +
-                        mPath + "' ", mStackTrace);
-                closeClosable();
-                onAllReferencesReleased();
-                releaseCustomFunctions();
-            }
-        } finally {
-            super.finalize();
-        }
-    }
-
     /**
-     * Private constructor.
+     * Returns true if the database is opened as read only.
      *
-     * @param path The full path to the database
-     * @param factory The factory to use when creating cursors, may be NULL.
-     * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}.  If the database file already
-     *              exists, mFlags will be updated appropriately.
-     * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
-     * corruption. may be NULL.
-     * @param connectionNum 0 for main database connection handle. 1..N for pooled database
-     * connection handles.
-     */
-    private SQLiteDatabase(String path, CursorFactory factory, int flags,
-            DatabaseErrorHandler errorHandler, short connectionNum) {
-        if (path == null) {
-            throw new IllegalArgumentException("path should not be null");
-        }
-        setMaxSqlCacheSize(DEFAULT_SQL_CACHE_SIZE);
-        mFlags = flags;
-        mPath = path;
-        mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
-        mFactory = factory;
-        mPrograms = new WeakHashMap<SQLiteClosable,Object>();
-        // Set the DatabaseErrorHandler to be used when SQLite reports corruption.
-        // If the caller sets errorHandler = null, then use default errorhandler.
-        mErrorHandler = (errorHandler == null) ? new DefaultDatabaseErrorHandler() : errorHandler;
-        mConnectionNum = connectionNum;
-        /* sqlite soft heap limit http://www.sqlite.org/c3ref/soft_heap_limit64.html
-         * set it to 4 times the default cursor window size.
-         * TODO what is an appropriate value, considering the WAL feature which could burn
-         * a lot of memory with many connections to the database. needs testing to figure out
-         * optimal value for this.
-         */
-        int limit = Resources.getSystem().getInteger(
-                com.android.internal.R.integer.config_cursorWindowSize) * 1024 * 4;
-        native_setSqliteSoftHeapLimit(limit);
-    }
-
-    /**
-     * return whether the DB is opened as read only.
-     * @return true if DB is opened as read only
+     * @return True if database is opened as read only.
      */
     public boolean isReadOnly() {
-        return (mFlags & OPEN_READ_MASK) == OPEN_READONLY;
+        synchronized (mLock) {
+            return isReadOnlyLocked();
+        }
+    }
+
+    private boolean isReadOnlyLocked() {
+        return (mConfigurationLocked.openFlags & OPEN_READ_MASK) == OPEN_READONLY;
     }
 
     /**
-     * @return true if the DB is currently open (has not been closed)
+     * Returns true if the database is in-memory db.
+     *
+     * @return True if the database is in-memory.
+     * @hide
      */
-    public boolean isOpen() {
-        return mNativeHandle != 0;
+    public boolean isInMemoryDatabase() {
+        synchronized (mLock) {
+            return mConfigurationLocked.isInMemoryDb();
+        }
     }
 
+    /**
+     * Returns true if the database is currently open.
+     *
+     * @return True if the database is currently open (has not been closed).
+     */
+    public boolean isOpen() {
+        synchronized (mLock) {
+            return mConnectionPoolLocked != null;
+        }
+    }
+
+    /**
+     * Returns true if the new version code is greater than the current database version.
+     *
+     * @param newVersion The new version code.
+     * @return True if the new version code is greater than the current database version. 
+     */
     public boolean needUpgrade(int newVersion) {
         return newVersion > getVersion();
     }
 
     /**
-     * Getter for the path to the database file.
+     * Gets the path to the database file.
      *
-     * @return the path to our database file.
+     * @return The path to the database file.
      */
     public final String getPath() {
-        return mPath;
-    }
-
-    /* package */ void logTimeStat(String sql, long beginMillis) {
-        if (ENABLE_DB_SAMPLE) {
-            logTimeStat(sql, beginMillis, null);
+        synchronized (mLock) {
+            return mConfigurationLocked.path;
         }
     }
 
-    private void logTimeStat(String sql, long beginMillis, String prefix) {
-        // Sample fast queries in proportion to the time taken.
-        // Quantize the % first, so the logged sampling probability
-        // exactly equals the actual sampling rate for this query.
-
-        int samplePercent;
-        long durationMillis = SystemClock.uptimeMillis() - beginMillis;
-        if (durationMillis == 0 && prefix == GET_LOCK_LOG_PREFIX) {
-            // The common case is locks being uncontended.  Don't log those,
-            // even at 1%, which is our default below.
-            return;
-        }
-        if (sQueryLogTimeInMillis == 0) {
-            sQueryLogTimeInMillis = SystemProperties.getInt("db.db_operation.threshold_ms", 500);
-        }
-        if (durationMillis >= sQueryLogTimeInMillis) {
-            samplePercent = 100;
-        } else {
-            samplePercent = (int) (100 * durationMillis / sQueryLogTimeInMillis) + 1;
-            if (mRandom.nextInt(100) >= samplePercent) return;
-        }
-
-        // Note: the prefix will be "COMMIT;" or "GETLOCK:" when non-null.  We wait to do
-        // it here so we avoid allocating in the common case.
-        if (prefix != null) {
-            sql = prefix + sql;
-        }
-        if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH);
-
-        // ActivityThread.currentPackageName() only returns non-null if the
-        // current thread is an application main thread.  This parameter tells
-        // us whether an event loop is blocked, and if so, which app it is.
-        //
-        // Sadly, there's no fast way to determine app name if this is *not* a
-        // main thread, or when we are invoked via Binder (e.g. ContentProvider).
-        // Hopefully the full path to the database will be informative enough.
-
-        String blockingPackage = AppGlobals.getInitialPackage();
-        if (blockingPackage == null) blockingPackage = "";
-
-        EventLog.writeEvent(
-            EVENT_DB_OPERATION,
-            getPathForLogs(),
-            sql,
-            durationMillis,
-            blockingPackage,
-            samplePercent);
-    }
-
-    /**
-     * Removes email addresses from database filenames before they're
-     * logged to the EventLog where otherwise apps could potentially
-     * read them.
-     */
-    private String getPathForLogs() {
-        if (mPathForLogs != null) {
-            return mPathForLogs;
-        }
-        if (mPath == null) {
-            return null;
-        }
-        if (mPath.indexOf('@') == -1) {
-            mPathForLogs = mPath;
-        } else {
-            mPathForLogs = EMAIL_IN_DB_PATTERN.matcher(mPath).replaceAll("XX@YY");
-        }
-        return mPathForLogs;
-    }
-
     /**
      * Sets the locale for this database.  Does nothing if this database has
      * the NO_LOCALIZED_COLLATORS flag set or was opened read only.
+     *
+     * @param locale The new locale.
+     *
      * @throws SQLException if the locale could not be set.  The most common reason
      * for this is that there is no collator available for the locale you requested.
      * In this case the database remains unchanged.
      */
     public void setLocale(Locale locale) {
-        lock();
-        try {
-            native_setLocale(locale.toString(), mFlags);
-        } finally {
-            unlock();
-        }
-    }
-
-    /* package */ void verifyDbIsOpen() {
-        if (!isOpen()) {
-            throw new IllegalStateException("database " + getPath() + " (conn# " +
-                    mConnectionNum + ") already closed");
-        }
-    }
-
-    /* package */ void verifyLockOwner() {
-        verifyDbIsOpen();
-        if (mLockingEnabled && !isDbLockedByCurrentThread()) {
-            throw new IllegalStateException("Don't have database lock!");
-        }
-    }
-
-    /**
-     * Adds the given SQL and its compiled-statement-id-returned-by-sqlite to the
-     * cache of compiledQueries attached to 'this'.
-     * <p>
-     * If there is already a {@link SQLiteCompiledSql} in compiledQueries for the given SQL,
-     * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current
-     * mapping is NOT replaced with the new mapping).
-     */
-    /* package */ synchronized void addToCompiledQueries(
-            String sql, SQLiteCompiledSql compiledStatement) {
-        // don't insert the new mapping if a mapping already exists
-        if (mCompiledQueries.get(sql) != null) {
-            return;
+        if (locale == null) {
+            throw new IllegalArgumentException("locale must not be null.");
         }
 
-        int maxCacheSz = (mConnectionNum == 0) ? mCompiledQueries.maxSize() :
-                mParentConnObj.mCompiledQueries.maxSize();
-
-        if (SQLiteDebug.DEBUG_SQL_CACHE) {
-            boolean printWarning = (mConnectionNum == 0)
-                    ? (!mCacheFullWarning && mCompiledQueries.size() == maxCacheSz)
-                    : (!mParentConnObj.mCacheFullWarning &&
-                    mParentConnObj.mCompiledQueries.size() == maxCacheSz);
-            if (printWarning) {
-                /*
-                 * cache size is not enough for this app. log a warning.
-                 * chances are it is NOT using ? for bindargs - or cachesize is too small.
-                 */
-                Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " +
-                        getPath() + ". Use setMaxSqlCacheSize() to increase cachesize. ");
-                mCacheFullWarning = true;
-                Log.d(TAG, "Here are the SQL statements in Cache of database: " + mPath);
-                for (String s : mCompiledQueries.snapshot().keySet()) {
-                    Log.d(TAG, "Sql statement in Cache: " + s);
-                }
-            }
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+            mConfigurationLocked.locale = locale;
+            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
         }
-        /* add the given SQLiteCompiledSql compiledStatement to cache.
-         * no need to worry about the cache size - because {@link #mCompiledQueries}
-         * self-limits its size.
-         */
-        mCompiledQueries.put(sql, compiledStatement);
-    }
-
-    /** package-level access for testing purposes */
-    /* package */ synchronized void deallocCachedSqlStatements() {
-        for (SQLiteCompiledSql compiledSql : mCompiledQueries.snapshot().values()) {
-            compiledSql.releaseSqlStatement();
-        }
-        mCompiledQueries.evictAll();
-    }
-
-    /**
-     * From the compiledQueries cache, returns the compiled-statement-id for the given SQL.
-     * Returns null, if not found in the cache.
-     */
-    /* package */ synchronized SQLiteCompiledSql getCompiledStatementForSql(String sql) {
-        return mCompiledQueries.get(sql);
     }
 
     /**
@@ -2162,115 +1516,21 @@
      * This method is thread-safe.
      *
      * @param cacheSize the size of the cache. can be (0 to {@link #MAX_SQL_CACHE_SIZE})
-     * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE} or
-     * the value set with previous setMaxSqlCacheSize() call.
+     * @throws IllegalStateException if input cacheSize > {@link #MAX_SQL_CACHE_SIZE}.
      */
     public void setMaxSqlCacheSize(int cacheSize) {
-        synchronized (this) {
-            LruCache<String, SQLiteCompiledSql> oldCompiledQueries = mCompiledQueries;
-            if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
-                throw new IllegalStateException(
-                        "expected value between 0 and " + MAX_SQL_CACHE_SIZE);
-            } else if (oldCompiledQueries != null && cacheSize < oldCompiledQueries.maxSize()) {
-                throw new IllegalStateException("cannot set cacheSize to a value less than the "
-                        + "value set with previous setMaxSqlCacheSize() call.");
-            }
-            mCompiledQueries = new LruCache<String, SQLiteCompiledSql>(cacheSize) {
-                @Override
-                protected void entryRemoved(boolean evicted, String key, SQLiteCompiledSql oldValue,
-                        SQLiteCompiledSql newValue) {
-                    verifyLockOwner();
-                    oldValue.releaseIfNotInUse();
-                }
-            };
-            if (oldCompiledQueries != null) {
-                for (Map.Entry<String, SQLiteCompiledSql> entry
-                        : oldCompiledQueries.snapshot().entrySet()) {
-                    mCompiledQueries.put(entry.getKey(), entry.getValue());
-                }
-            }
+        if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) {
+            throw new IllegalStateException(
+                    "expected value between 0 and " + MAX_SQL_CACHE_SIZE);
         }
-    }
 
-    /* package */ synchronized boolean isInStatementCache(String sql) {
-        return mCompiledQueries.get(sql) != null;
-    }
-
-    /* package */ synchronized void releaseCompiledSqlObj(
-            String sql, SQLiteCompiledSql compiledSql) {
-        if (mCompiledQueries.get(sql) == compiledSql) {
-            // it is in cache - reset its inUse flag
-            compiledSql.release();
-        } else {
-            // it is NOT in cache. finalize it.
-            compiledSql.releaseSqlStatement();
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+            mConfigurationLocked.maxSqlCacheSize = cacheSize;
+            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
         }
     }
 
-    private synchronized int getCacheHitNum() {
-        return mCompiledQueries.hitCount();
-    }
-
-    private synchronized int getCacheMissNum() {
-        return mCompiledQueries.missCount();
-    }
-
-    private synchronized int getCachesize() {
-        return mCompiledQueries.size();
-    }
-
-    /* package */ void finalizeStatementLater(int id) {
-        if (!isOpen()) {
-            // database already closed. this statement will already have been finalized.
-            return;
-        }
-        synchronized(mClosedStatementIds) {
-            if (mClosedStatementIds.contains(id)) {
-                // this statement id is already queued up for finalization.
-                return;
-            }
-            mClosedStatementIds.add(id);
-        }
-    }
-
-    /* package */ boolean isInQueueOfStatementsToBeFinalized(int id) {
-        if (!isOpen()) {
-            // database already closed. this statement will already have been finalized.
-            // return true so that the caller doesn't have to worry about finalizing this statement.
-            return true;
-        }
-        synchronized(mClosedStatementIds) {
-            return mClosedStatementIds.contains(id);
-        }
-    }
-
-    /* package */ void closePendingStatements() {
-        if (!isOpen()) {
-            // since this database is already closed, no need to finalize anything.
-            mClosedStatementIds.clear();
-            return;
-        }
-        verifyLockOwner();
-        /* to minimize synchronization on mClosedStatementIds, make a copy of the list */
-        ArrayList<Integer> list = new ArrayList<Integer>(mClosedStatementIds.size());
-        synchronized(mClosedStatementIds) {
-            list.addAll(mClosedStatementIds);
-            mClosedStatementIds.clear();
-        }
-        // finalize all the statements from the copied list
-        int size = list.size();
-        for (int i = 0; i < size; i++) {
-            native_finalize(list.get(i));
-        }
-    }
-
-    /**
-     * for testing only
-     */
-    /* package */ ArrayList<Integer> getQueuedUpStmtList() {
-        return mClosedStatementIds;
-    }
-
     /**
      * This method enables parallel execution of queries from multiple threads on the same database.
      * It does this by opening multiple handles to the database and using a different
@@ -2314,37 +1574,43 @@
      * @return true if write-ahead-logging is set. false otherwise
      */
     public boolean enableWriteAheadLogging() {
-        // make sure the database is not READONLY. WAL doesn't make sense for readonly-databases.
-        if (isReadOnly()) {
-            return false;
-        }
-        // acquire lock - no that no other thread is enabling WAL at the same time
-        lock();
-        try {
-            if (mConnectionPool != null) {
-                // already enabled
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+
+            if (mIsWALEnabledLocked) {
                 return true;
             }
-            if (mPath.equalsIgnoreCase(MEMORY_DB_PATH)) {
+
+            if (isReadOnlyLocked()) {
+                // WAL doesn't make sense for readonly-databases.
+                // TODO: True, but connection pooling does still make sense...
+                return false;
+            }
+
+            if (mConfigurationLocked.isInMemoryDb()) {
                 Log.i(TAG, "can't enable WAL for memory databases.");
                 return false;
             }
 
             // make sure this database has NO attached databases because sqlite's write-ahead-logging
             // doesn't work for databases with attached databases
-            if (mHasAttachedDbs) {
+            if (mHasAttachedDbsLocked) {
                 if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG,
-                            "this database: " + mPath + " has attached databases. can't  enable WAL.");
+                    Log.d(TAG, "this database: " + mConfigurationLocked.label
+                            + " has attached databases. can't  enable WAL.");
                 }
                 return false;
             }
-            mConnectionPool = new DatabaseConnectionPool(this);
-            setJournalMode(mPath, "WAL");
-            return true;
-        } finally {
-            unlock();
+
+            mIsWALEnabledLocked = true;
+            mConfigurationLocked.maxConnectionPoolSize = Math.max(2,
+                    Resources.getSystem().getInteger(
+                            com.android.internal.R.integer.db_connection_pool_size));
+            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
         }
+
+        setJournalMode("WAL");
+        return true;
     }
 
     /**
@@ -2352,178 +1618,68 @@
      * @hide
      */
     public void disableWriteAheadLogging() {
-        // grab database lock so that writeAheadLogging is not disabled from 2 different threads
-        // at the same time
-        lock();
-        try {
-            if (mConnectionPool == null) {
-                return; // already disabled
+        synchronized (mLock) {
+            throwIfNotOpenLocked();
+
+            if (!mIsWALEnabledLocked) {
+                return;
             }
-            mConnectionPool.close();
-            setJournalMode(mPath, "TRUNCATE");
-            mConnectionPool = null;
-        } finally {
-            unlock();
-        }
-    }
 
-    /* package */ SQLiteDatabase getDatabaseHandle(String sql) {
-        if (isPooledConnection()) {
-            // this is a pooled database connection
-            // use it if it is open AND if I am not currently part of a transaction
-            if (isOpen() && !amIInTransaction()) {
-                // TODO: use another connection from the pool
-                // if this connection is currently in use by some other thread
-                // AND if there are free connections in the pool
-                return this;
-            } else {
-                // the pooled connection is not open! could have been closed either due
-                // to corruption on this or some other connection to the database
-                // OR, maybe the connection pool is disabled after this connection has been
-                // allocated to me. try to get some other pooled or main database connection
-                return getParentDbConnObj().getDbConnection(sql);
-            }
-        } else {
-            // this is NOT a pooled connection. can we get one?
-            return getDbConnection(sql);
-        }
-    }
-
-    /* package */ SQLiteDatabase createPoolConnection(short connectionNum) {
-        SQLiteDatabase db = openDatabase(mPath, mFactory, mFlags, mErrorHandler, connectionNum);
-        db.mParentConnObj = this;
-        return db;
-    }
-
-    private synchronized SQLiteDatabase getParentDbConnObj() {
-        return mParentConnObj;
-    }
-
-    private boolean isPooledConnection() {
-        return this.mConnectionNum > 0;
-    }
-
-    /* package */ SQLiteDatabase getDbConnection(String sql) {
-        verifyDbIsOpen();
-        // this method should always be called with main database connection handle.
-        // the only time when it is called with pooled database connection handle is
-        // corruption occurs while trying to open a pooled database connection handle.
-        // in that case, simply return 'this' handle
-        if (isPooledConnection()) {
-            return this;
+            mIsWALEnabledLocked = false;
+            mConfigurationLocked.maxConnectionPoolSize = 1;
+            mConnectionPoolLocked.reconfigure(mConfigurationLocked);
         }
 
-        // use the current connection handle if
-        // 1. if the caller is part of the ongoing transaction, if any
-        // 2. OR, if there is NO connection handle pool setup
-        if (amIInTransaction() || mConnectionPool == null) {
-            return this;
-        } else {
-            // get a connection handle from the pool
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                assert mConnectionPool != null;
-                Log.i(TAG, mConnectionPool.toString());
-            }
-            return mConnectionPool.get(sql);
-        }
-    }
-
-    private void releaseDbConnection(SQLiteDatabase db) {
-        // ignore this release call if
-        // 1. the database is closed
-        // 2. OR, if db is NOT a pooled connection handle
-        // 3. OR, if the database being released is same as 'this' (this condition means
-        //     that we should always be releasing a pooled connection handle by calling this method
-        //     from the 'main' connection handle
-        if (!isOpen() || !db.isPooledConnection() || (db == this)) {
-            return;
-        }
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            assert isPooledConnection();
-            assert mConnectionPool != null;
-            Log.d(TAG, "releaseDbConnection threadid = " + Thread.currentThread().getId() +
-                    ", releasing # " + db.mConnectionNum + ", " + getPath());
-        }
-        mConnectionPool.release(db);
+        setJournalMode("TRUNCATE");
     }
 
     /**
-     * this method is used to collect data about ALL open databases in the current process.
-     * bugreport is a user of this data.
+     * Collect statistics about all open databases in the current process.
+     * Used by bug report.
      */
-    /* package */ static ArrayList<DbStats> getDbStats() {
+    static ArrayList<DbStats> getDbStats() {
         ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
-        // make a local copy of mActiveDatabases - so that this method is not competing
-        // for synchronization lock on mActiveDatabases
-        ArrayList<WeakReference<SQLiteDatabase>> tempList;
-        synchronized(mActiveDatabases) {
-            tempList = (ArrayList<WeakReference<SQLiteDatabase>>)mActiveDatabases.clone();
-        }
-        for (WeakReference<SQLiteDatabase> w : tempList) {
-            SQLiteDatabase db = w.get();
-            if (db == null || !db.isOpen()) {
-                continue;
-            }
-
-            try {
-                // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
-                int lookasideUsed = db.native_getDbLookaside();
-
-                // get the lastnode of the dbname
-                String path = db.getPath();
-                int indx = path.lastIndexOf("/");
-                String lastnode = path.substring((indx != -1) ? ++indx : 0);
-
-                // get list of attached dbs and for each db, get its size and pagesize
-                List<Pair<String, String>> attachedDbs = db.getAttachedDbs();
-                if (attachedDbs == null) {
-                    continue;
-                }
-                for (int i = 0; i < attachedDbs.size(); i++) {
-                    Pair<String, String> p = attachedDbs.get(i);
-                    long pageCount = DatabaseUtils.longForQuery(db, "PRAGMA " + p.first
-                            + ".page_count;", null);
-
-                    // first entry in the attached db list is always the main database
-                    // don't worry about prefixing the dbname with "main"
-                    String dbName;
-                    if (i == 0) {
-                        dbName = lastnode;
-                    } else {
-                        // lookaside is only relevant for the main db
-                        lookasideUsed = 0;
-                        dbName = "  (attached) " + p.first;
-                        // if the attached db has a path, attach the lastnode from the path to above
-                        if (p.second.trim().length() > 0) {
-                            int idx = p.second.lastIndexOf("/");
-                            dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
-                        }
-                    }
-                    if (pageCount > 0) {
-                        dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
-                                lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(),
-                                db.getCachesize()));
-                    }
-                }
-                // if there are pooled connections, return the cache stats for them also.
-                // while we are trying to query the pooled connections for stats, some other thread
-                // could be disabling conneciton pool. so, grab a reference to the connection pool.
-                DatabaseConnectionPool connPool = db.mConnectionPool;
-                if (connPool != null) {
-                    for (SQLiteDatabase pDb : connPool.getConnectionList()) {
-                        dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
-                                + lastnode, 0, 0, 0, pDb.getCacheHitNum(),
-                                pDb.getCacheMissNum(), pDb.getCachesize()));
-                    }
-                }
-            } catch (SQLiteException e) {
-                // ignore. we don't care about exceptions when we are taking adb
-                // bugreport!
-            }
+        for (SQLiteDatabase db : getActiveDatabases()) {
+            db.collectDbStats(dbStatsList);
         }
         return dbStatsList;
     }
 
+    private void collectDbStats(ArrayList<DbStats> dbStatsList) {
+        synchronized (mLock) {
+            if (mConnectionPoolLocked != null) {
+                mConnectionPoolLocked.collectDbStats(dbStatsList);
+            }
+        }
+    }
+
+    private static ArrayList<SQLiteDatabase> getActiveDatabases() {
+        ArrayList<SQLiteDatabase> databases = new ArrayList<SQLiteDatabase>();
+        synchronized (sActiveDatabases) {
+            databases.addAll(sActiveDatabases.keySet());
+        }
+        return databases;
+    }
+
+    /**
+     * Dump detailed information about all open databases in the current process.
+     * Used by bug report.
+     */
+    static void dumpAll(Printer printer) {
+        for (SQLiteDatabase db : getActiveDatabases()) {
+            db.dump(printer);
+        }
+    }
+
+    private void dump(Printer printer) {
+        synchronized (mLock) {
+            if (mConnectionPoolLocked != null) {
+                printer.println("");
+                mConnectionPoolLocked.dump(printer);
+            }
+        }
+    }
+
     /**
      * Returns list of full pathnames of all attached databases including the main database
      * by executing 'pragma database_list' on the database.
@@ -2532,23 +1688,27 @@
      * is not open.
      */
     public List<Pair<String, String>> getAttachedDbs() {
-        if (!isOpen()) {
-            return null;
-        }
         ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>();
-        if (!mHasAttachedDbs) {
-            // No attached databases.
-            // There is a small window where attached databases exist but this flag is not set yet.
-            // This can occur when this thread is in a race condition with another thread
-            // that is executing the SQL statement: "attach database <blah> as <foo>"
-            // If this thread is NOT ok with such a race condition (and thus possibly not receive
-            // the entire list of attached databases), then the caller should ensure that no thread
-            // is executing any SQL statements while a thread is calling this method.
-            // Typically, this method is called when 'adb bugreport' is done or the caller wants to
-            // collect stats on the database and all its attached databases.
-            attachedDbs.add(new Pair<String, String>("main", mPath));
-            return attachedDbs;
+        synchronized (mLock) {
+            if (mConnectionPoolLocked == null) {
+                return null; // not open
+            }
+
+            if (!mHasAttachedDbsLocked) {
+                // No attached databases.
+                // There is a small window where attached databases exist but this flag is not
+                // set yet.  This can occur when this thread is in a race condition with another
+                // thread that is executing the SQL statement: "attach database <blah> as <foo>"
+                // If this thread is NOT ok with such a race condition (and thus possibly not
+                // receivethe entire list of attached databases), then the caller should ensure
+                // that no thread is executing any SQL statements while a thread is calling this
+                // method.  Typically, this method is called when 'adb bugreport' is done or the
+                // caller wants to collect stats on the database and all its attached databases.
+                attachedDbs.add(new Pair<String, String>("main", mConfigurationLocked.path));
+                return attachedDbs;
+            }
         }
+
         // has attached databases. query sqlite to get the list of attached databases.
         Cursor c = null;
         try {
@@ -2583,7 +1743,8 @@
      * false otherwise.
      */
     public boolean isDatabaseIntegrityOk() {
-        verifyDbIsOpen();
+        throwIfNotOpen(); // fail fast
+
         List<Pair<String, String>> attachedDbs = null;
         try {
             attachedDbs = getAttachedDbs();
@@ -2594,8 +1755,9 @@
         } catch (SQLiteException e) {
             // can't get attachedDb list. do integrity check on the main database
             attachedDbs = new ArrayList<Pair<String, String>>();
-            attachedDbs.add(new Pair<String, String>("main", this.mPath));
+            attachedDbs.add(new Pair<String, String>("main", getPath()));
         }
+
         for (int i = 0; i < attachedDbs.size(); i++) {
             Pair<String, String> p = attachedDbs.get(i);
             SQLiteStatement prog = null;
@@ -2615,59 +1777,64 @@
     }
 
     /**
-     * Native call to open the database.
+     * Prevent other threads from using the database's primary connection.
      *
-     * @param path The full path to the database
-     */
-    private native void dbopen(String path, int flags);
-
-    /**
-     * Native call to setup tracing of all SQL statements
+     * This method is only used by {@link SQLiteOpenHelper} when transitioning from
+     * a readable to a writable database.  It should not be used in any other way.
      *
-     * @param path the full path to the database
-     * @param connectionNum connection number: 0 - N, where the main database
-     *            connection handle is numbered 0 and the connection handles in the connection
-     *            pool are numbered 1..N.
+     * @see #unlockPrimaryConnection()
      */
-    private native void enableSqlTracing(String path, short connectionNum);
+    void lockPrimaryConnection() {
+        getThreadSession().beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED,
+                null, SQLiteConnectionPool.CONNECTION_FLAG_PRIMARY_CONNECTION_AFFINITY);
+    }
 
     /**
-     * Native call to setup profiling of all SQL statements.
-     * currently, sqlite's profiling = printing of execution-time
-     * (wall-clock time) of each of the SQL statements, as they
-     * are executed.
+     * Allow other threads to use the database's primary connection.
      *
-     * @param path the full path to the database
-     * @param connectionNum connection number: 0 - N, where the main database
-     *            connection handle is numbered 0 and the connection handles in the connection
-     *            pool are numbered 1..N.
+     * @see #lockPrimaryConnection()
      */
-    private native void enableSqlProfiling(String path, short connectionNum);
+    void unlockPrimaryConnection() {
+        getThreadSession().endTransaction();
+    }
+
+    @Override
+    public String toString() {
+        return "SQLiteDatabase: " + getPath();
+    }
+
+    private void throwIfNotOpen() {
+        synchronized (mConnectionPoolLocked) {
+            throwIfNotOpenLocked();
+        }
+    }
+
+    private void throwIfNotOpenLocked() {
+        if (mConnectionPoolLocked == null) {
+            throw new IllegalStateException("The database '" + mConfigurationLocked.label
+                    + "' is not open.");
+        }
+    }
 
     /**
-     * Native call to set the locale.  {@link #lock} must be held when calling
-     * this method.
-     * @throws SQLException
+     * Used to allow returning sub-classes of {@link Cursor} when calling query.
      */
-    private native void native_setLocale(String loc, int flags);
+    public interface CursorFactory {
+        /**
+         * See {@link SQLiteCursor#SQLiteCursor(SQLiteCursorDriver, String, SQLiteQuery)}.
+         */
+        public Cursor newCursor(SQLiteDatabase db,
+                SQLiteCursorDriver masterQuery, String editTable,
+                SQLiteQuery query);
+    }
 
     /**
-     * return the SQLITE_DBSTATUS_LOOKASIDE_USED documented here
-     * http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html
-     * @return int value of SQLITE_DBSTATUS_LOOKASIDE_USED
+     * A callback interface for a custom sqlite3 function.
+     * This can be used to create a function that can be called from
+     * sqlite3 database triggers.
+     * @hide
      */
-    private native int native_getDbLookaside();
-
-    /**
-     * finalizes the given statement id.
-     *
-     * @param statementId statement to be finzlied by sqlite
-     */
-    private final native void native_finalize(int statementId);
-
-    /**
-     * set sqlite soft heap limit
-     * http://www.sqlite.org/c3ref/soft_heap_limit64.html
-     */
-    private native void native_setSqliteSoftHeapLimit(int softHeapLimit);
+    public interface CustomFunction {
+        public void callback(String[] args);
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
new file mode 100644
index 0000000..bc79ad3
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * Describes how to configure a database.
+ * <p>
+ * The purpose of this object is to keep track of all of the little
+ * configuration settings that are applied to a database after it
+ * is opened so that they can be applied to all connections in the
+ * connection pool uniformly.
+ * </p><p>
+ * Each connection maintains its own copy of this object so it can
+ * keep track of which settings have already been applied.
+ * </p>
+ *
+ * @hide
+ */
+public final class SQLiteDatabaseConfiguration {
+    // The pattern we use to strip email addresses from database paths
+    // when constructing a label to use in log messages.
+    private static final Pattern EMAIL_IN_DB_PATTERN =
+            Pattern.compile("[\\w\\.\\-]+@[\\w\\.\\-]+");
+
+    /**
+     * Special path used by in-memory databases.
+     */
+    public static final String MEMORY_DB_PATH = ":memory:";
+
+    /**
+     * The database path.
+     */
+    public final String path;
+
+    /**
+     * The flags used to open the database.
+     */
+    public final int openFlags;
+
+    /**
+     * The label to use to describe the database when it appears in logs.
+     * This is derived from the path but is stripped to remove PII.
+     */
+    public final String label;
+
+    /**
+     * The maximum number of connections to retain in the connection pool.
+     * Must be at least 1.
+     *
+     * Default is 1.
+     */
+    public int maxConnectionPoolSize;
+
+    /**
+     * The maximum size of the prepared statement cache for each database connection.
+     * Must be non-negative.
+     *
+     * Default is 25.
+     */
+    public int maxSqlCacheSize;
+
+    /**
+     * The database locale.
+     *
+     * Default is the value returned by {@link Locale#getDefault()}.
+     */
+    public Locale locale;
+
+    /**
+     * The custom functions to register.
+     */
+    public final ArrayList<SQLiteCustomFunction> customFunctions =
+            new ArrayList<SQLiteCustomFunction>();
+
+    /**
+     * Creates a database configuration with the required parameters for opening a
+     * database and default values for all other parameters.
+     *
+     * @param path The database path.
+     * @param openFlags Open flags for the database, such as {@link SQLiteDatabase#OPEN_READWRITE}.
+     */
+    public SQLiteDatabaseConfiguration(String path, int openFlags) {
+        if (path == null) {
+            throw new IllegalArgumentException("path must not be null.");
+        }
+
+        this.path = path;
+        this.openFlags = openFlags;
+        label = stripPathForLogs(path);
+
+        // Set default values for optional parameters.
+        maxConnectionPoolSize = 1;
+        maxSqlCacheSize = 25;
+        locale = Locale.getDefault();
+    }
+
+    /**
+     * Creates a database configuration as a copy of another configuration.
+     *
+     * @param other The other configuration.
+     */
+    public SQLiteDatabaseConfiguration(SQLiteDatabaseConfiguration other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other must not be null.");
+        }
+
+        this.path = other.path;
+        this.openFlags = other.openFlags;
+        this.label = other.label;
+        updateParametersFrom(other);
+    }
+
+    /**
+     * Updates the non-immutable parameters of this configuration object
+     * from the other configuration object.
+     *
+     * @param other The object from which to copy the parameters.
+     */
+    public void updateParametersFrom(SQLiteDatabaseConfiguration other) {
+        if (other == null) {
+            throw new IllegalArgumentException("other must not be null.");
+        }
+        if (!path.equals(other.path) || openFlags != other.openFlags) {
+            throw new IllegalArgumentException("other configuration must refer to "
+                    + "the same database.");
+        }
+
+        maxConnectionPoolSize = other.maxConnectionPoolSize;
+        maxSqlCacheSize = other.maxSqlCacheSize;
+        locale = other.locale;
+        customFunctions.clear();
+        customFunctions.addAll(other.customFunctions);
+    }
+
+    /**
+     * Returns true if the database is in-memory.
+     * @return True if the database is in-memory.
+     */
+    public boolean isInMemoryDb() {
+        return path.equalsIgnoreCase(MEMORY_DB_PATH);
+    }
+
+    private static String stripPathForLogs(String path) {
+        if (path.indexOf('@') == -1) {
+            return path;
+        }
+        return EMAIL_IN_DB_PATTERN.matcher(path).replaceAll("XX@YY");
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 029bb4a..d87c3e4 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -30,6 +30,12 @@
  */
 public final class SQLiteDebug {
     /**
+     * Controls the printing of informational SQL log messages.
+     */
+    public static final boolean DEBUG_SQL_LOG =
+            Log.isLoggable("SQLiteLog", Log.VERBOSE);
+
+    /**
      * Controls the printing of SQL statements as they are executed.
      */
     public static final boolean DEBUG_SQL_STATEMENTS =
@@ -186,6 +192,7 @@
      * @param printer The printer for dumping database state.
      */
     public static void dump(Printer printer, String[] args) {
+        SQLiteDatabase.dumpAll(printer);
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
index a5e762e..52fd1d2 100644
--- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
+++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java
@@ -25,10 +25,9 @@
  * @hide
  */
 public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
-    private String mEditTable; 
-    private SQLiteDatabase mDatabase;
-    private Cursor mCursor;
-    private String mSql;
+    private final SQLiteDatabase mDatabase;
+    private final String mEditTable; 
+    private final String mSql;
     private SQLiteQuery mQuery;
 
     public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) {
@@ -38,33 +37,27 @@
     }
 
     public Cursor query(CursorFactory factory, String[] selectionArgs) {
-        // Compile the query
-        SQLiteQuery query = null;
-
+        final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql);
+        final Cursor cursor;
         try {
-            mDatabase.lock(mSql);
-            mDatabase.closePendingStatements();
-            query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
+            query.bindAllArgsAsStrings(selectionArgs);
 
-            // Create the cursor
             if (factory == null) {
-                mCursor = new SQLiteCursor(this, mEditTable, query);
+                cursor = new SQLiteCursor(this, mEditTable, query);
             } else {
-                mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
+                cursor = factory.newCursor(mDatabase, this, mEditTable, query);
             }
-
-            mQuery = query;
-            query = null;
-            return mCursor;
-        } finally {
-            // Make sure this object is cleaned up if something happens
-            if (query != null) query.close();
-            mDatabase.unlock();
+        } catch (RuntimeException ex) {
+            query.close();
+            throw ex;
         }
+
+        mQuery = query;
+        return cursor;
     }
 
     public void cursorClosed() {
-        mCursor = null;
+        // Do nothing
     }
 
     public void setBindArguments(String[] bindArgs) {
diff --git a/core/java/android/database/sqlite/SQLiteGlobal.java b/core/java/android/database/sqlite/SQLiteGlobal.java
new file mode 100644
index 0000000..5e129be
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteGlobal.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import android.os.StatFs;
+
+/**
+ * Provides access to SQLite functions that affect all database connection,
+ * such as memory management.
+ *
+ * @hide
+ */
+public final class SQLiteGlobal {
+    private static final String TAG = "SQLiteGlobal";
+
+    private static final Object sLock = new Object();
+    private static boolean sInitialized;
+    private static int sSoftHeapLimit;
+    private static int sDefaultPageSize;
+
+    private static native void nativeConfig(boolean verboseLog, int softHeapLimit);
+    private static native int nativeReleaseMemory(int bytesToFree);
+
+    private SQLiteGlobal() {
+    }
+
+    /**
+     * Initializes global SQLite settings the first time it is called.
+     * Should be called before opening the first (or any) database.
+     * Does nothing on repeated subsequent calls.
+     */
+    public static void initializeOnce() {
+        synchronized (sLock) {
+            if (!sInitialized) {
+                sInitialized = true;
+
+                // Limit to 8MB for now.  This is 4 times the maximum cursor window
+                // size, as has been used by the original code in SQLiteDatabase for
+                // a long time.
+                // TODO: We really do need to test whether this helps or hurts us.
+                sSoftHeapLimit = 8 * 1024 * 1024;
+
+                // Configure SQLite.
+                nativeConfig(SQLiteDebug.DEBUG_SQL_LOG, sSoftHeapLimit);
+            }
+        }
+    }
+
+    /**
+     * Attempts to release memory by pruning the SQLite page cache and other
+     * internal data structures.
+     *
+     * @return The number of bytes that were freed.
+     */
+    public static int releaseMemory() {
+        synchronized (sLock) {
+            if (!sInitialized) {
+                return 0;
+            }
+            return nativeReleaseMemory(sSoftHeapLimit);
+        }
+    }
+
+    /**
+     * Gets the default page size to use when creating a database.
+     */
+    public static int getDefaultPageSize() {
+        synchronized (sLock) {
+            if (sDefaultPageSize == 0) {
+                sDefaultPageSize = new StatFs("/data").getBlockSize();
+            }
+            return sDefaultPageSize;
+        }
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 56cf948..31da7e4 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -143,12 +143,14 @@
         // If we have a read-only database open, someone could be using it
         // (though they shouldn't), which would cause a lock to be held on
         // the file, and our attempts to open the database read-write would
-        // fail waiting for the file lock.  To prevent that, we acquire the
-        // lock on the read-only database, which shuts out other users.
+        // fail waiting for the file lock.  To prevent that, we acquire a lock
+        // on the read-only database, which shuts out other users.
 
         boolean success = false;
         SQLiteDatabase db = null;
-        if (mDatabase != null) mDatabase.lock();
+        if (mDatabase != null) {
+            mDatabase.lockPrimaryConnection();
+        }
         try {
             mIsInitializing = true;
             if (mName == null) {
@@ -185,11 +187,13 @@
             if (success) {
                 if (mDatabase != null) {
                     try { mDatabase.close(); } catch (Exception e) { }
-                    mDatabase.unlock();
+                    mDatabase.unlockPrimaryConnection();
                 }
                 mDatabase = db;
             } else {
-                if (mDatabase != null) mDatabase.unlock();
+                if (mDatabase != null) {
+                    mDatabase.unlockPrimaryConnection();
+                }
                 if (db != null) db.close();
             }
         }
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 2bbc6d7..8194458 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -17,225 +17,104 @@
 package android.database.sqlite;
 
 import android.database.DatabaseUtils;
-import android.database.Cursor;
 
-import java.util.HashMap;
+import java.util.Arrays;
 
 /**
  * A base class for compiled SQLite programs.
- *<p>
- * SQLiteProgram is NOT internally synchronized so code using a SQLiteProgram from multiple
- * threads should perform its own synchronization when using the SQLiteProgram.
+ * <p>
+ * This class is not thread-safe.
+ * </p>
  */
 public abstract class SQLiteProgram extends SQLiteClosable {
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
 
-    private static final String TAG = "SQLiteProgram";
+    private final SQLiteDatabase mDatabase;
+    private final String mSql;
+    private final boolean mReadOnly;
+    private final String[] mColumnNames;
+    private final int mNumParameters;
+    private final Object[] mBindArgs;
 
-    /** The database this program is compiled against.
-     * @hide
-     */
-    protected SQLiteDatabase mDatabase;
-
-    /** The SQL used to create this query */
-    /* package */ final String mSql;
-
-    /**
-     * Native linkage, do not modify. This comes from the database and should not be modified
-     * in here or in the native code.
-     * @hide
-     */
-    protected int nHandle;
-
-    /**
-     * the SQLiteCompiledSql object for the given sql statement.
-     */
-    /* package */ SQLiteCompiledSql mCompiledSql;
-
-    /**
-     * SQLiteCompiledSql statement id is populated with the corresponding object from the above
-     * member. This member is used by the native_bind_* methods
-     * @hide
-     */
-    protected int nStatement;
-
-    /**
-     * In the case of {@link SQLiteStatement}, this member stores the bindargs passed
-     * to the following methods, instead of actually doing the binding.
-     * <ul>
-     *   <li>{@link #bindBlob(int, byte[])}</li>
-     *   <li>{@link #bindDouble(int, double)}</li>
-     *   <li>{@link #bindLong(int, long)}</li>
-     *   <li>{@link #bindNull(int)}</li>
-     *   <li>{@link #bindString(int, String)}</li>
-     * </ul>
-     * <p>
-     * Each entry in the array is a Pair of
-     * <ol>
-     *   <li>bind arg position number</li>
-     *   <li>the value to be bound to the bindarg</li>
-     * </ol>
-     * <p>
-     * It is lazily initialized in the above bind methods
-     * and it is cleared in {@link #clearBindings()} method.
-     * <p>
-     * It is protected (in multi-threaded environment) by {@link SQLiteProgram}.this
-     */
-    /* package */ HashMap<Integer, Object> mBindArgs = null;
-    /* package */ final int mStatementType;
-    /* package */ static final int STATEMENT_CACHEABLE = 16;
-    /* package */ static final int STATEMENT_DONT_PREPARE = 32;
-    /* package */ static final int STATEMENT_USE_POOLED_CONN = 64;
-    /* package */ static final int STATEMENT_TYPE_MASK = 0x0f;
-
-    /* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
-        this(db, sql, null, true);
-    }
-
-    /* package */ SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs,
-            boolean compileFlag) {
+    SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs) {
+        mDatabase = db;
         mSql = sql.trim();
+
         int n = DatabaseUtils.getSqlStatementType(mSql);
         switch (n) {
-            case DatabaseUtils.STATEMENT_UPDATE:
-                mStatementType = n | STATEMENT_CACHEABLE;
-                break;
-            case DatabaseUtils.STATEMENT_SELECT:
-                mStatementType = n | STATEMENT_CACHEABLE | STATEMENT_USE_POOLED_CONN;
-                break;
             case DatabaseUtils.STATEMENT_BEGIN:
             case DatabaseUtils.STATEMENT_COMMIT:
             case DatabaseUtils.STATEMENT_ABORT:
-                mStatementType = n | STATEMENT_DONT_PREPARE;
+                mReadOnly = false;
+                mColumnNames = EMPTY_STRING_ARRAY;
+                mNumParameters = 0;
                 break;
+
             default:
-                mStatementType = n;
-        }
-        db.acquireReference();
-        db.addSQLiteClosable(this);
-        mDatabase = db;
-        nHandle = db.mNativeHandle;
-        if (bindArgs != null) {
-            int size = bindArgs.length;
-            for (int i = 0; i < size; i++) {
-                this.addToBindArgs(i + 1, bindArgs[i]);
-            }
-        }
-        if (compileFlag) {
-            compileAndbindAllArgs();
-        }
-    }
-
-    private void compileSql() {
-        // only cache CRUD statements
-        if ((mStatementType & STATEMENT_CACHEABLE) == 0) {
-            mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
-            nStatement = mCompiledSql.nStatement;
-            // since it is not in the cache, no need to acquire() it.
-            return;
+                boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT);
+                SQLiteStatementInfo info = new SQLiteStatementInfo();
+                db.getThreadSession().prepare(mSql,
+                        db.getThreadDefaultConnectionFlags(assumeReadOnly), info);
+                mReadOnly = info.readOnly;
+                mColumnNames = info.columnNames;
+                mNumParameters = info.numParameters;
+                break;
         }
 
-        mCompiledSql = mDatabase.getCompiledStatementForSql(mSql);
-        if (mCompiledSql == null) {
-            // create a new compiled-sql obj
-            mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
-
-            // add it to the cache of compiled-sqls
-            // but before adding it and thus making it available for anyone else to use it,
-            // make sure it is acquired by me.
-            mCompiledSql.acquire();
-            mDatabase.addToCompiledQueries(mSql, mCompiledSql);
+        if (mNumParameters != 0) {
+            mBindArgs = new Object[mNumParameters];
         } else {
-            // it is already in compiled-sql cache.
-            // try to acquire the object.
-            if (!mCompiledSql.acquire()) {
-                int last = mCompiledSql.nStatement;
-                // the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object.
-                // we can't have two different SQLiteProgam objects can't share the same
-                // CompiledSql object. create a new one.
-                // finalize it when I am done with it in "this" object.
-                mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql);
-                // since it is not in the cache, no need to acquire() it.
+            mBindArgs = null;
+        }
+
+        if (bindArgs != null) {
+            if (bindArgs.length > mNumParameters) {
+                throw new IllegalArgumentException("Too many bind arguments.  "
+                        + bindArgs.length + " arguments were provided but the statement needs "
+                        + mNumParameters + " arguments.");
             }
+            System.arraycopy(bindArgs, 0, mBindArgs, 0, bindArgs.length);
         }
-        nStatement = mCompiledSql.nStatement;
     }
 
-    @Override
-    protected void onAllReferencesReleased() {
-        release();
-        mDatabase.removeSQLiteClosable(this);
-        mDatabase.releaseReference();
+    final SQLiteDatabase getDatabase() {
+        return mDatabase;
     }
 
-    @Override
-    protected void onAllReferencesReleasedFromContainer() {
-        release();
-        mDatabase.releaseReference();
-    }
-
-    /* package */ void release() {
-        if (mCompiledSql == null) {
-            return;
-        }
-        mDatabase.releaseCompiledSqlObj(mSql, mCompiledSql);
-        mCompiledSql = null;
-        nStatement = 0;
-    }
-
-    /**
-     * Returns a unique identifier for this program.
-     *
-     * @return a unique identifier for this program
-     * @deprecated do not use this method. it is not guaranteed to be the same across executions of
-     * the SQL statement contained in this object.
-     */
-    @Deprecated
-    public final int getUniqueId() {
-      return -1;
-    }
-
-    /**
-     * used only for testing purposes
-     */
-    /* package */ int getSqlStatementId() {
-      synchronized(this) {
-        return (mCompiledSql == null) ? 0 : nStatement;
-      }
-    }
-
-    /* package */ String getSqlString() {
+    final String getSql() {
         return mSql;
     }
 
-    private void bind(int type, int index, Object value) {
-        mDatabase.verifyDbIsOpen();
-        addToBindArgs(index, (type == Cursor.FIELD_TYPE_NULL) ? null : value);
-        if (nStatement > 0) {
-            // bind only if the SQL statement is compiled
-            acquireReference();
-            try {
-                switch (type) {
-                    case Cursor.FIELD_TYPE_NULL:
-                        native_bind_null(index);
-                        break;
-                    case Cursor.FIELD_TYPE_BLOB:
-                        native_bind_blob(index, (byte[]) value);
-                        break;
-                    case Cursor.FIELD_TYPE_FLOAT:
-                        native_bind_double(index, (Double) value);
-                        break;
-                    case Cursor.FIELD_TYPE_INTEGER:
-                        native_bind_long(index, (Long) value);
-                        break;
-                    case Cursor.FIELD_TYPE_STRING:
-                    default:
-                        native_bind_string(index, (String) value);
-                        break;
-                }
-            } finally {
-                releaseReference();
-            }
-        }
+    final Object[] getBindArgs() {
+        return mBindArgs;
+    }
+
+    final String[] getColumnNames() {
+        return mColumnNames;
+    }
+
+    /** @hide */
+    protected final SQLiteSession getSession() {
+        return mDatabase.getThreadSession();
+    }
+
+    /** @hide */
+    protected final int getConnectionFlags() {
+        return mDatabase.getThreadDefaultConnectionFlags(mReadOnly);
+    }
+
+    /** @hide */
+    protected final void onCorruption() {
+        mDatabase.onCorruption();
+    }
+
+    /**
+     * Unimplemented.
+     * @deprecated This method is deprecated and must not be used.
+     */
+    @Deprecated
+    public final int getUniqueId() {
+        return -1;
     }
 
     /**
@@ -245,7 +124,7 @@
      * @param index The 1-based index to the parameter to bind null to
      */
     public void bindNull(int index) {
-        bind(Cursor.FIELD_TYPE_NULL, index, null);
+        bind(index, null);
     }
 
     /**
@@ -256,7 +135,7 @@
      * @param value The value to bind
      */
     public void bindLong(int index, long value) {
-        bind(Cursor.FIELD_TYPE_INTEGER, index, value);
+        bind(index, value);
     }
 
     /**
@@ -267,7 +146,7 @@
      * @param value The value to bind
      */
     public void bindDouble(int index, double value) {
-        bind(Cursor.FIELD_TYPE_FLOAT, index, value);
+        bind(index, value);
     }
 
     /**
@@ -275,13 +154,13 @@
      * {@link #clearBindings} is called.
      *
      * @param index The 1-based index to the parameter to bind
-     * @param value The value to bind
+     * @param value The value to bind, must not be null
      */
     public void bindString(int index, String value) {
         if (value == null) {
             throw new IllegalArgumentException("the bind value at index " + index + " is null");
         }
-        bind(Cursor.FIELD_TYPE_STRING, index, value);
+        bind(index, value);
     }
 
     /**
@@ -289,29 +168,21 @@
      * {@link #clearBindings} is called.
      *
      * @param index The 1-based index to the parameter to bind
-     * @param value The value to bind
+     * @param value The value to bind, must not be null
      */
     public void bindBlob(int index, byte[] value) {
         if (value == null) {
             throw new IllegalArgumentException("the bind value at index " + index + " is null");
         }
-        bind(Cursor.FIELD_TYPE_BLOB, index, value);
+        bind(index, value);
     }
 
     /**
      * Clears all existing bindings. Unset bindings are treated as NULL.
      */
     public void clearBindings() {
-        mBindArgs = null;
-        if (this.nStatement == 0) {
-            return;
-        }
-        mDatabase.verifyDbIsOpen();
-        acquireReference();
-        try {
-            native_clear_bindings();
-        } finally {
-            releaseReference();
+        if (mBindArgs != null) {
+            Arrays.fill(mBindArgs, null);
         }
     }
 
@@ -319,102 +190,33 @@
      * Release this program's resources, making it invalid.
      */
     public void close() {
-        mBindArgs = null;
-        if (nHandle == 0 || !mDatabase.isOpen()) {
-            return;
-        }
         releaseReference();
     }
 
-    private void addToBindArgs(int index, Object value) {
-        if (mBindArgs == null) {
-            mBindArgs = new HashMap<Integer, Object>();
-        }
-        mBindArgs.put(index, value);
-    }
-
-    /* package */ void compileAndbindAllArgs() {
-        if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
-            if (mBindArgs != null) {
-                throw new IllegalArgumentException("Can't pass bindargs for this sql :" + mSql);
-            }
-            // no need to prepare this SQL statement
-            return;
-        }
-        if (nStatement == 0) {
-            // SQL statement is not compiled yet. compile it now.
-            compileSql();
-        }
-        if (mBindArgs == null) {
-            return;
-        }
-        for (int index : mBindArgs.keySet()) {
-            Object value = mBindArgs.get(index);
-            if (value == null) {
-                native_bind_null(index);
-            } else if (value instanceof Double || value instanceof Float) {
-                native_bind_double(index, ((Number) value).doubleValue());
-            } else if (value instanceof Number) {
-                native_bind_long(index, ((Number) value).longValue());
-            } else if (value instanceof Boolean) {
-                Boolean bool = (Boolean)value;
-                native_bind_long(index, (bool) ? 1 : 0);
-                if (bool) {
-                    native_bind_long(index, 1);
-                } else {
-                    native_bind_long(index, 0);
-                }
-            } else if (value instanceof byte[]){
-                native_bind_blob(index, (byte[]) value);
-            } else {
-                native_bind_string(index, value.toString());
-            }
-        }
-    }
-
     /**
      * Given an array of String bindArgs, this method binds all of them in one single call.
      *
-     * @param bindArgs the String array of bind args.
+     * @param bindArgs the String array of bind args, none of which must be null.
      */
     public void bindAllArgsAsStrings(String[] bindArgs) {
-        if (bindArgs == null) {
-            return;
-        }
-        int size = bindArgs.length;
-        for (int i = 0; i < size; i++) {
-            bindString(i + 1, bindArgs[i]);
+        if (bindArgs != null) {
+            for (int i = bindArgs.length; i != 0; i--) {
+                bindString(i, bindArgs[i - 1]);
+            }
         }
     }
 
-    /* package */ synchronized final void setNativeHandle(int nHandle) {
-        this.nHandle = nHandle;
+    @Override
+    protected void onAllReferencesReleased() {
+        clearBindings();
     }
 
-    /**
-     * @hide
-     * Compiles SQL into a SQLite program.
-     *
-     * <P>The database lock must be held when calling this method.
-     * @param sql The SQL to compile.
-     */
-    protected final native void native_compile(String sql);
-
-    /**
-     * @hide
-     */
-    protected final native void native_finalize();
-
-    /** @hide */
-    protected final native void native_bind_null(int index);
-    /** @hide */
-    protected final native void native_bind_long(int index, long value);
-    /** @hide */
-    protected final native void native_bind_double(int index, double value);
-    /** @hide */
-    protected final native void native_bind_string(int index, String value);
-    /** @hide */
-    protected final native void native_bind_blob(int index, byte[] value);
-    private final native void native_clear_bindings();
+    private void bind(int index, Object value) {
+        if (index < 1 || index > mNumParameters) {
+            throw new IllegalArgumentException("Cannot bind argument at index "
+                    + index + " because the index is out of range.  "
+                    + "The statement has " + mNumParameters + " parameters.");
+        }
+        mBindArgs[index - 1] = value;
+    }
 }
-
diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java
index 6dd2539..17aa886 100644
--- a/core/java/android/database/sqlite/SQLiteQuery.java
+++ b/core/java/android/database/sqlite/SQLiteQuery.java
@@ -17,60 +17,24 @@
 package android.database.sqlite;
 
 import android.database.CursorWindow;
-import android.os.SystemClock;
-import android.text.TextUtils;
 import android.util.Log;
 
 /**
- * A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
- * This class is used by SQLiteCursor and isn't useful itself.
- *
- * SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple
- * threads should perform its own synchronization when using the SQLiteQuery.
+ * Represents a query that reads the resulting rows into a {@link SQLiteQuery}.
+ * This class is used by {@link SQLiteCursor} and isn't useful itself.
+ * <p>
+ * This class is not thread-safe.
+ * </p>
  */
 public final class SQLiteQuery extends SQLiteProgram {
     private static final String TAG = "SQLiteQuery";
 
-    private static native long nativeFillWindow(int databasePtr, int statementPtr, int windowPtr,
-            int offsetParam, int startPos, int requiredPos, boolean countAllRows);
-
-    private static native int nativeColumnCount(int statementPtr);
-    private static native String nativeColumnName(int statementPtr, int columnIndex);
-
-    /** The index of the unbound OFFSET parameter */
-    private int mOffsetIndex = 0;
-
-    private boolean mClosed = false;
-
-    /**
-     * Create a persistent query object.
-     *
-     * @param db The database that this query object is associated with
-     * @param query The SQL string for this query. 
-     * @param offsetIndex The 1-based index to the OFFSET parameter, 
-     */
-    /* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
-        super(db, query);
-        mOffsetIndex = offsetIndex;
-        bindAllArgsAsStrings(bindArgs);
+    SQLiteQuery(SQLiteDatabase db, String query) {
+        super(db, query, null);
     }
 
     /**
-     * Constructor used to create new instance to replace a given instance of this class.
-     * This constructor is used when the current Query object is now associated with a different
-     * {@link SQLiteDatabase} object.
-     *
-     * @param db The database that this query object is associated with
-     * @param query the instance of {@link SQLiteQuery} to be replaced
-     */
-    /* package */ SQLiteQuery(SQLiteDatabase db, SQLiteQuery query) {
-        super(db, query.mSql);
-        this.mBindArgs = query.mBindArgs;
-        this.mOffsetIndex = query.mOffsetIndex;
-    }
-
-    /**
-     * Reads rows into a buffer. This method acquires the database lock.
+     * Reads rows into a buffer.
      *
      * @param window The window to fill into
      * @param startPos The start position for filling the window.
@@ -81,106 +45,30 @@
      * @return Number of rows that were enumerated.  Might not be all rows
      * unless countAllRows is true.
      */
-    /* package */ int fillWindow(CursorWindow window,
-            int startPos, int requiredPos, boolean countAllRows) {
-        mDatabase.lock(mSql);
-        long timeStart = SystemClock.uptimeMillis();
+    int fillWindow(CursorWindow window, int startPos, int requiredPos, boolean countAllRows) {
+        acquireReference();
         try {
-            acquireReference();
+            window.acquireReference();
             try {
-                window.acquireReference();
-                long result = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
-                        mOffsetIndex, startPos, requiredPos, countAllRows);
-                int actualPos = (int)(result >> 32);
-                int countedRows = (int)result;
-                window.setStartPosition(actualPos);
-                if (SQLiteDebug.DEBUG_LOG_SLOW_QUERIES) {
-                    long elapsed = SystemClock.uptimeMillis() - timeStart;
-                    if (SQLiteDebug.shouldLogSlowQuery(elapsed)) {
-                        Log.d(TAG, "fillWindow took " + elapsed
-                                + " ms: window=\"" + window
-                                + "\", startPos=" + startPos
-                                + ", requiredPos=" + requiredPos
-                                + ", offset=" + mOffsetIndex
-                                + ", actualPos=" + actualPos
-                                + ", filledRows=" + window.getNumRows()
-                                + ", countedRows=" + countedRows
-                                + ", query=\"" + mSql + "\""
-                                + ", args=[" + (mBindArgs != null ?
-                                        TextUtils.join(", ", mBindArgs.values()) : "")
-                                + "]");
-                    }
-                }
-                mDatabase.logTimeStat(mSql, timeStart);
-                return countedRows;
-            } catch (IllegalStateException e){
-                // simply ignore it
-                return 0;
-            } catch (SQLiteDatabaseCorruptException e) {
-                mDatabase.onCorruption();
-                throw e;
-            } catch (SQLiteException e) {
-                Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql);
-                throw e;
+                int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(),
+                        window, startPos, requiredPos, countAllRows, getConnectionFlags());
+                return numRows;
+            } catch (SQLiteDatabaseCorruptException ex) {
+                onCorruption();
+                throw ex;
+            } catch (SQLiteException ex) {
+                Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
+                throw ex;
             } finally {
                 window.releaseReference();
             }
         } finally {
             releaseReference();
-            mDatabase.unlock();
-        }
-    }
-
-    /**
-     * Get the column count for the statement. Only valid on query based
-     * statements. The database must be locked
-     * when calling this method.
-     * 
-     * @return The number of column in the statement's result set.
-     */
-    /* package */ int columnCountLocked() {
-        acquireReference();
-        try {
-            return nativeColumnCount(nStatement);
-        } finally {
-            releaseReference();
-        }
-    }
-
-    /**
-     * Retrieves the column name for the given column index. The database must be locked
-     * when calling this method.
-     * 
-     * @param columnIndex the index of the column to get the name for
-     * @return The requested column's name
-     */
-    /* package */ String columnNameLocked(int columnIndex) {
-        acquireReference();
-        try {
-            return nativeColumnName(nStatement, columnIndex);
-        } finally {
-            releaseReference();
         }
     }
 
     @Override
     public String toString() {
-        return "SQLiteQuery: " + mSql;
-    }
-
-    @Override
-    public void close() {
-        super.close();
-        mClosed = true;
-    }
-
-    /**
-     * Called by SQLiteCursor when it is requeried.
-     */
-    /* package */ void requery() {
-        if (mClosed) {
-            throw new IllegalStateException("requerying a closed cursor");
-        }
-        compileAndbindAllArgs();
+        return "SQLiteQuery: " + getSql();
     }
 }
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 8f8eb6e..1b7b398 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -341,7 +341,7 @@
             // in both the wrapped and original forms.
             String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy,
                     having, sortOrder, limit);
-            validateSql(db, sqlForValidation); // will throw if query is invalid
+            validateQuerySql(db, sqlForValidation); // will throw if query is invalid
         }
 
         String sql = buildQuery(
@@ -357,16 +357,12 @@
     }
 
     /**
-     * Verifies that a SQL statement is valid by compiling it.
+     * Verifies that a SQL SELECT statement is valid by compiling it.
      * If the SQL statement is not valid, this method will throw a {@link SQLiteException}.
      */
-    private void validateSql(SQLiteDatabase db, String sql) {
-        db.lock(sql);
-        try {
-            new SQLiteCompiledSql(db, sql).releaseSqlStatement();
-        } finally {
-            db.unlock();
-        }
+    private void validateQuerySql(SQLiteDatabase db, String sql) {
+        db.getThreadSession().prepare(sql,
+                db.getThreadDefaultConnectionFlags(true /*readOnly*/), null);
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
new file mode 100644
index 0000000..61fe45a
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -0,0 +1,878 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+import android.database.CursorWindow;
+import android.database.DatabaseUtils;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Provides a single client the ability to use a database.
+ *
+ * <h2>About database sessions</h2>
+ * <p>
+ * Database access is always performed using a session.  The session
+ * manages the lifecycle of transactions and database connections.
+ * </p><p>
+ * Sessions can be used to perform both read-only and read-write operations.
+ * There is some advantage to knowing when a session is being used for
+ * read-only purposes because the connection pool can optimize the use
+ * of the available connections to permit multiple read-only operations
+ * to execute in parallel whereas read-write operations may need to be serialized.
+ * </p><p>
+ * When <em>Write Ahead Logging (WAL)</em> is enabled, the database can
+ * execute simultaneous read-only and read-write transactions, provided that
+ * at most one read-write transaction is performed at a time.  When WAL is not
+ * enabled, read-only transactions can execute in parallel but read-write
+ * transactions are mutually exclusive.
+ * </p>
+ *
+ * <h2>Ownership and concurrency guarantees</h2>
+ * <p>
+ * Session objects are not thread-safe.  In fact, session objects are thread-bound.
+ * The {@link SQLiteDatabase} uses a thread-local variable to associate a session
+ * with each thread for the use of that thread alone.  Consequently, each thread
+ * has its own session object and therefore its own transaction state independent
+ * of other threads.
+ * </p><p>
+ * A thread has at most one session per database.  This constraint ensures that
+ * a thread can never use more than one database connection at a time for a
+ * given database.  As the number of available database connections is limited,
+ * if a single thread tried to acquire multiple connections for the same database
+ * at the same time, it might deadlock.  Therefore we allow there to be only
+ * one session (so, at most one connection) per thread per database.
+ * </p>
+ *
+ * <h2>Transactions</h2>
+ * <p>
+ * There are two kinds of transaction: implicit transactions and explicit
+ * transactions.
+ * </p><p>
+ * An implicit transaction is created whenever a database operation is requested
+ * and there is no explicit transaction currently in progress.  An implicit transaction
+ * only lasts for the duration of the database operation in question and then it
+ * is ended.  If the database operation was successful, then its changes are committed.
+ * </p><p>
+ * An explicit transaction is started by calling {@link #beginTransaction} and
+ * specifying the desired transaction mode.  Once an explicit transaction has begun,
+ * all subsequent database operations will be performed as part of that transaction.
+ * To end an explicit transaction, first call {@link #setTransactionSuccessful} if the
+ * transaction was successful, then call {@link #end}.  If the transaction was
+ * marked successful, its changes will be committed, otherwise they will be rolled back.
+ * </p><p>
+ * Explicit transactions can also be nested.  A nested explicit transaction is
+ * started with {@link #beginTransaction}, marked successful with
+ * {@link #setTransactionSuccessful}and ended with {@link #endTransaction}.
+ * If any nested transaction is not marked successful, then the entire transaction
+ * including all of its nested transactions will be rolled back
+ * when the outermost transaction is ended.
+ * </p><p>
+ * To improve concurrency, an explicit transaction can be yielded by calling
+ * {@link #yieldTransaction}.  If there is contention for use of the database,
+ * then yielding ends the current transaction, commits its changes, releases the
+ * database connection for use by another session for a little while, and starts a
+ * new transaction with the same properties as the original one.
+ * Changes committed by {@link #yieldTransaction} cannot be rolled back.
+ * </p><p>
+ * When a transaction is started, the client can provide a {@link SQLiteTransactionListener}
+ * to listen for notifications of transaction-related events.
+ * </p><p>
+ * Recommended usage:
+ * <code><pre>
+ * // First, begin the transaction.
+ * session.beginTransaction(SQLiteSession.TRANSACTION_MODE_DEFERRED, 0);
+ * try {
+ *     // Then do stuff...
+ *     session.execute("INSERT INTO ...", null, 0);
+ *
+ *     // As the very last step before ending the transaction, mark it successful.
+ *     session.setTransactionSuccessful();
+ * } finally {
+ *     // Finally, end the transaction.
+ *     // This statement will commit the transaction if it was marked successful or
+ *     // roll it back otherwise.
+ *     session.endTransaction();
+ * }
+ * </pre></code>
+ * </p>
+ *
+ * <h2>Database connections</h2>
+ * <p>
+ * A {@link SQLiteDatabase} can have multiple active sessions at the same
+ * time.  Each session acquires and releases connections to the database
+ * as needed to perform each requested database transaction.  If all connections
+ * are in use, then database transactions on some sessions will block until a
+ * connection becomes available.
+ * </p><p>
+ * The session acquires a single database connection only for the duration
+ * of a single (implicit or explicit) database transaction, then releases it.
+ * This characteristic allows a small pool of database connections to be shared
+ * efficiently by multiple sessions as long as they are not all trying to perform
+ * database transactions at the same time.
+ * </p>
+ *
+ * <h2>Responsiveness</h2>
+ * <p>
+ * Because there are a limited number of database connections and the session holds
+ * a database connection for the entire duration of a database transaction,
+ * it is important to keep transactions short.  This is especially important
+ * for read-write transactions since they may block other transactions
+ * from executing.  Consider calling {@link #yieldTransaction} periodically
+ * during long-running transactions.
+ * </p><p>
+ * Another important consideration is that transactions that take too long to
+ * run may cause the application UI to become unresponsive.  Even if the transaction
+ * is executed in a background thread, the user will get bored and
+ * frustrated if the application shows no data for several seconds while
+ * a transaction runs.
+ * </p><p>
+ * Guidelines:
+ * <ul>
+ * <li>Do not perform database transactions on the UI thread.</li>
+ * <li>Keep database transactions as short as possible.</li>
+ * <li>Simple queries often run faster than complex queries.</li>
+ * <li>Measure the performance of your database transactions.</li>
+ * <li>Consider what will happen when the size of the data set grows.
+ * A query that works well on 100 rows may struggle with 10,000.</li>
+ * </ul>
+ *
+ * TODO: Support timeouts on all possibly blocking operations.
+ *
+ * @hide
+ */
+public final class SQLiteSession {
+    private final SQLiteConnectionPool mConnectionPool;
+
+    private SQLiteConnection mConnection;
+    private int mConnectionFlags;
+    private Transaction mTransactionPool;
+    private Transaction mTransactionStack;
+
+    /**
+     * Transaction mode: Deferred.
+     * <p>
+     * In a deferred transaction, no locks are acquired on the database
+     * until the first operation is performed.  If the first operation is
+     * read-only, then a <code>SHARED</code> lock is acquired, otherwise
+     * a <code>RESERVED</code> lock is acquired.
+     * </p><p>
+     * While holding a <code>SHARED</code> lock, this session is only allowed to
+     * read but other sessions are allowed to read or write.
+     * While holding a <code>RESERVED</code> lock, this session is allowed to read
+     * or write but other sessions are only allowed to read.
+     * </p><p>
+     * Because the lock is only acquired when needed in a deferred transaction,
+     * it is possible for another session to write to the database first before
+     * this session has a chance to do anything.
+     * </p><p>
+     * Corresponds to the SQLite <code>BEGIN DEFERRED</code> transaction mode.
+     * </p>
+     */
+    public static final int TRANSACTION_MODE_DEFERRED = 0;
+
+    /**
+     * Transaction mode: Immediate.
+     * <p>
+     * When an immediate transaction begins, the session acquires a
+     * <code>RESERVED</code> lock.
+     * </p><p>
+     * While holding a <code>RESERVED</code> lock, this session is allowed to read
+     * or write but other sessions are only allowed to read.
+     * </p><p>
+     * Corresponds to the SQLite <code>BEGIN IMMEDIATE</code> transaction mode.
+     * </p>
+     */
+    public static final int TRANSACTION_MODE_IMMEDIATE = 1;
+
+    /**
+     * Transaction mode: Exclusive.
+     * <p>
+     * When an exclusive transaction begins, the session acquires an
+     * <code>EXCLUSIVE</code> lock.
+     * </p><p>
+     * While holding an <code>EXCLUSIVE</code> lock, this session is allowed to read
+     * or write but no other sessions are allowed to access the database.
+     * </p><p>
+     * Corresponds to the SQLite <code>BEGIN EXCLUSIVE</code> transaction mode.
+     * </p>
+     */
+    public static final int TRANSACTION_MODE_EXCLUSIVE = 2;
+
+    /**
+     * Creates a session bound to the specified connection pool.
+     *
+     * @param connectionPool The connection pool.
+     */
+    public SQLiteSession(SQLiteConnectionPool connectionPool) {
+        if (connectionPool == null) {
+            throw new IllegalArgumentException("connectionPool must not be null");
+        }
+
+        mConnectionPool = connectionPool;
+    }
+
+    /**
+     * Returns true if the session has a transaction in progress.
+     *
+     * @return True if the session has a transaction in progress.
+     */
+    public boolean hasTransaction() {
+        return mTransactionStack != null;
+    }
+
+    /**
+     * Returns true if the session has a nested transaction in progress.
+     *
+     * @return True if the session has a nested transaction in progress.
+     */
+    public boolean hasNestedTransaction() {
+        return mTransactionStack != null && mTransactionStack.mParent != null;
+    }
+
+    /**
+     * Returns true if the session has an active database connection.
+     *
+     * @return True if the session has an active database connection.
+     */
+    public boolean hasConnection() {
+        return mConnection != null;
+    }
+
+    /**
+     * Begins a transaction.
+     * <p>
+     * Transactions may nest.  If the transaction is not in progress,
+     * then a database connection is obtained and a new transaction is started.
+     * Otherwise, a nested transaction is started.
+     * </p><p>
+     * Each call to {@link #beginTransaction} must be matched exactly by a call
+     * to {@link #endTransaction}.  To mark a transaction as successful,
+     * call {@link #setTransactionSuccessful} before calling {@link #endTransaction}.
+     * If the transaction is not successful, or if any of its nested
+     * transactions were not successful, then the entire transaction will
+     * be rolled back when the outermost transaction is ended.
+     * </p>
+     *
+     * @param transactionMode The transaction mode.  One of: {@link #TRANSACTION_MODE_DEFERRED},
+     * {@link #TRANSACTION_MODE_IMMEDIATE}, or {@link #TRANSACTION_MODE_EXCLUSIVE}.
+     * Ignored when creating a nested transaction.
+     * @param transactionListener The transaction listener, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     *
+     * @throws IllegalStateException if {@link #setTransactionSuccessful} has already been
+     * called for the current transaction.
+     *
+     * @see #setTransactionSuccessful
+     * @see #yieldTransaction
+     * @see #endTransaction
+     */
+    public void beginTransaction(int transactionMode,
+            SQLiteTransactionListener transactionListener, int connectionFlags) {
+        throwIfTransactionMarkedSuccessful();
+        beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags);
+    }
+
+    private void beginTransactionUnchecked(int transactionMode,
+            SQLiteTransactionListener transactionListener, int connectionFlags) {
+        acquireConnectionIfNoTransaction(null, connectionFlags); // might throw
+        try {
+            // Set up the transaction such that we can back out safely
+            // in case we fail part way.
+            if (mTransactionStack == null) {
+                // Execute SQL might throw a runtime exception.
+                switch (transactionMode) {
+                    case TRANSACTION_MODE_IMMEDIATE:
+                        mConnection.execute("BEGIN IMMEDIATE;", null); // might throw
+                        break;
+                    case TRANSACTION_MODE_EXCLUSIVE:
+                        mConnection.execute("BEGIN EXCLUSIVE;", null); // might throw
+                        break;
+                    default:
+                        mConnection.execute("BEGIN;", null); // might throw
+                        break;
+                }
+            }
+
+            // Listener might throw a runtime exception.
+            if (transactionListener != null) {
+                try {
+                    transactionListener.onBegin(); // might throw
+                } catch (RuntimeException ex) {
+                    if (mTransactionStack == null) {
+                        mConnection.execute("ROLLBACK;", null); // might throw
+                    }
+                    throw ex;
+                }
+            }
+
+            // Bookkeeping can't throw, except an OOM, which is just too bad...
+            Transaction transaction = obtainTransaction(transactionMode, transactionListener);
+            transaction.mParent = mTransactionStack;
+            mTransactionStack = transaction;
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Marks the current transaction as having completed successfully.
+     * <p>
+     * This method can be called at most once between {@link #beginTransaction} and
+     * {@link #endTransaction} to indicate that the changes made by the transaction should be
+     * committed.  If this method is not called, the changes will be rolled back
+     * when the transaction is ended.
+     * </p>
+     *
+     * @throws IllegalStateException if there is no current transaction, or if
+     * {@link #setTransactionSuccessful} has already been called for the current transaction.
+     *
+     * @see #beginTransaction
+     * @see #endTransaction
+     */
+    public void setTransactionSuccessful() {
+        throwIfNoTransaction();
+        throwIfTransactionMarkedSuccessful();
+
+        mTransactionStack.mMarkedSuccessful = true;
+    }
+
+    /**
+     * Ends the current transaction and commits or rolls back changes.
+     * <p>
+     * If this is the outermost transaction (not nested within any other
+     * transaction), then the changes are committed if {@link #setTransactionSuccessful}
+     * was called or rolled back otherwise.
+     * </p><p>
+     * This method must be called exactly once for each call to {@link #beginTransaction}.
+     * </p>
+     *
+     * @throws IllegalStateException if there is no current transaction.
+     *
+     * @see #beginTransaction
+     * @see #setTransactionSuccessful
+     * @see #yieldTransaction
+     */
+    public void endTransaction() {
+        throwIfNoTransaction();
+        assert mConnection != null;
+
+        endTransactionUnchecked();
+    }
+
+    private void endTransactionUnchecked() {
+        final Transaction top = mTransactionStack;
+        boolean successful = top.mMarkedSuccessful && !top.mChildFailed;
+
+        RuntimeException listenerException = null;
+        final SQLiteTransactionListener listener = top.mListener;
+        if (listener != null) {
+            try {
+                if (successful) {
+                    listener.onCommit(); // might throw
+                } else {
+                    listener.onRollback(); // might throw
+                }
+            } catch (RuntimeException ex) {
+                listenerException = ex;
+                successful = false;
+            }
+        }
+
+        mTransactionStack = top.mParent;
+        recycleTransaction(top);
+
+        if (mTransactionStack != null) {
+            if (!successful) {
+                mTransactionStack.mChildFailed = true;
+            }
+        } else {
+            try {
+                if (successful) {
+                    mConnection.execute("COMMIT;", null); // might throw
+                } else {
+                    mConnection.execute("ROLLBACK;", null); // might throw
+                }
+            } finally {
+                releaseConnectionIfNoTransaction(); // might throw
+            }
+        }
+
+        if (listenerException != null) {
+            throw listenerException;
+        }
+    }
+
+    /**
+     * Temporarily ends a transaction to let other threads have use of
+     * the database.  Begins a new transaction after a specified delay.
+     * <p>
+     * If there are other threads waiting to acquire connections,
+     * then the current transaction is committed and the database
+     * connection is released.  After a short delay, a new transaction
+     * is started.
+     * </p><p>
+     * The transaction is assumed to be successful so far.  Do not call
+     * {@link #setTransactionSuccessful()} before calling this method.
+     * This method will fail if the transaction has already been marked
+     * successful.
+     * </p><p>
+     * The changes that were committed by a yield cannot be rolled back later.
+     * </p><p>
+     * Before this method was called, there must already have been
+     * a transaction in progress.  When this method returns, there will
+     * still be a transaction in progress, either the same one as before
+     * or a new one if the transaction was actually yielded.
+     * </p><p>
+     * This method should not be called when there is a nested transaction
+     * in progress because it is not possible to yield a nested transaction.
+     * If <code>throwIfNested</code> is true, then attempting to yield
+     * a nested transaction will throw {@link IllegalStateException}, otherwise
+     * the method will return <code>false</code> in that case.
+     * </p><p>
+     * If there is no nested transaction in progress but a previous nested
+     * transaction failed, then the transaction is not yielded (because it
+     * must be rolled back) and this method returns <code>false</code>.
+     * </p>
+     *
+     * @param sleepAfterYieldDelayMillis A delay time to wait after yielding
+     * the database connection to allow other threads some time to run.
+     * If the value is less than or equal to zero, there will be no additional
+     * delay beyond the time it will take to begin a new transaction.
+     * @param throwIfUnsafe If true, then instead of returning false when no
+     * transaction is in progress, a nested transaction is in progress, or when
+     * the transaction has already been marked successful, throws {@link IllegalStateException}.
+     * @return True if the transaction was actually yielded.
+     *
+     * @throws IllegalStateException if <code>throwIfNested</code> is true and
+     * there is no current transaction, there is a nested transaction in progress or
+     * if {@link #setTransactionSuccessful} has already been called for the current transaction.
+     *
+     * @see #beginTransaction
+     * @see #endTransaction
+     */
+    public boolean yieldTransaction(long sleepAfterYieldDelayMillis, boolean throwIfUnsafe) {
+        if (throwIfUnsafe) {
+            throwIfNoTransaction();
+            throwIfTransactionMarkedSuccessful();
+            throwIfNestedTransaction();
+        } else {
+            if (mTransactionStack == null || mTransactionStack.mMarkedSuccessful
+                    || mTransactionStack.mParent != null) {
+                return false;
+            }
+        }
+        assert mConnection != null;
+
+        if (mTransactionStack.mChildFailed) {
+            return false;
+        }
+
+        return yieldTransactionUnchecked(sleepAfterYieldDelayMillis); // might throw
+    }
+
+    private boolean yieldTransactionUnchecked(long sleepAfterYieldDelayMillis) {
+        if (!mConnectionPool.shouldYieldConnection(mConnection, mConnectionFlags)) {
+            return false;
+        }
+
+        final int transactionMode = mTransactionStack.mMode;
+        final SQLiteTransactionListener listener = mTransactionStack.mListener;
+        final int connectionFlags = mConnectionFlags;
+        endTransactionUnchecked(); // might throw
+
+        if (sleepAfterYieldDelayMillis > 0) {
+            try {
+                Thread.sleep(sleepAfterYieldDelayMillis);
+            } catch (InterruptedException ex) {
+                // we have been interrupted, that's all we need to do
+            }
+        }
+
+        beginTransactionUnchecked(transactionMode, listener, connectionFlags); // might throw
+        return true;
+    }
+
+    /**
+     * Prepares a statement for execution but does not bind its parameters or execute it.
+     * <p>
+     * This method can be used to check for syntax errors during compilation
+     * prior to execution of the statement.  If the {@code outStatementInfo} argument
+     * is not null, the provided {@link SQLiteStatementInfo} object is populated
+     * with information about the statement.
+     * </p><p>
+     * A prepared statement makes no reference to the arguments that may eventually
+     * be bound to it, consequently it it possible to cache certain prepared statements
+     * such as SELECT or INSERT/UPDATE statements.  If the statement is cacheable,
+     * then it will be stored in the cache for later and reused if possible.
+     * </p>
+     *
+     * @param sql The SQL statement to prepare.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @param outStatementInfo The {@link SQLiteStatementInfo} object to populate
+     * with information about the statement, or null if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error.
+     */
+    public void prepare(String sql, int connectionFlags, SQLiteStatementInfo outStatementInfo) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            mConnection.prepare(sql, outStatementInfo); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Executes a statement that does not return a result.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public void execute(String sql, Object[] bindArgs, int connectionFlags) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+            return;
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            mConnection.execute(sql, bindArgs); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Executes a statement that returns a single <code>long</code> result.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @return The value of the first column in the first row of the result set
+     * as a <code>long</code>, or zero if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public long executeForLong(String sql, Object[] bindArgs, int connectionFlags) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+            return 0;
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            return mConnection.executeForLong(sql, bindArgs); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Executes a statement that returns a single {@link String} result.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @return The value of the first column in the first row of the result set
+     * as a <code>String</code>, or null if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public String executeForString(String sql, Object[] bindArgs, int connectionFlags) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+            return null;
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            return mConnection.executeForString(sql, bindArgs); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Executes a statement that returns a single BLOB result as a
+     * file descriptor to a shared memory region.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @return The file descriptor for a shared memory region that contains
+     * the value of the first column in the first row of the result set as a BLOB,
+     * or null if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs,
+            int connectionFlags) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+            return null;
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            return mConnection.executeForBlobFileDescriptor(sql, bindArgs); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Executes a statement that returns a count of the number of rows
+     * that were changed.  Use for UPDATE or DELETE SQL statements.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @return The number of rows that were changed.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+            return 0;
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            return mConnection.executeForChangedRowCount(sql, bindArgs); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Executes a statement that returns the row id of the last row inserted
+     * by the statement.  Use for INSERT SQL statements.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @return The row id of the last row that was inserted, or 0 if none.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+
+        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+            return 0;
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            return mConnection.executeForLastInsertedRowId(sql, bindArgs); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Executes a statement and populates the specified {@link CursorWindow}
+     * with a range of results.  Returns the number of rows that were counted
+     * during query execution.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param window The cursor window to clear and fill.
+     * @param startPos The start position for filling the window.
+     * @param requiredPos The position of a row that MUST be in the window.
+     * If it won't fit, then the query should discard part of what it filled
+     * so that it does.  Must be greater than or equal to <code>startPos</code>.
+     * @param countAllRows True to count all rows that the query would return
+     * regagless of whether they fit in the window.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @return The number of rows that were counted during query execution.  Might
+     * not be all rows in the result set unless <code>countAllRows</code> is true.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    public int executeForCursorWindow(String sql, Object[] bindArgs,
+            CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
+            int connectionFlags) {
+        if (sql == null) {
+            throw new IllegalArgumentException("sql must not be null.");
+        }
+        if (window == null) {
+            throw new IllegalArgumentException("window must not be null.");
+        }
+
+        if (executeSpecial(sql, bindArgs, connectionFlags)) {
+            window.clear();
+            return 0;
+        }
+
+        acquireConnectionIfNoTransaction(sql, connectionFlags); // might throw
+        try {
+            return mConnection.executeForCursorWindow(sql, bindArgs,
+                    window, startPos, requiredPos, countAllRows); // might throw
+        } finally {
+            releaseConnectionIfNoTransaction(); // might throw
+        }
+    }
+
+    /**
+     * Performs special reinterpretation of certain SQL statements such as "BEGIN",
+     * "COMMIT" and "ROLLBACK" to ensure that transaction state invariants are
+     * maintained.
+     *
+     * This function is mainly used to support legacy apps that perform their
+     * own transactions by executing raw SQL rather than calling {@link #beginTransaction}
+     * and the like.
+     *
+     * @param sql The SQL statement to execute.
+     * @param bindArgs The arguments to bind, or null if none.
+     * @param connectionFlags The connection flags to use if a connection must be
+     * acquired by this operation.  Refer to {@link SQLiteConnectionPool}.
+     * @return True if the statement was of a special form that was handled here,
+     * false otherwise.
+     *
+     * @throws SQLiteException if an error occurs, such as a syntax error
+     * or invalid number of bind arguments.
+     */
+    private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags) {
+        final int type = DatabaseUtils.getSqlStatementType(sql);
+        switch (type) {
+            case DatabaseUtils.STATEMENT_BEGIN:
+                beginTransaction(TRANSACTION_MODE_EXCLUSIVE, null, connectionFlags);
+                return true;
+
+            case DatabaseUtils.STATEMENT_COMMIT:
+                setTransactionSuccessful();
+                endTransaction();
+                return true;
+
+            case DatabaseUtils.STATEMENT_ABORT:
+                endTransaction();
+                return true;
+        }
+        return false;
+    }
+
+    private void acquireConnectionIfNoTransaction(String sql, int connectionFlags) {
+        if (mTransactionStack == null) {
+            assert mConnection == null;
+            mConnection = mConnectionPool.acquireConnection(sql, connectionFlags); // might throw
+            mConnectionFlags = connectionFlags;
+        }
+    }
+
+    private void releaseConnectionIfNoTransaction() {
+        if (mTransactionStack == null && mConnection != null) {
+            try {
+                mConnectionPool.releaseConnection(mConnection); // might throw
+            } finally {
+                mConnection = null;
+            }
+        }
+    }
+
+    private void throwIfNoTransaction() {
+        if (mTransactionStack == null) {
+            throw new IllegalStateException("Cannot perform this operation because "
+                    + "there is no current transaction.");
+        }
+    }
+
+    private void throwIfTransactionMarkedSuccessful() {
+        if (mTransactionStack != null && mTransactionStack.mMarkedSuccessful) {
+            throw new IllegalStateException("Cannot perform this operation because "
+                    + "the transaction has already been marked successful.  The only "
+                    + "thing you can do now is call endTransaction().");
+        }
+    }
+
+    private void throwIfNestedTransaction() {
+        if (mTransactionStack == null && mTransactionStack.mParent != null) {
+            throw new IllegalStateException("Cannot perform this operation because "
+                    + "a nested transaction is in progress.");
+        }
+    }
+
+    private Transaction obtainTransaction(int mode, SQLiteTransactionListener listener) {
+        Transaction transaction = mTransactionPool;
+        if (transaction != null) {
+            mTransactionPool = transaction.mParent;
+            transaction.mParent = null;
+            transaction.mMarkedSuccessful = false;
+            transaction.mChildFailed = false;
+        } else {
+            transaction = new Transaction();
+        }
+        transaction.mMode = mode;
+        transaction.mListener = listener;
+        return transaction;
+    }
+
+    private void recycleTransaction(Transaction transaction) {
+        transaction.mParent = mTransactionPool;
+        transaction.mListener = null;
+        mTransactionPool = transaction;
+    }
+
+    private static final class Transaction {
+        public Transaction mParent;
+        public int mMode;
+        public SQLiteTransactionListener mListener;
+        public boolean mMarkedSuccessful;
+        public boolean mChildFailed;
+    }
+}
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index c99a6fb..4e20da0 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,47 +16,19 @@
 
 package android.database.sqlite;
 
-import android.database.DatabaseUtils;
 import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.IOException;
-
-import dalvik.system.BlockGuard;
 
 /**
- * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
- * The statement cannot return multiple rows, but 1x1 result sets are allowed.
- * Don't use SQLiteStatement constructor directly, please use
- * {@link SQLiteDatabase#compileStatement(String)}
- *<p>
- * SQLiteStatement is NOT internally synchronized so code using a SQLiteStatement from multiple
- * threads should perform its own synchronization when using the SQLiteStatement.
+ * Represents a statement that can be executed against a database.  The statement
+ * cannot return multiple rows or columns, but single value (1 x 1) result sets
+ * are supported.
+ * <p>
+ * This class is not thread-safe.
+ * </p>
  */
-@SuppressWarnings("deprecation")
-public final class SQLiteStatement extends SQLiteProgram
-{
-    private static final String TAG = "SQLiteStatement";
-
-    private static final boolean READ = true;
-    private static final boolean WRITE = false;
-
-    private SQLiteDatabase mOrigDb;
-    private int mState;
-    /** possible value for {@link #mState}. indicates that a transaction is started. */
-    private static final int TRANS_STARTED = 1;
-    /** possible value for {@link #mState}. indicates that a lock is acquired. */
-    private static final int LOCK_ACQUIRED = 2;
-
-    /**
-     * Don't use SQLiteStatement constructor directly, please use
-     * {@link SQLiteDatabase#compileStatement(String)}
-     * @param db
-     * @param sql
-     */
-    /* package */ SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
-        super(db, sql, bindArgs, false /* don't compile sql statement */);
+public final class SQLiteStatement extends SQLiteProgram {
+    SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) {
+        super(db, sql, bindArgs);
     }
 
     /**
@@ -67,7 +39,15 @@
      *         some reason
      */
     public void execute() {
-        executeUpdateDelete();
+        acquireReference();
+        try {
+            getSession().execute(getSql(), getBindArgs(), getConnectionFlags());
+        } catch (SQLiteDatabaseCorruptException ex) {
+            onCorruption();
+            throw ex;
+        } finally {
+            releaseReference();
+        }
     }
 
     /**
@@ -79,21 +59,15 @@
      *         some reason
      */
     public int executeUpdateDelete() {
+        acquireReference();
         try {
-            saveSqlAsLastSqlStatement();
-            acquireAndLock(WRITE);
-            int numChanges = 0;
-            if ((mStatementType & STATEMENT_DONT_PREPARE) > 0) {
-                // since the statement doesn't have to be prepared,
-                // call the following native method which will not prepare
-                // the query plan
-                native_executeSql(mSql);
-            } else {
-                numChanges = native_execute();
-            }
-            return numChanges;
+            return getSession().executeForChangedRowCount(
+                    getSql(), getBindArgs(), getConnectionFlags());
+        } catch (SQLiteDatabaseCorruptException ex) {
+            onCorruption();
+            throw ex;
         } finally {
-            releaseAndUnlock();
+            releaseReference();
         }
     }
 
@@ -107,23 +81,18 @@
      *         some reason
      */
     public long executeInsert() {
+        acquireReference();
         try {
-            saveSqlAsLastSqlStatement();
-            acquireAndLock(WRITE);
-            return native_executeInsert();
+            return getSession().executeForLastInsertedRowId(
+                    getSql(), getBindArgs(), getConnectionFlags());
+        } catch (SQLiteDatabaseCorruptException ex) {
+            onCorruption();
+            throw ex;
         } finally {
-            releaseAndUnlock();
+            releaseReference();
         }
     }
 
-    private void saveSqlAsLastSqlStatement() {
-        if (((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
-                DatabaseUtils.STATEMENT_UPDATE) ||
-                (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
-                DatabaseUtils.STATEMENT_BEGIN) {
-            mDatabase.setLastSqlStatement(mSql);
-        }
-    }
     /**
      * Execute a statement that returns a 1 by 1 table with a numeric value.
      * For example, SELECT COUNT(*) FROM table;
@@ -133,17 +102,15 @@
      * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
      */
     public long simpleQueryForLong() {
+        acquireReference();
         try {
-            long timeStart = acquireAndLock(READ);
-            long retValue = native_1x1_long();
-            mDatabase.logTimeStat(mSql, timeStart);
-            return retValue;
-        } catch (SQLiteDoneException e) {
-            throw new SQLiteDoneException(
-                    "expected 1 row from this query but query returned no data. check the query: " +
-                    mSql);
+            return getSession().executeForLong(
+                    getSql(), getBindArgs(), getConnectionFlags());
+        } catch (SQLiteDatabaseCorruptException ex) {
+            onCorruption();
+            throw ex;
         } finally {
-            releaseAndUnlock();
+            releaseReference();
         }
     }
 
@@ -156,17 +123,15 @@
      * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
      */
     public String simpleQueryForString() {
+        acquireReference();
         try {
-            long timeStart = acquireAndLock(READ);
-            String retValue = native_1x1_string();
-            mDatabase.logTimeStat(mSql, timeStart);
-            return retValue;
-        } catch (SQLiteDoneException e) {
-            throw new SQLiteDoneException(
-                    "expected 1 row from this query but query returned no data. check the query: " +
-                    mSql);
+            return getSession().executeForString(
+                    getSql(), getBindArgs(), getConnectionFlags());
+        } catch (SQLiteDatabaseCorruptException ex) {
+            onCorruption();
+            throw ex;
         } finally {
-            releaseAndUnlock();
+            releaseReference();
         }
     }
 
@@ -179,121 +144,20 @@
      * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
      */
     public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
-        try {
-            long timeStart = acquireAndLock(READ);
-            ParcelFileDescriptor retValue = native_1x1_blob_ashmem();
-            mDatabase.logTimeStat(mSql, timeStart);
-            return retValue;
-        } catch (IOException ex) {
-            Log.e(TAG, "simpleQueryForBlobFileDescriptor() failed", ex);
-            return null;
-        } catch (SQLiteDoneException e) {
-            throw new SQLiteDoneException(
-                    "expected 1 row from this query but query returned no data. check the query: " +
-                    mSql);
-        } finally {
-            releaseAndUnlock();
-        }
-    }
-
-    /**
-     * Called before every method in this class before executing a SQL statement,
-     * this method does the following:
-     * <ul>
-     *   <li>make sure the database is open</li>
-     *   <li>get a database connection from the connection pool,if possible</li>
-     *   <li>notifies {@link BlockGuard} of read/write</li>
-     *   <li>if the SQL statement is an update, start transaction if not already in one.
-     *   otherwise, get lock on the database</li>
-     *   <li>acquire reference on this object</li>
-     *   <li>and then return the current time _after_ the database lock was acquired</li>
-     * </ul>
-     * <p>
-     * This method removes the duplicate code from the other public
-     * methods in this class.
-     */
-    private long acquireAndLock(boolean rwFlag) {
-        mState = 0;
-        // use pooled database connection handles for SELECT SQL statements
-        mDatabase.verifyDbIsOpen();
-        SQLiteDatabase db = ((mStatementType & SQLiteProgram.STATEMENT_USE_POOLED_CONN) > 0)
-                ? mDatabase.getDbConnection(mSql) : mDatabase;
-        // use the database connection obtained above
-        mOrigDb = mDatabase;
-        mDatabase = db;
-        setNativeHandle(mDatabase.mNativeHandle);
-        if (rwFlag == WRITE) {
-            BlockGuard.getThreadPolicy().onWriteToDisk();
-        } else {
-            BlockGuard.getThreadPolicy().onReadFromDisk();
-        }
-
-        /*
-         * Special case handling of SQLiteDatabase.execSQL("BEGIN transaction").
-         * we know it is execSQL("BEGIN transaction") from the caller IF there is no lock held.
-         * beginTransaction() methods in SQLiteDatabase call lockForced() before
-         * calling execSQL("BEGIN transaction").
-         */
-        if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) == DatabaseUtils.STATEMENT_BEGIN) {
-            if (!mDatabase.isDbLockedByCurrentThread()) {
-                // transaction is  NOT started by calling beginTransaction() methods in
-                // SQLiteDatabase
-                mDatabase.setTransactionUsingExecSqlFlag();
-            }
-        } else if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
-                DatabaseUtils.STATEMENT_UPDATE) {
-            // got update SQL statement. if there is NO pending transaction, start one
-            if (!mDatabase.inTransaction()) {
-                mDatabase.beginTransactionNonExclusive();
-                mState = TRANS_STARTED;
-            }
-        }
-        // do I have database lock? if not, grab it.
-        if (!mDatabase.isDbLockedByCurrentThread()) {
-            mDatabase.lock(mSql);
-            mState = LOCK_ACQUIRED;
-        }
-
         acquireReference();
-        long startTime = SystemClock.uptimeMillis();
-        mDatabase.closePendingStatements();
-        compileAndbindAllArgs();
-        return startTime;
+        try {
+            return getSession().executeForBlobFileDescriptor(
+                    getSql(), getBindArgs(), getConnectionFlags());
+        } catch (SQLiteDatabaseCorruptException ex) {
+            onCorruption();
+            throw ex;
+        } finally {
+            releaseReference();
+        }
     }
 
-    /**
-     * this method releases locks and references acquired in {@link #acquireAndLock(boolean)}
-     */
-    private void releaseAndUnlock() {
-        releaseReference();
-        if (mState == TRANS_STARTED) {
-            try {
-                mDatabase.setTransactionSuccessful();
-            } finally {
-                mDatabase.endTransaction();
-            }
-        } else if (mState == LOCK_ACQUIRED) {
-            mDatabase.unlock();
-        }
-        if ((mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
-                DatabaseUtils.STATEMENT_COMMIT ||
-                (mStatementType & SQLiteProgram.STATEMENT_TYPE_MASK) ==
-                DatabaseUtils.STATEMENT_ABORT) {
-            mDatabase.resetTransactionUsingExecSqlFlag();
-        }
-        clearBindings();
-        // release the compiled sql statement so that the caller's SQLiteStatement no longer
-        // has a hard reference to a database object that may get deallocated at any point.
-        release();
-        // restore the database connection handle to the original value
-        mDatabase = mOrigDb;
-        setNativeHandle(mDatabase.mNativeHandle);
+    @Override
+    public String toString() {
+        return "SQLiteProgram: " + getSql();
     }
-
-    private final native int native_execute();
-    private final native long native_executeInsert();
-    private final native long native_1x1_long();
-    private final native String native_1x1_string();
-    private final native ParcelFileDescriptor native_1x1_blob_ashmem() throws IOException;
-    private final native void native_executeSql(String sql);
 }
diff --git a/core/java/android/database/sqlite/SQLiteStatementInfo.java b/core/java/android/database/sqlite/SQLiteStatementInfo.java
new file mode 100644
index 0000000..3edfdb0
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteStatementInfo.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.database.sqlite;
+
+/**
+ * Describes a SQLite statement.
+ *
+ * @hide
+ */
+public final class SQLiteStatementInfo {
+    /**
+     * The number of parameters that the statement has.
+     */
+    public int numParameters;
+
+    /**
+     * The names of all columns in the result set of the statement.
+     */
+    public String[] columnNames;
+
+    /**
+     * True if the statement is read-only.
+     */
+    public boolean readOnly;
+}
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index f1014a7..51e373c 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -86,6 +86,23 @@
     }
 
     /**
+     * Sets the size of the cache.
+     * @param maxSize The new maximum size.
+     *
+     * @hide
+     */
+    public void resize(int maxSize) {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+
+        synchronized (this) {
+            this.maxSize = maxSize;
+        }
+        trimToSize(maxSize);
+    }
+
+    /**
      * Returns the value for {@code key} if it exists in the cache or can be
      * created by {@code #create}. If a value was returned, it is moved to the
      * head of the queue. This returns null if a value is not cached and cannot
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 8be1996..39b84bb 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -39,12 +39,10 @@
 	android_opengl_GLES11Ext.cpp \
 	android_opengl_GLES20.cpp \
 	android_database_CursorWindow.cpp \
-	android_database_SQLiteCompiledSql.cpp \
+	android_database_SQLiteCommon.cpp \
+	android_database_SQLiteConnection.cpp \
+	android_database_SQLiteGlobal.cpp \
 	android_database_SQLiteDebug.cpp \
-	android_database_SQLiteDatabase.cpp \
-	android_database_SQLiteProgram.cpp \
-	android_database_SQLiteQuery.cpp \
-	android_database_SQLiteStatement.cpp \
 	android_emoji_EmojiFactory.cpp \
 	android_view_Display.cpp \
 	android_view_DisplayEventReceiver.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index c006615..8a3063f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -121,12 +121,9 @@
 extern int register_android_view_Surface(JNIEnv* env);
 extern int register_android_view_TextureView(JNIEnv* env);
 extern int register_android_database_CursorWindow(JNIEnv* env);
-extern int register_android_database_SQLiteCompiledSql(JNIEnv* env);
-extern int register_android_database_SQLiteDatabase(JNIEnv* env);
+extern int register_android_database_SQLiteConnection(JNIEnv* env);
+extern int register_android_database_SQLiteGlobal(JNIEnv* env);
 extern int register_android_database_SQLiteDebug(JNIEnv* env);
-extern int register_android_database_SQLiteProgram(JNIEnv* env);
-extern int register_android_database_SQLiteQuery(JNIEnv* env);
-extern int register_android_database_SQLiteStatement(JNIEnv* env);
 extern int register_android_debug_JNITest(JNIEnv* env);
 extern int register_android_nio_utils(JNIEnv* env);
 extern int register_android_text_format_Time(JNIEnv* env);
@@ -1141,12 +1138,9 @@
     REG_JNI(register_android_graphics_YuvImage),
 
     REG_JNI(register_android_database_CursorWindow),
-    REG_JNI(register_android_database_SQLiteCompiledSql),
-    REG_JNI(register_android_database_SQLiteDatabase),
+    REG_JNI(register_android_database_SQLiteConnection),
+    REG_JNI(register_android_database_SQLiteGlobal),
     REG_JNI(register_android_database_SQLiteDebug),
-    REG_JNI(register_android_database_SQLiteProgram),
-    REG_JNI(register_android_database_SQLiteQuery),
-    REG_JNI(register_android_database_SQLiteStatement),
     REG_JNI(register_android_os_Debug),
     REG_JNI(register_android_os_FileObserver),
     REG_JNI(register_android_os_FileUtils),
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 659fa35..d53644d 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -31,8 +31,8 @@
 #include <unistd.h>
 
 #include "binder/CursorWindow.h"
-#include "sqlite3_exception.h"
 #include "android_util_Binder.h"
+#include "android_database_SQLiteCommon.h"
 
 namespace android {
 
diff --git a/core/jni/android_database_SQLiteCommon.cpp b/core/jni/android_database_SQLiteCommon.cpp
new file mode 100644
index 0000000..d5fdb15
--- /dev/null
+++ b/core/jni/android_database_SQLiteCommon.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android_database_SQLiteCommon.h"
+
+namespace android {
+
+/* throw a SQLiteException with a message appropriate for the error in handle */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) {
+    throw_sqlite3_exception(env, handle, NULL);
+}
+
+/* throw a SQLiteException with the given message */
+void throw_sqlite3_exception(JNIEnv* env, const char* message) {
+    throw_sqlite3_exception(env, NULL, message);
+}
+
+/* throw a SQLiteException with a message appropriate for the error in handle
+   concatenated with the given message
+ */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) {
+    if (handle) {
+        throw_sqlite3_exception(env, sqlite3_errcode(handle),
+                                sqlite3_errmsg(handle), message);
+    } else {
+        // we use SQLITE_OK so that a generic SQLiteException is thrown;
+        // any code not specified in the switch statement below would do.
+        throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message);
+    }
+}
+
+/* throw a SQLiteException for a given error code */
+void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) {
+    if (errcode == SQLITE_DONE) {
+        throw_sqlite3_exception(env, errcode, NULL, message);
+    } else {
+        char temp[21];
+        sprintf(temp, "error code %d", errcode);
+        throw_sqlite3_exception(env, errcode, temp, message);
+    }
+}
+
+/* throw a SQLiteException for a given error code, sqlite3message, and
+   user message
+ */
+void throw_sqlite3_exception(JNIEnv* env, int errcode,
+                             const char* sqlite3Message, const char* message) {
+    const char* exceptionClass;
+    switch (errcode) {
+        case SQLITE_IOERR:
+            exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
+            break;
+        case SQLITE_CORRUPT:
+        case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also
+            exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
+            break;
+        case SQLITE_CONSTRAINT:
+           exceptionClass = "android/database/sqlite/SQLiteConstraintException";
+           break;
+        case SQLITE_ABORT:
+           exceptionClass = "android/database/sqlite/SQLiteAbortException";
+           break;
+        case SQLITE_DONE:
+           exceptionClass = "android/database/sqlite/SQLiteDoneException";
+           break;
+        case SQLITE_FULL:
+           exceptionClass = "android/database/sqlite/SQLiteFullException";
+           break;
+        case SQLITE_MISUSE:
+           exceptionClass = "android/database/sqlite/SQLiteMisuseException";
+           break;
+        case SQLITE_PERM:
+           exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
+           break;
+        case SQLITE_BUSY:
+           exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
+           break;
+        case SQLITE_LOCKED:
+           exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
+           break;
+        case SQLITE_READONLY:
+           exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
+           break;
+        case SQLITE_CANTOPEN:
+           exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
+           break;
+        case SQLITE_TOOBIG:
+           exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
+           break;
+        case SQLITE_RANGE:
+           exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
+           break;
+        case SQLITE_NOMEM:
+           exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
+           break;
+        case SQLITE_MISMATCH:
+           exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
+           break;
+        case SQLITE_UNCLOSED:
+           exceptionClass = "android/database/sqlite/SQLiteUnfinalizedObjectsException";
+           break;
+        default:
+           exceptionClass = "android/database/sqlite/SQLiteException";
+           break;
+    }
+
+    if (sqlite3Message != NULL && message != NULL) {
+        char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3);
+        if (fullMessage != NULL) {
+            strcpy(fullMessage, sqlite3Message);
+            strcat(fullMessage, ": ");
+            strcat(fullMessage, message);
+            jniThrowException(env, exceptionClass, fullMessage);
+            free(fullMessage);
+        } else {
+            jniThrowException(env, exceptionClass, sqlite3Message);
+        }
+    } else if (sqlite3Message != NULL) {
+        jniThrowException(env, exceptionClass, sqlite3Message);
+    } else {
+        jniThrowException(env, exceptionClass, message);
+    }
+}
+
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteCommon.h b/core/jni/android_database_SQLiteCommon.h
new file mode 100644
index 0000000..0cac176
--- /dev/null
+++ b/core/jni/android_database_SQLiteCommon.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_DATABASE_SQLITE_COMMON_H
+#define _ANDROID_DATABASE_SQLITE_COMMON_H
+
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include <sqlite3.h>
+
+// Special log tags defined in SQLiteDebug.java.
+#define SQLITE_LOG_TAG "SQLiteLog"
+#define SQLITE_TRACE_TAG "SQLiteStatements"
+#define SQLITE_PROFILE_TAG "SQLiteTime"
+
+namespace android {
+
+/* throw a SQLiteException with a message appropriate for the error in handle */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle);
+
+/* throw a SQLiteException with the given message */
+void throw_sqlite3_exception(JNIEnv* env, const char* message);
+
+/* throw a SQLiteException with a message appropriate for the error in handle
+   concatenated with the given message
+ */
+void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message);
+
+/* throw a SQLiteException for a given error code */
+void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message);
+
+void throw_sqlite3_exception(JNIEnv* env, int errcode,
+        const char* sqlite3Message, const char* message);
+
+}
+
+#endif // _ANDROID_DATABASE_SQLITE_COMMON_H
diff --git a/core/jni/android_database_SQLiteCompiledSql.cpp b/core/jni/android_database_SQLiteCompiledSql.cpp
deleted file mode 100644
index 857267a..0000000
--- a/core/jni/android_database_SQLiteCompiledSql.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2006-2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "Cursor"
-
-#include <jni.h>
-#include <JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-
-#include <sqlite3.h>
-
-#include <utils/Log.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "sqlite3_exception.h"
-
-
-namespace android {
-
-static jfieldID gHandleField;
-static jfieldID gStatementField;
-
-
-#define GET_STATEMENT(env, object) \
-        (sqlite3_stmt *)env->GetIntField(object, gStatementField)
-#define GET_HANDLE(env, object) \
-        (sqlite3 *)env->GetIntField(object, gHandleField)
-
-
-sqlite3_stmt * compile(JNIEnv* env, jobject object,
-                       sqlite3 * handle, jstring sqlString)
-{
-    int err;
-    jchar const * sql;
-    jsize sqlLen;
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
-    // Make sure not to leak the statement if it already exists
-    if (statement != NULL) {
-        sqlite3_finalize(statement);
-        env->SetIntField(object, gStatementField, 0);
-    }
-
-    // Compile the SQL
-    sql = env->GetStringChars(sqlString, NULL);
-    sqlLen = env->GetStringLength(sqlString);
-    err = sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL);
-    env->ReleaseStringChars(sqlString, sql);
-
-    if (err == SQLITE_OK) {
-        // Store the statement in the Java object for future calls
-        ALOGV("Prepared statement %p on %p", statement, handle);
-        env->SetIntField(object, gStatementField, (int)statement);
-        return statement;
-    } else {
-        // Error messages like 'near ")": syntax error' are not
-        // always helpful enough, so construct an error string that
-        // includes the query itself.
-        const char *query = env->GetStringUTFChars(sqlString, NULL);
-        char *message = (char*) malloc(strlen(query) + 50);
-        if (message) {
-            strcpy(message, ", while compiling: "); // less than 50 chars
-            strcat(message, query);
-        }
-        env->ReleaseStringUTFChars(sqlString, query);
-        throw_sqlite3_exception(env, handle, message);
-        free(message);
-        return NULL;
-    }
-}
-
-static void native_compile(JNIEnv* env, jobject object, jstring sqlString)
-{
-    compile(env, object, GET_HANDLE(env, object), sqlString);
-}
-
-
-static JNINativeMethod sMethods[] =
-{
-     /* name, signature, funcPtr */
-    {"native_compile", "(Ljava/lang/String;)V", (void *)native_compile},
-};
-
-int register_android_database_SQLiteCompiledSql(JNIEnv * env)
-{
-    jclass clazz;
-
-    clazz = env->FindClass("android/database/sqlite/SQLiteCompiledSql");
-    if (clazz == NULL) {
-        ALOGE("Can't find android/database/sqlite/SQLiteCompiledSql");
-        return -1;
-    }
-
-    gHandleField = env->GetFieldID(clazz, "nHandle", "I");
-    gStatementField = env->GetFieldID(clazz, "nStatement", "I");
-
-    if (gHandleField == NULL || gStatementField == NULL) {
-        ALOGE("Error locating fields");
-        return -1;
-    }
-
-    return AndroidRuntime::registerNativeMethods(env,
-        "android/database/sqlite/SQLiteCompiledSql", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp
new file mode 100644
index 0000000..d0d53f6
--- /dev/null
+++ b/core/jni/android_database_SQLiteConnection.cpp
@@ -0,0 +1,959 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SQLiteConnection"
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
+
+#include <string.h>
+#include <unistd.h>
+
+#include "binder/CursorWindow.h"
+
+#include <sqlite3.h>
+#include <sqlite3_android.h>
+
+#include "android_database_SQLiteCommon.h"
+
+#define UTF16_STORAGE 0
+#define ANDROID_TABLE "android_metadata"
+
+namespace android {
+
+static struct {
+    jfieldID name;
+    jfieldID numArgs;
+    jmethodID dispatchCallback;
+} gSQLiteCustomFunctionClassInfo;
+
+static struct {
+    jclass clazz;
+} gStringClassInfo;
+
+struct SQLiteConnection {
+    // Open flags.
+    // Must be kept in sync with the constants defined in SQLiteDatabase.java.
+    enum {
+        OPEN_READWRITE          = 0x00000000,
+        OPEN_READONLY           = 0x00000001,
+        OPEN_READ_MASK          = 0x00000001,
+        NO_LOCALIZED_COLLATORS  = 0x00000010,
+        CREATE_IF_NECESSARY     = 0x10000000,
+    };
+
+    sqlite3* const db;
+    const int openFlags;
+    const String8 path;
+    const String8 label;
+
+    SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) :
+        db(db), openFlags(openFlags), path(path), label(label) { }
+};
+
+// Called each time a statement begins execution, when tracing is enabled.
+static void sqliteTraceCallback(void *data, const char *sql) {
+    SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
+    ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n",
+            connection->label.string(), sql);
+}
+
+// Called each time a statement finishes execution, when profiling is enabled.
+static void sqliteProfileCallback(void *data, const char *sql, sqlite3_uint64 tm) {
+    SQLiteConnection* connection = static_cast<SQLiteConnection*>(data);
+    ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n",
+            connection->label.string(), sql, tm * 0.000001f);
+}
+
+
+static jint nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags,
+        jstring labelStr, jboolean enableTrace, jboolean enableProfile) {
+    int sqliteFlags;
+    if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) {
+        sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
+    } else if (openFlags & SQLiteConnection::OPEN_READONLY) {
+        sqliteFlags = SQLITE_OPEN_READONLY;
+    } else {
+        sqliteFlags = SQLITE_OPEN_READWRITE;
+    }
+
+    const char* pathChars = env->GetStringUTFChars(pathStr, NULL);
+    String8 path(pathChars);
+    env->ReleaseStringUTFChars(pathStr, pathChars);
+
+    const char* labelChars = env->GetStringUTFChars(labelStr, NULL);
+    String8 label(labelChars);
+    env->ReleaseStringUTFChars(labelStr, labelChars);
+
+    sqlite3* db;
+    int err = sqlite3_open_v2(path.string(), &db, sqliteFlags, NULL);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception_errcode(env, err, "Could not open database");
+        return 0;
+    }
+
+    // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY
+    err = sqlite3_busy_timeout(db, 1000 /* ms */);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, db, "Could not set busy timeout");
+        sqlite3_close(db);
+        return 0;
+    }
+
+    // Enable WAL auto-checkpointing after a commit whenever at least one frame is in the log.
+    // This ensures that a checkpoint will occur after each transaction if needed.
+    err = sqlite3_wal_autocheckpoint(db, 1);
+    if (err) {
+        throw_sqlite3_exception(env, db, "Could not enable auto-checkpointing.");
+        sqlite3_close(db);
+        return 0;
+    }
+
+    // Register custom Android functions.
+    err = register_android_functions(db, UTF16_STORAGE);
+    if (err) {
+        throw_sqlite3_exception(env, db, "Could not register Android SQL functions.");
+        sqlite3_close(db);
+        return 0;
+    }
+
+    // Create wrapper object.
+    SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label);
+
+    // Enable tracing and profiling if requested.
+    if (enableTrace) {
+        sqlite3_trace(db, &sqliteTraceCallback, connection);
+    }
+    if (enableProfile) {
+        sqlite3_profile(db, &sqliteProfileCallback, connection);
+    }
+
+    ALOGV("Opened connection %p with label '%s'", db, label.string());
+    return reinterpret_cast<jint>(connection);
+}
+
+static void nativeClose(JNIEnv* env, jclass clazz, jint connectionPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+    if (connection) {
+        ALOGV("Closing connection %p", connection->db);
+        int err = sqlite3_close(connection->db);
+        if (err != SQLITE_OK) {
+            // This can happen if sub-objects aren't closed first.  Make sure the caller knows.
+            ALOGE("sqlite3_close(%p) failed: %d", connection->db, err);
+            throw_sqlite3_exception(env, connection->db, "Count not close db.");
+            return;
+        }
+
+        delete connection;
+    }
+}
+
+// Called each time a custom function is evaluated.
+static void sqliteCustomFunctionCallback(sqlite3_context *context,
+        int argc, sqlite3_value **argv) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+    // Get the callback function object.
+    // Create a new local reference to it in case the callback tries to do something
+    // dumb like unregister the function (thereby destroying the global ref) while it is running.
+    jobject functionObjGlobal = reinterpret_cast<jobject>(sqlite3_user_data(context));
+    jobject functionObj = env->NewLocalRef(functionObjGlobal);
+
+    jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
+    if (argsArray) {
+        for (int i = 0; i < argc; i++) {
+            const jchar* arg = static_cast<const jchar*>(sqlite3_value_text16(argv[i]));
+            if (!arg) {
+                ALOGW("NULL argument in custom_function_callback.  This should not happen.");
+            } else {
+                size_t argLen = sqlite3_value_bytes16(argv[i]) / sizeof(jchar);
+                jstring argStr = env->NewString(arg, argLen);
+                if (!argStr) {
+                    goto error; // out of memory error
+                }
+                env->SetObjectArrayElement(argsArray, i, argStr);
+                env->DeleteLocalRef(argStr);
+            }
+        }
+
+        // TODO: Support functions that return values.
+        env->CallVoidMethod(functionObj,
+                gSQLiteCustomFunctionClassInfo.dispatchCallback, argsArray);
+
+error:
+        env->DeleteLocalRef(argsArray);
+    }
+
+    env->DeleteLocalRef(functionObj);
+
+    if (env->ExceptionCheck()) {
+        ALOGE("An exception was thrown by custom SQLite function.");
+        LOGE_EX(env);
+        env->ExceptionClear();
+    }
+}
+
+// Called when a custom function is destroyed.
+static void sqliteCustomFunctionDestructor(void* data) {
+    jobject functionObjGlobal = reinterpret_cast<jobject>(data);
+
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    env->DeleteGlobalRef(functionObjGlobal);
+}
+
+static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jobject functionObj) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+    jstring nameStr = jstring(env->GetObjectField(
+            functionObj, gSQLiteCustomFunctionClassInfo.name));
+    jint numArgs = env->GetIntField(functionObj, gSQLiteCustomFunctionClassInfo.numArgs);
+
+    jobject functionObjGlobal = env->NewGlobalRef(functionObj);
+
+    const char* name = env->GetStringUTFChars(nameStr, NULL);
+    int err = sqlite3_create_function_v2(connection->db, name, numArgs, SQLITE_UTF16,
+            reinterpret_cast<void*>(functionObjGlobal),
+            &sqliteCustomFunctionCallback, NULL, NULL, &sqliteCustomFunctionDestructor);
+    env->ReleaseStringUTFChars(nameStr, name);
+
+    if (err != SQLITE_OK) {
+        ALOGE("sqlite3_create_function returned %d", err);
+        env->DeleteGlobalRef(functionObjGlobal);
+        throw_sqlite3_exception(env, connection->db);
+        return;
+    }
+}
+
+// Set locale in the android_metadata table, install localized collators, and rebuild indexes
+static void nativeSetLocale(JNIEnv* env, jclass clazz, jint connectionPtr, jstring localeStr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+    if (connection->openFlags & SQLiteConnection::NO_LOCALIZED_COLLATORS) {
+        // We should probably throw IllegalStateException but the contract for
+        // setLocale says that we just do nothing.  Oh well.
+        return;
+    }
+
+    int err;
+    char const* locale = env->GetStringUTFChars(localeStr, NULL);
+    sqlite3_stmt* stmt = NULL;
+    char** meta = NULL;
+    int rowCount, colCount;
+    char* dbLocale = NULL;
+
+    // create the table, if necessary and possible
+    if (!(connection->openFlags & SQLiteConnection::OPEN_READONLY)) {
+        err = sqlite3_exec(connection->db,
+                "CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)",
+                NULL, NULL, NULL);
+        if (err != SQLITE_OK) {
+            ALOGE("CREATE TABLE " ANDROID_TABLE " failed");
+            throw_sqlite3_exception(env, connection->db);
+            goto done;
+        }
+    }
+
+    // try to read from the table
+    err = sqlite3_get_table(connection->db,
+            "SELECT locale FROM " ANDROID_TABLE " LIMIT 1",
+            &meta, &rowCount, &colCount, NULL);
+    if (err != SQLITE_OK) {
+        ALOGE("SELECT locale FROM " ANDROID_TABLE " failed");
+        throw_sqlite3_exception(env, connection->db);
+        goto done;
+    }
+
+    dbLocale = (rowCount >= 1) ? meta[colCount] : NULL;
+
+    if (dbLocale != NULL && !strcmp(dbLocale, locale)) {
+        // database locale is the same as the desired locale; set up the collators and go
+        err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
+        if (err != SQLITE_OK) {
+            throw_sqlite3_exception(env, connection->db);
+        }
+        goto done;   // no database changes needed
+    }
+
+    if (connection->openFlags & SQLiteConnection::OPEN_READONLY) {
+        // read-only database, so we're going to have to put up with whatever we got
+        // For registering new index. Not for modifing the read-only database.
+        err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
+        if (err != SQLITE_OK) {
+            throw_sqlite3_exception(env, connection->db);
+        }
+        goto done;
+    }
+
+    // need to update android_metadata and indexes atomically, so use a transaction...
+    err = sqlite3_exec(connection->db, "BEGIN TRANSACTION", NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        ALOGE("BEGIN TRANSACTION failed setting locale");
+        throw_sqlite3_exception(env, connection->db);
+        goto done;
+    }
+
+    err = register_localized_collators(connection->db, locale, UTF16_STORAGE);
+    if (err != SQLITE_OK) {
+        ALOGE("register_localized_collators() failed setting locale");
+        throw_sqlite3_exception(env, connection->db);
+        goto rollback;
+    }
+
+    err = sqlite3_exec(connection->db, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        ALOGE("DELETE failed setting locale");
+        throw_sqlite3_exception(env, connection->db);
+        goto rollback;
+    }
+
+    static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);";
+    err = sqlite3_prepare_v2(connection->db, sql, -1, &stmt, NULL);
+    if (err != SQLITE_OK) {
+        ALOGE("sqlite3_prepare_v2(\"%s\") failed", sql);
+        throw_sqlite3_exception(env, connection->db);
+        goto rollback;
+    }
+
+    err = sqlite3_bind_text(stmt, 1, locale, -1, SQLITE_TRANSIENT);
+    if (err != SQLITE_OK) {
+        ALOGE("sqlite3_bind_text() failed setting locale");
+        throw_sqlite3_exception(env, connection->db);
+        goto rollback;
+    }
+
+    err = sqlite3_step(stmt);
+    if (err != SQLITE_OK && err != SQLITE_DONE) {
+        ALOGE("sqlite3_step(\"%s\") failed setting locale", sql);
+        throw_sqlite3_exception(env, connection->db);
+        goto rollback;
+    }
+
+    err = sqlite3_exec(connection->db, "REINDEX LOCALIZED", NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        ALOGE("REINDEX LOCALIZED failed");
+        throw_sqlite3_exception(env, connection->db);
+        goto rollback;
+    }
+
+    // all done, yay!
+    err = sqlite3_exec(connection->db, "COMMIT TRANSACTION", NULL, NULL, NULL);
+    if (err != SQLITE_OK) {
+        ALOGE("COMMIT TRANSACTION failed setting locale");
+        throw_sqlite3_exception(env, connection->db);
+        goto done;
+    }
+
+rollback:
+    if (err != SQLITE_OK) {
+        sqlite3_exec(connection->db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
+    }
+
+done:
+    if (stmt) {
+        sqlite3_finalize(stmt);
+    }
+    if (meta) {
+        sqlite3_free_table(meta);
+    }
+    if (locale) {
+        env->ReleaseStringUTFChars(localeStr, locale);
+    }
+}
+
+static jint nativePrepareStatement(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jstring sqlString) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+    jsize sqlLength = env->GetStringLength(sqlString);
+    const jchar* sql = env->GetStringCritical(sqlString, NULL);
+    sqlite3_stmt* statement;
+    int err = sqlite3_prepare16_v2(connection->db,
+            sql, sqlLength * sizeof(jchar), &statement, NULL);
+    env->ReleaseStringCritical(sqlString, sql);
+
+    if (err != SQLITE_OK) {
+        // Error messages like 'near ")": syntax error' are not
+        // always helpful enough, so construct an error string that
+        // includes the query itself.
+        const char *query = env->GetStringUTFChars(sqlString, NULL);
+        char *message = (char*) malloc(strlen(query) + 50);
+        if (message) {
+            strcpy(message, ", while compiling: "); // less than 50 chars
+            strcat(message, query);
+        }
+        env->ReleaseStringUTFChars(sqlString, query);
+        throw_sqlite3_exception(env, connection->db, message);
+        free(message);
+        return 0;
+    }
+
+    ALOGV("Prepared statement %p on connection %p", statement, connection->db);
+    return reinterpret_cast<jint>(statement);
+}
+
+static void nativeFinalizeStatement(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    ALOGV("Finalized statement %p on connection %p", statement, connection->db);
+    int err = sqlite3_finalize(statement);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, connection->db, NULL);
+    }
+}
+
+static jint nativeGetParameterCount(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    return sqlite3_bind_parameter_count(statement);
+}
+
+static jboolean nativeIsReadOnly(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    return sqlite3_stmt_readonly(statement) != 0;
+}
+
+static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    return sqlite3_column_count(statement);
+}
+
+static jstring nativeGetColumnName(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr, jint index) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    const jchar* name = static_cast<const jchar*>(sqlite3_column_name16(statement, index));
+    if (name) {
+        size_t length = 0;
+        while (name[length]) {
+            length += 1;
+        }
+        return env->NewString(name, length);
+    }
+    return NULL;
+}
+
+static void nativeBindNull(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr, jint index) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = sqlite3_bind_null(statement, index);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, connection->db, NULL);
+    }
+}
+
+static void nativeBindLong(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr, jint index, jlong value) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = sqlite3_bind_int64(statement, index, value);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, connection->db, NULL);
+    }
+}
+
+static void nativeBindDouble(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr, jint index, jdouble value) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = sqlite3_bind_double(statement, index, value);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, connection->db, NULL);
+    }
+}
+
+static void nativeBindString(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr, jint index, jstring valueString) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    jsize valueLength = env->GetStringLength(valueString);
+    const jchar* value = env->GetStringCritical(valueString, NULL);
+    int err = sqlite3_bind_text16(statement, index, value, valueLength * sizeof(jchar),
+            SQLITE_TRANSIENT);
+    env->ReleaseStringCritical(valueString, value);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, connection->db, NULL);
+    }
+}
+
+static void nativeBindBlob(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr, jint index, jbyteArray valueArray) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    jsize valueLength = env->GetArrayLength(valueArray);
+    jbyte* value = static_cast<jbyte*>(env->GetPrimitiveArrayCritical(valueArray, NULL));
+    int err = sqlite3_bind_blob(statement, index, value, valueLength, SQLITE_TRANSIENT);
+    env->ReleasePrimitiveArrayCritical(valueArray, value, JNI_ABORT);
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, connection->db, NULL);
+    }
+}
+
+static void nativeResetStatementAndClearBindings(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = sqlite3_reset(statement);
+    if (err == SQLITE_OK) {
+        err = sqlite3_clear_bindings(statement);
+    }
+    if (err != SQLITE_OK) {
+        throw_sqlite3_exception(env, connection->db, NULL);
+    }
+}
+
+static int executeNonQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) {
+    int err = sqlite3_step(statement);
+    if (err == SQLITE_ROW) {
+        throw_sqlite3_exception(env,
+                "Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
+    } else if (err != SQLITE_DONE) {
+        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(connection->db));
+    }
+    return err;
+}
+
+static void nativeExecute(JNIEnv* env, jclass clazz, jint connectionPtr,
+        jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    executeNonQuery(env, connection, statement);
+}
+
+static jint nativeExecuteForChangedRowCount(JNIEnv* env, jclass clazz,
+        jint connectionPtr, jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = executeNonQuery(env, connection, statement);
+    return err == SQLITE_DONE ? sqlite3_changes(connection->db) : -1;
+}
+
+static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz,
+        jint connectionPtr, jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = executeNonQuery(env, connection, statement);
+    return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
+            ? sqlite3_last_insert_rowid(connection->db) : -1;
+}
+
+static int executeOneRowQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_stmt* statement) {
+    int err = sqlite3_step(statement);
+    if (err != SQLITE_ROW) {
+        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(connection->db));
+    }
+    return err;
+}
+
+static jlong nativeExecuteForLong(JNIEnv* env, jclass clazz,
+        jint connectionPtr, jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = executeOneRowQuery(env, connection, statement);
+    if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
+        return sqlite3_column_int64(statement, 0);
+    }
+    return -1;
+}
+
+static jstring nativeExecuteForString(JNIEnv* env, jclass clazz,
+        jint connectionPtr, jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = executeOneRowQuery(env, connection, statement);
+    if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
+        const jchar* text = static_cast<const jchar*>(sqlite3_column_text16(statement, 0));
+        if (text) {
+            size_t length = sqlite3_column_bytes16(statement, 0) / sizeof(jchar);
+            return env->NewString(text, length);
+        }
+    }
+    return NULL;
+}
+
+static int createAshmemRegionWithData(JNIEnv* env, const void* data, size_t length) {
+    int error = 0;
+    int fd = ashmem_create_region(NULL, length);
+    if (fd < 0) {
+        error = errno;
+        ALOGE("ashmem_create_region failed: %s", strerror(error));
+    } else {
+        if (length > 0) {
+            void* ptr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+            if (ptr == MAP_FAILED) {
+                error = errno;
+                ALOGE("mmap failed: %s", strerror(error));
+            } else {
+                memcpy(ptr, data, length);
+                munmap(ptr, length);
+            }
+        }
+
+        if (!error) {
+            if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
+                error = errno;
+                ALOGE("ashmem_set_prot_region failed: %s", strerror(errno));
+            } else {
+                return fd;
+            }
+        }
+
+        close(fd);
+    }
+
+    jniThrowIOException(env, error);
+    return -1;
+}
+
+static jint nativeExecuteForBlobFileDescriptor(JNIEnv* env, jclass clazz,
+        jint connectionPtr, jint statementPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+
+    int err = executeOneRowQuery(env, connection, statement);
+    if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
+        const void* blob = sqlite3_column_blob(statement, 0);
+        if (blob) {
+            int length = sqlite3_column_bytes(statement, 0);
+            if (length >= 0) {
+                return createAshmemRegionWithData(env, blob, length);
+            }
+        }
+    }
+    return -1;
+}
+
+enum CopyRowResult {
+    CPR_OK,
+    CPR_FULL,
+    CPR_ERROR,
+};
+
+static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
+        sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
+    // Allocate a new field directory for the row.
+    status_t status = window->allocRow();
+    if (status) {
+        LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
+                startPos, addedRows, status);
+        return CPR_FULL;
+    }
+
+    // Pack the row into the window.
+    CopyRowResult result = CPR_OK;
+    for (int i = 0; i < numColumns; i++) {
+        int type = sqlite3_column_type(statement, i);
+        if (type == SQLITE_TEXT) {
+            // TEXT data
+            const char* text = reinterpret_cast<const char*>(
+                    sqlite3_column_text(statement, i));
+            // SQLite does not include the NULL terminator in size, but does
+            // ensure all strings are NULL terminated, so increase size by
+            // one to make sure we store the terminator.
+            size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
+            status = window->putString(addedRows, i, text, sizeIncludingNull);
+            if (status) {
+                LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
+                        sizeIncludingNull, startPos + addedRows, i, status);
+                result = CPR_FULL;
+                break;
+            }
+            LOG_WINDOW("%d,%d is TEXT with %u bytes",
+                    startPos + addedRows, i, sizeIncludingNull);
+        } else if (type == SQLITE_INTEGER) {
+            // INTEGER data
+            int64_t value = sqlite3_column_int64(statement, i);
+            status = window->putLong(addedRows, i, value);
+            if (status) {
+                LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
+                        i, status);
+                result = CPR_FULL;
+                break;
+            }
+            LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
+        } else if (type == SQLITE_FLOAT) {
+            // FLOAT data
+            double value = sqlite3_column_double(statement, i);
+            status = window->putDouble(addedRows, i, value);
+            if (status) {
+                LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
+                        i, status);
+                result = CPR_FULL;
+                break;
+            }
+            LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
+        } else if (type == SQLITE_BLOB) {
+            // BLOB data
+            const void* blob = sqlite3_column_blob(statement, i);
+            size_t size = sqlite3_column_bytes(statement, i);
+            status = window->putBlob(addedRows, i, blob, size);
+            if (status) {
+                LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
+                        size, startPos + addedRows, i, status);
+                result = CPR_FULL;
+                break;
+            }
+            LOG_WINDOW("%d,%d is Blob with %u bytes",
+                    startPos + addedRows, i, size);
+        } else if (type == SQLITE_NULL) {
+            // NULL field
+            status = window->putNull(addedRows, i);
+            if (status) {
+                LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
+                        i, status);
+                result = CPR_FULL;
+                break;
+            }
+
+            LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
+        } else {
+            // Unknown data
+            ALOGE("Unknown column type when filling database window");
+            throw_sqlite3_exception(env, "Unknown column type when filling window");
+            result = CPR_ERROR;
+            break;
+        }
+    }
+
+    // Free the last row if if was not successfully copied.
+    if (result != CPR_OK) {
+        window->freeLastRow();
+    }
+    return result;
+}
+
+static jlong nativeExecuteForCursorWindow(JNIEnv* env, jclass clazz,
+        jint connectionPtr, jint statementPtr, jint windowPtr,
+        jint startPos, jint requiredPos, jboolean countAllRows) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
+    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
+
+    status_t status = window->clear();
+    if (status) {
+        String8 msg;
+        msg.appendFormat("Failed to clear the cursor window, status=%d", status);
+        throw_sqlite3_exception(env, connection->db, msg.string());
+        return 0;
+    }
+
+    int numColumns = sqlite3_column_count(statement);
+    status = window->setNumColumns(numColumns);
+    if (status) {
+        String8 msg;
+        msg.appendFormat("Failed to set the cursor window column count to %d, status=%d",
+                numColumns, status);
+        throw_sqlite3_exception(env, connection->db, msg.string());
+        return 0;
+    }
+
+    int retryCount = 0;
+    int totalRows = 0;
+    int addedRows = 0;
+    bool windowFull = false;
+    bool gotException = false;
+    while (!gotException && (!windowFull || countAllRows)) {
+        int err = sqlite3_step(statement);
+        if (err == SQLITE_ROW) {
+            LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
+            retryCount = 0;
+            totalRows += 1;
+
+            // Skip the row if the window is full or we haven't reached the start position yet.
+            if (startPos >= totalRows || windowFull) {
+                continue;
+            }
+
+            CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+            if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {
+                // We filled the window before we got to the one row that we really wanted.
+                // Clear the window and start filling it again from here.
+                // TODO: Would be nicer if we could progressively replace earlier rows.
+                window->clear();
+                window->setNumColumns(numColumns);
+                startPos += addedRows;
+                addedRows = 0;
+                cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+            }
+
+            if (cpr == CPR_OK) {
+                addedRows += 1;
+            } else if (cpr == CPR_FULL) {
+                windowFull = true;
+            } else {
+                gotException = true;
+            }
+        } else if (err == SQLITE_DONE) {
+            // All rows processed, bail
+            LOG_WINDOW("Processed all rows");
+            break;
+        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
+            // The table is locked, retry
+            LOG_WINDOW("Database locked, retrying");
+            if (retryCount > 50) {
+                ALOGE("Bailing on database busy retry");
+                throw_sqlite3_exception(env, connection->db, "retrycount exceeded");
+                gotException = true;
+            } else {
+                // Sleep to give the thread holding the lock a chance to finish
+                usleep(1000);
+                retryCount++;
+            }
+        } else {
+            throw_sqlite3_exception(env, connection->db);
+            gotException = true;
+        }
+    }
+
+    LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
+            "to the window in %d bytes",
+            statement, totalRows, addedRows, window->size() - window->freeSpace());
+    sqlite3_reset(statement);
+
+    // Report the total number of rows on request.
+    if (startPos > totalRows) {
+        ALOGE("startPos %d > actual rows %d", startPos, totalRows);
+    }
+    jlong result = jlong(startPos) << 32 | jlong(totalRows);
+    return result;
+}
+
+static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jint connectionPtr) {
+    SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr);
+
+    int cur = -1;
+    int unused;
+    sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &unused, 0);
+    return cur;
+}
+
+
+static JNINativeMethod sMethods[] =
+{
+    /* name, signature, funcPtr */
+    { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZ)I",
+            (void*)nativeOpen },
+    { "nativeClose", "(I)V",
+            (void*)nativeClose },
+    { "nativeRegisterCustomFunction", "(ILandroid/database/sqlite/SQLiteCustomFunction;)V",
+            (void*)nativeRegisterCustomFunction },
+    { "nativeSetLocale", "(ILjava/lang/String;)V",
+            (void*)nativeSetLocale },
+    { "nativePrepareStatement", "(ILjava/lang/String;)I",
+            (void*)nativePrepareStatement },
+    { "nativeFinalizeStatement", "(II)V",
+            (void*)nativeFinalizeStatement },
+    { "nativeGetParameterCount", "(II)I",
+            (void*)nativeGetParameterCount },
+    { "nativeIsReadOnly", "(II)Z",
+            (void*)nativeIsReadOnly },
+    { "nativeGetColumnCount", "(II)I",
+            (void*)nativeGetColumnCount },
+    { "nativeGetColumnName", "(III)Ljava/lang/String;",
+            (void*)nativeGetColumnName },
+    { "nativeBindNull", "(III)V",
+            (void*)nativeBindNull },
+    { "nativeBindLong", "(IIIJ)V",
+            (void*)nativeBindLong },
+    { "nativeBindDouble", "(IIID)V",
+            (void*)nativeBindDouble },
+    { "nativeBindString", "(IIILjava/lang/String;)V",
+            (void*)nativeBindString },
+    { "nativeBindBlob", "(III[B)V",
+            (void*)nativeBindBlob },
+    { "nativeResetStatementAndClearBindings", "(II)V",
+            (void*)nativeResetStatementAndClearBindings },
+    { "nativeExecute", "(II)V",
+            (void*)nativeExecute },
+    { "nativeExecuteForLong", "(II)J",
+            (void*)nativeExecuteForLong },
+    { "nativeExecuteForString", "(II)Ljava/lang/String;",
+            (void*)nativeExecuteForString },
+    { "nativeExecuteForBlobFileDescriptor", "(II)I",
+            (void*)nativeExecuteForBlobFileDescriptor },
+    { "nativeExecuteForChangedRowCount", "(II)I",
+            (void*)nativeExecuteForChangedRowCount },
+    { "nativeExecuteForLastInsertedRowId", "(II)J",
+            (void*)nativeExecuteForLastInsertedRowId },
+    { "nativeExecuteForCursorWindow", "(IIIIIZ)J",
+            (void*)nativeExecuteForCursorWindow },
+    { "nativeGetDbLookaside", "(I)I",
+            (void*)nativeGetDbLookaside },
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+        var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method" methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_database_SQLiteConnection(JNIEnv *env)
+{
+    jclass clazz;
+    FIND_CLASS(clazz, "android/database/sqlite/SQLiteCustomFunction");
+
+    GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.name, clazz,
+            "name", "Ljava/lang/String;");
+    GET_FIELD_ID(gSQLiteCustomFunctionClassInfo.numArgs, clazz,
+            "numArgs", "I");
+    GET_METHOD_ID(gSQLiteCustomFunctionClassInfo.dispatchCallback,
+            clazz, "dispatchCallback", "([Ljava/lang/String;)V");
+
+    FIND_CLASS(clazz, "java/lang/String");
+    gStringClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
+
+    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteConnection",
+            sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteDatabase.cpp b/core/jni/android_database_SQLiteDatabase.cpp
deleted file mode 100644
index 28c421d..0000000
--- a/core/jni/android_database_SQLiteDatabase.cpp
+++ /dev/null
@@ -1,636 +0,0 @@
-/*
- * Copyright (C) 2006-2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SqliteDatabaseCpp"
-
-#include <utils/Log.h>
-#include <utils/String8.h>
-#include <utils/String16.h>
-
-#include <jni.h>
-#include <JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-
-#include <sqlite3.h>
-#include <sqlite3_android.h>
-#include <string.h>
-#include <utils/Log.h>
-#include <utils/threads.h>
-#include <utils/List.h>
-#include <utils/Errors.h>
-#include <ctype.h>
-
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <string.h>
-#include <netdb.h>
-#include <sys/ioctl.h>
-
-#include "sqlite3_exception.h"
-
-#define UTF16_STORAGE 0
-#define INVALID_VERSION -1
-#define ANDROID_TABLE "android_metadata"
-/* uncomment the next line to force-enable logging of all statements */
-// #define DB_LOG_STATEMENTS
-
-#define DEBUG_JNI 0
-
-namespace android {
-
-enum {
-    OPEN_READWRITE          = 0x00000000,
-    OPEN_READONLY           = 0x00000001,
-    OPEN_READ_MASK          = 0x00000001,
-    NO_LOCALIZED_COLLATORS  = 0x00000010,
-    CREATE_IF_NECESSARY     = 0x10000000
-};
-
-static jfieldID offset_db_handle;
-static jmethodID method_custom_function_callback;
-static jclass string_class;
-static jint sSqliteSoftHeapLimit = 0;
-
-static char *createStr(const char *path, short extra) {
-    int len = strlen(path) + extra;
-    char *str = (char *)malloc(len + 1);
-    strncpy(str, path, len);
-    str[len] = NULL;
-    return str;
-}
-
-static void sqlLogger(void *databaseName, int iErrCode, const char *zMsg) {
-    // skip printing this message if it is due to certain types of errors
-    if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT) return;
-    // print databasename, errorcode and msg
-    ALOGI("sqlite returned: error code = %d, msg = %s, db=%s\n", iErrCode, zMsg, databaseName);
-}
-
-// register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called.
-static void registerLoggingFunc(const char *path) {
-    static bool loggingFuncSet = false;
-    if (loggingFuncSet) {
-        return;
-    }
-
-    ALOGV("Registering sqlite logging func \n");
-    int err = sqlite3_config(SQLITE_CONFIG_LOG, &sqlLogger, (void *)createStr(path, 0));
-    if (err != SQLITE_OK) {
-        ALOGW("sqlite returned error = %d when trying to register logging func.\n", err);
-        return;
-    }
-    loggingFuncSet = true;
-}
-
-/* public native void dbopen(String path, int flags, String locale); */
-static void dbopen(JNIEnv* env, jobject object, jstring pathString, jint flags)
-{
-    int err;
-    sqlite3 * handle = NULL;
-    sqlite3_stmt * statement = NULL;
-    char const * path8 = env->GetStringUTFChars(pathString, NULL);
-    int sqliteFlags;
-
-    // register the logging func on sqlite. needs to be done BEFORE any sqlite3 func is called.
-    registerLoggingFunc(path8);
-
-    // convert our flags into the sqlite flags
-    if (flags & CREATE_IF_NECESSARY) {
-        sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
-    } else if (flags & OPEN_READONLY) {
-        sqliteFlags = SQLITE_OPEN_READONLY;
-    } else {
-        sqliteFlags = SQLITE_OPEN_READWRITE;
-    }
-
-    err = sqlite3_open_v2(path8, &handle, sqliteFlags, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("sqlite3_open_v2(\"%s\", &handle, %d, NULL) failed\n", path8, sqliteFlags);
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-    // The soft heap limit prevents the page cache allocations from growing
-    // beyond the given limit, no matter what the max page cache sizes are
-    // set to. The limit does not, as of 3.5.0, affect any other allocations.
-    sqlite3_soft_heap_limit(sSqliteSoftHeapLimit);
-
-    // Set the default busy handler to retry for 1000ms and then return SQLITE_BUSY
-    err = sqlite3_busy_timeout(handle, 1000 /* ms */);
-    if (err != SQLITE_OK) {
-        ALOGE("sqlite3_busy_timeout(handle, 1000) failed for \"%s\"\n", path8);
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-#ifdef DB_INTEGRITY_CHECK
-    static const char* integritySql = "pragma integrity_check(1);";
-    err = sqlite3_prepare_v2(handle, integritySql, -1, &statement, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("sqlite_prepare_v2(handle, \"%s\") failed for \"%s\"\n", integritySql, path8);
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-    // first is OK or error message
-    err = sqlite3_step(statement);
-    if (err != SQLITE_ROW) {
-        ALOGE("integrity check failed for \"%s\"\n", integritySql, path8);
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    } else {
-        const char *text = (const char*)sqlite3_column_text(statement, 0);
-        if (strcmp(text, "ok") != 0) {
-            ALOGE("integrity check failed for \"%s\": %s\n", integritySql, path8, text);
-            jniThrowException(env, "android/database/sqlite/SQLiteDatabaseCorruptException", text);
-            goto done;
-        }
-    }
-#endif
-
-    err = register_android_functions(handle, UTF16_STORAGE);
-    if (err) {
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-    ALOGV("Opened '%s' - %p\n", path8, handle);
-    env->SetIntField(object, offset_db_handle, (int) handle);
-    handle = NULL;  // The caller owns the handle now.
-
-done:
-    // Release allocated resources
-    if (path8 != NULL) env->ReleaseStringUTFChars(pathString, path8);
-    if (statement != NULL) sqlite3_finalize(statement);
-    if (handle != NULL) sqlite3_close(handle);
-}
-
-static char *getDatabaseName(JNIEnv* env, sqlite3 * handle, jstring databaseName, short connNum) {
-    char const *path = env->GetStringUTFChars(databaseName, NULL);
-    if (path == NULL) {
-        ALOGE("Failure in getDatabaseName(). VM ran out of memory?\n");
-        return NULL; // VM would have thrown OutOfMemoryError
-    }
-    char *dbNameStr = createStr(path, 4);
-    if (connNum > 999) { // TODO: if number of pooled connections > 999, fix this line.
-      connNum = -1;
-    }
-    sprintf(dbNameStr + strlen(path), "|%03d", connNum);
-    env->ReleaseStringUTFChars(databaseName, path);
-    return dbNameStr;
-}
-
-static void sqlTrace(void *databaseName, const char *sql) {
-    ALOGI("sql_statement|%s|%s\n", (char *)databaseName, sql);
-}
-
-/* public native void enableSqlTracing(); */
-static void enableSqlTracing(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
-{
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    sqlite3_trace(handle, &sqlTrace, (void *)getDatabaseName(env, handle, databaseName, connType));
-}
-
-static void sqlProfile(void *databaseName, const char *sql, sqlite3_uint64 tm) {
-    double d = tm/1000000.0;
-    ALOGI("elapsedTime4Sql|%s|%.3f ms|%s\n", (char *)databaseName, d, sql);
-}
-
-/* public native void enableSqlProfiling(); */
-static void enableSqlProfiling(JNIEnv* env, jobject object, jstring databaseName, jshort connType)
-{
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    sqlite3_profile(handle, &sqlProfile, (void *)getDatabaseName(env, handle, databaseName,
-            connType));
-}
-
-/* public native void close(); */
-static void dbclose(JNIEnv* env, jobject object)
-{
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-
-    if (handle != NULL) {
-        // release the memory associated with the traceFuncArg in enableSqlTracing function
-        void *traceFuncArg = sqlite3_trace(handle, &sqlTrace, NULL);
-        if (traceFuncArg != NULL) {
-            free(traceFuncArg);
-        }
-        // release the memory associated with the traceFuncArg in enableSqlProfiling function
-        traceFuncArg = sqlite3_profile(handle, &sqlProfile, NULL);
-        if (traceFuncArg != NULL) {
-            free(traceFuncArg);
-        }
-        ALOGV("Closing database: handle=%p\n", handle);
-        int result = sqlite3_close(handle);
-        if (result == SQLITE_OK) {
-            ALOGV("Closed %p\n", handle);
-            env->SetIntField(object, offset_db_handle, 0);
-        } else {
-            // This can happen if sub-objects aren't closed first.  Make sure the caller knows.
-            throw_sqlite3_exception(env, handle);
-            ALOGE("sqlite3_close(%p) failed: %d\n", handle, result);
-        }
-    }
-}
-
-/* native int native_getDbLookaside(); */
-static jint native_getDbLookaside(JNIEnv* env, jobject object)
-{
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    int pCur = -1;
-    int unused;
-    sqlite3_db_status(handle, SQLITE_DBSTATUS_LOOKASIDE_USED, &pCur, &unused, 0);
-    return pCur;
-}
-
-/* set locale in the android_metadata table, install localized collators, and rebuild indexes */
-static void native_setLocale(JNIEnv* env, jobject object, jstring localeString, jint flags)
-{
-    if ((flags & NO_LOCALIZED_COLLATORS)) return;
-
-    int err;
-    char const* locale8 = env->GetStringUTFChars(localeString, NULL);
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    sqlite3_stmt* stmt = NULL;
-    char** meta = NULL;
-    int rowCount, colCount;
-    char* dbLocale = NULL;
-
-    // create the table, if necessary and possible
-    if (!(flags & OPEN_READONLY)) {
-        static const char *createSql ="CREATE TABLE IF NOT EXISTS " ANDROID_TABLE " (locale TEXT)";
-        err = sqlite3_exec(handle, createSql, NULL, NULL, NULL);
-        if (err != SQLITE_OK) {
-            ALOGE("CREATE TABLE " ANDROID_TABLE " failed\n");
-            throw_sqlite3_exception(env, handle);
-            goto done;
-        }
-    }
-
-    // try to read from the table
-    static const char *selectSql = "SELECT locale FROM " ANDROID_TABLE " LIMIT 1";
-    err = sqlite3_get_table(handle, selectSql, &meta, &rowCount, &colCount, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("SELECT locale FROM " ANDROID_TABLE " failed\n");
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-    dbLocale = (rowCount >= 1) ? meta[colCount] : NULL;
-
-    if (dbLocale != NULL && !strcmp(dbLocale, locale8)) {
-        // database locale is the same as the desired locale; set up the collators and go
-        err = register_localized_collators(handle, locale8, UTF16_STORAGE);
-        if (err != SQLITE_OK) throw_sqlite3_exception(env, handle);
-        goto done;   // no database changes needed
-    }
-
-    if ((flags & OPEN_READONLY)) {
-        // read-only database, so we're going to have to put up with whatever we got
-        // For registering new index. Not for modifing the read-only database.
-        err = register_localized_collators(handle, locale8, UTF16_STORAGE);
-        if (err != SQLITE_OK) throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-    // need to update android_metadata and indexes atomically, so use a transaction...
-    err = sqlite3_exec(handle, "BEGIN TRANSACTION", NULL, NULL, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("BEGIN TRANSACTION failed setting locale\n");
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-    err = register_localized_collators(handle, locale8, UTF16_STORAGE);
-    if (err != SQLITE_OK) {
-        ALOGE("register_localized_collators() failed setting locale\n");
-        throw_sqlite3_exception(env, handle);
-        goto rollback;
-    }
-
-    err = sqlite3_exec(handle, "DELETE FROM " ANDROID_TABLE, NULL, NULL, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("DELETE failed setting locale\n");
-        throw_sqlite3_exception(env, handle);
-        goto rollback;
-    }
-
-    static const char *sql = "INSERT INTO " ANDROID_TABLE " (locale) VALUES(?);";
-    err = sqlite3_prepare_v2(handle, sql, -1, &stmt, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("sqlite3_prepare_v2(\"%s\") failed\n", sql);
-        throw_sqlite3_exception(env, handle);
-        goto rollback;
-    }
-
-    err = sqlite3_bind_text(stmt, 1, locale8, -1, SQLITE_TRANSIENT);
-    if (err != SQLITE_OK) {
-        ALOGE("sqlite3_bind_text() failed setting locale\n");
-        throw_sqlite3_exception(env, handle);
-        goto rollback;
-    }
-
-    err = sqlite3_step(stmt);
-    if (err != SQLITE_OK && err != SQLITE_DONE) {
-        ALOGE("sqlite3_step(\"%s\") failed setting locale\n", sql);
-        throw_sqlite3_exception(env, handle);
-        goto rollback;
-    }
-
-    err = sqlite3_exec(handle, "REINDEX LOCALIZED", NULL, NULL, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("REINDEX LOCALIZED failed\n");
-        throw_sqlite3_exception(env, handle);
-        goto rollback;
-    }
-
-    // all done, yay!
-    err = sqlite3_exec(handle, "COMMIT TRANSACTION", NULL, NULL, NULL);
-    if (err != SQLITE_OK) {
-        ALOGE("COMMIT TRANSACTION failed setting locale\n");
-        throw_sqlite3_exception(env, handle);
-        goto done;
-    }
-
-rollback:
-    if (err != SQLITE_OK) {
-        sqlite3_exec(handle, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
-    }
-
-done:
-    if (locale8 != NULL) env->ReleaseStringUTFChars(localeString, locale8);
-    if (stmt != NULL) sqlite3_finalize(stmt);
-    if (meta != NULL) sqlite3_free_table(meta);
-}
-
-static void native_setSqliteSoftHeapLimit(JNIEnv* env, jobject clazz, jint limit) {
-    sSqliteSoftHeapLimit = limit;
-}
-
-static jint native_releaseMemory(JNIEnv *env, jobject clazz)
-{
-    // Attempt to release as much memory from the
-    return sqlite3_release_memory(sSqliteSoftHeapLimit);
-}
-
-static void native_finalize(JNIEnv* env, jobject object, jint statementId)
-{
-    if (statementId > 0) {
-        sqlite3_finalize((sqlite3_stmt *)statementId);
-    }
-}
-
-static void custom_function_callback(sqlite3_context * context, int argc, sqlite3_value ** argv) {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    if (!env) {
-        ALOGE("custom_function_callback cannot call into Java on this thread");
-        return;
-    }
-    // get global ref to CustomFunction object from our user data
-    jobject function = (jobject)sqlite3_user_data(context);
-
-    // pack up the arguments into a string array
-    jobjectArray strArray = env->NewObjectArray(argc, string_class, NULL);
-    if (!strArray)
-        goto done;
-    for (int i = 0; i < argc; i++) {
-        char* arg = (char *)sqlite3_value_text(argv[i]);
-        if (!arg) {
-            ALOGE("NULL argument in custom_function_callback.  This should not happen.");
-            return;
-        }
-        jobject obj = env->NewStringUTF(arg);
-        if (!obj)
-            goto done;
-        env->SetObjectArrayElement(strArray, i, obj);
-        env->DeleteLocalRef(obj);
-    }
-
-    env->CallVoidMethod(function, method_custom_function_callback, strArray);
-    env->DeleteLocalRef(strArray);
-
-done:
-    if (env->ExceptionCheck()) {
-        ALOGE("An exception was thrown by custom sqlite3 function.");
-        LOGE_EX(env);
-        env->ExceptionClear();
-    }
-}
-
-static jint native_addCustomFunction(JNIEnv* env, jobject object,
-        jstring name, jint numArgs, jobject function)
-{
-    sqlite3 * handle = (sqlite3 *)env->GetIntField(object, offset_db_handle);
-    char const *nameStr = env->GetStringUTFChars(name, NULL);
-    jobject ref = env->NewGlobalRef(function);
-    ALOGD_IF(DEBUG_JNI, "native_addCustomFunction %s ref: %p", nameStr, ref);
-    int err = sqlite3_create_function(handle, nameStr, numArgs, SQLITE_UTF8,
-            (void *)ref, custom_function_callback, NULL, NULL);
-    env->ReleaseStringUTFChars(name, nameStr);
-
-    if (err == SQLITE_OK)
-        return (int)ref;
-    else {
-        ALOGE("sqlite3_create_function returned %d", err);
-        env->DeleteGlobalRef(ref);
-        throw_sqlite3_exception(env, handle);
-        return 0;
-     }
-}
-
-static void native_releaseCustomFunction(JNIEnv* env, jobject object, jint ref)
-{
-    ALOGD_IF(DEBUG_JNI, "native_releaseCustomFunction %d", ref);
-    env->DeleteGlobalRef((jobject)ref);
-}
-
-static JNINativeMethod sMethods[] =
-{
-    /* name, signature, funcPtr */
-    {"dbopen", "(Ljava/lang/String;I)V", (void *)dbopen},
-    {"dbclose", "()V", (void *)dbclose},
-    {"enableSqlTracing", "(Ljava/lang/String;S)V", (void *)enableSqlTracing},
-    {"enableSqlProfiling", "(Ljava/lang/String;S)V", (void *)enableSqlProfiling},
-    {"native_setLocale", "(Ljava/lang/String;I)V", (void *)native_setLocale},
-    {"native_getDbLookaside", "()I", (void *)native_getDbLookaside},
-    {"native_setSqliteSoftHeapLimit", "(I)V", (void *)native_setSqliteSoftHeapLimit},
-    {"releaseMemory", "()I", (void *)native_releaseMemory},
-    {"native_finalize", "(I)V", (void *)native_finalize},
-    {"native_addCustomFunction",
-                    "(Ljava/lang/String;ILandroid/database/sqlite/SQLiteDatabase$CustomFunction;)I",
-                    (void *)native_addCustomFunction},
-    {"native_releaseCustomFunction", "(I)V", (void *)native_releaseCustomFunction},
-};
-
-int register_android_database_SQLiteDatabase(JNIEnv *env)
-{
-    jclass clazz;
-
-    clazz = env->FindClass("android/database/sqlite/SQLiteDatabase");
-    if (clazz == NULL) {
-        ALOGE("Can't find android/database/sqlite/SQLiteDatabase\n");
-        return -1;
-    }
-
-    string_class = (jclass)env->NewGlobalRef(env->FindClass("java/lang/String"));
-    if (string_class == NULL) {
-        ALOGE("Can't find java/lang/String\n");
-        return -1;
-    }
-
-    offset_db_handle = env->GetFieldID(clazz, "mNativeHandle", "I");
-    if (offset_db_handle == NULL) {
-        ALOGE("Can't find SQLiteDatabase.mNativeHandle\n");
-        return -1;
-    }
-
-    clazz = env->FindClass("android/database/sqlite/SQLiteDatabase$CustomFunction");
-    if (clazz == NULL) {
-        ALOGE("Can't find android/database/sqlite/SQLiteDatabase$CustomFunction\n");
-        return -1;
-    }
-    method_custom_function_callback = env->GetMethodID(clazz, "callback", "([Ljava/lang/String;)V");
-    if (method_custom_function_callback == NULL) {
-        ALOGE("Can't find method SQLiteDatabase.CustomFunction.callback\n");
-        return -1;
-    }
-
-    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteDatabase",
-            sMethods, NELEM(sMethods));
-}
-
-/* throw a SQLiteException with a message appropriate for the error in handle */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) {
-    throw_sqlite3_exception(env, handle, NULL);
-}
-
-/* throw a SQLiteException with the given message */
-void throw_sqlite3_exception(JNIEnv* env, const char* message) {
-    throw_sqlite3_exception(env, NULL, message);
-}
-
-/* throw a SQLiteException with a message appropriate for the error in handle
-   concatenated with the given message
- */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message) {
-    if (handle) {
-        throw_sqlite3_exception(env, sqlite3_errcode(handle),
-                                sqlite3_errmsg(handle), message);
-    } else {
-        // we use SQLITE_OK so that a generic SQLiteException is thrown;
-        // any code not specified in the switch statement below would do.
-        throw_sqlite3_exception(env, SQLITE_OK, "unknown error", message);
-    }
-}
-
-/* throw a SQLiteException for a given error code */
-void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message) {
-    if (errcode == SQLITE_DONE) {
-        throw_sqlite3_exception(env, errcode, NULL, message);
-    } else {
-        char temp[21];
-        sprintf(temp, "error code %d", errcode);
-        throw_sqlite3_exception(env, errcode, temp, message);
-    }
-}
-
-/* throw a SQLiteException for a given error code, sqlite3message, and
-   user message
- */
-void throw_sqlite3_exception(JNIEnv* env, int errcode,
-                             const char* sqlite3Message, const char* message) {
-    const char* exceptionClass;
-    switch (errcode) {
-        case SQLITE_IOERR:
-            exceptionClass = "android/database/sqlite/SQLiteDiskIOException";
-            break;
-        case SQLITE_CORRUPT:
-        case SQLITE_NOTADB: // treat "unsupported file format" error as corruption also
-            exceptionClass = "android/database/sqlite/SQLiteDatabaseCorruptException";
-            break;
-        case SQLITE_CONSTRAINT:
-           exceptionClass = "android/database/sqlite/SQLiteConstraintException";
-           break;
-        case SQLITE_ABORT:
-           exceptionClass = "android/database/sqlite/SQLiteAbortException";
-           break;
-        case SQLITE_DONE:
-           exceptionClass = "android/database/sqlite/SQLiteDoneException";
-           break;
-        case SQLITE_FULL:
-           exceptionClass = "android/database/sqlite/SQLiteFullException";
-           break;
-        case SQLITE_MISUSE:
-           exceptionClass = "android/database/sqlite/SQLiteMisuseException";
-           break;
-        case SQLITE_PERM:
-           exceptionClass = "android/database/sqlite/SQLiteAccessPermException";
-           break;
-        case SQLITE_BUSY:
-           exceptionClass = "android/database/sqlite/SQLiteDatabaseLockedException";
-           break;
-        case SQLITE_LOCKED:
-           exceptionClass = "android/database/sqlite/SQLiteTableLockedException";
-           break;
-        case SQLITE_READONLY:
-           exceptionClass = "android/database/sqlite/SQLiteReadOnlyDatabaseException";
-           break;
-        case SQLITE_CANTOPEN:
-           exceptionClass = "android/database/sqlite/SQLiteCantOpenDatabaseException";
-           break;
-        case SQLITE_TOOBIG:
-           exceptionClass = "android/database/sqlite/SQLiteBlobTooBigException";
-           break;
-        case SQLITE_RANGE:
-           exceptionClass = "android/database/sqlite/SQLiteBindOrColumnIndexOutOfRangeException";
-           break;
-        case SQLITE_NOMEM:
-           exceptionClass = "android/database/sqlite/SQLiteOutOfMemoryException";
-           break;
-        case SQLITE_MISMATCH:
-           exceptionClass = "android/database/sqlite/SQLiteDatatypeMismatchException";
-           break;
-        case SQLITE_UNCLOSED:
-           exceptionClass = "android/database/sqlite/SQLiteUnfinalizedObjectsException";
-           break;
-        default:
-           exceptionClass = "android/database/sqlite/SQLiteException";
-           break;
-    }
-
-    if (sqlite3Message != NULL && message != NULL) {
-        char* fullMessage = (char *)malloc(strlen(sqlite3Message) + strlen(message) + 3);
-        if (fullMessage != NULL) {
-            strcpy(fullMessage, sqlite3Message);
-            strcat(fullMessage, ": ");
-            strcat(fullMessage, message);
-            jniThrowException(env, exceptionClass, fullMessage);
-            free(fullMessage);
-        } else {
-            jniThrowException(env, exceptionClass, sqlite3Message);
-        }
-    } else if (sqlite3Message != NULL) {
-        jniThrowException(env, exceptionClass, sqlite3Message);
-    } else {
-        jniThrowException(env, exceptionClass, message);
-    }
-}
-
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteGlobal.cpp b/core/jni/android_database_SQLiteGlobal.cpp
new file mode 100644
index 0000000..82cae5a
--- /dev/null
+++ b/core/jni/android_database_SQLiteGlobal.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SQLiteGlobal"
+
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <sqlite3.h>
+#include <sqlite3_android.h>
+
+#include "android_database_SQLiteCommon.h"
+
+namespace android {
+
+// Called each time a message is logged.
+static void sqliteLogCallback(void* data, int iErrCode, const char* zMsg) {
+    bool verboseLog = !!data;
+    if (iErrCode == 0 || iErrCode == SQLITE_CONSTRAINT) {
+        if (verboseLog) {
+            ALOGV(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
+        }
+    } else {
+        ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", iErrCode, zMsg);
+    }
+}
+
+// Sets the global SQLite configuration.
+// This must be called before any other SQLite functions are called. */
+static void nativeConfig(JNIEnv* env, jclass clazz, jboolean verboseLog, jint softHeapLimit) {
+    // Enable multi-threaded mode.  In this mode, SQLite is safe to use by multiple
+    // threads as long as no two threads use the same database connection at the same
+    // time (which we guarantee in the SQLite database wrappers).
+    sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
+
+    // Redirect SQLite log messages to the Android log.
+    sqlite3_config(SQLITE_CONFIG_LOG, &sqliteLogCallback, verboseLog ? (void*)1 : NULL);
+
+    // The soft heap limit prevents the page cache allocations from growing
+    // beyond the given limit, no matter what the max page cache sizes are
+    // set to. The limit does not, as of 3.5.0, affect any other allocations.
+    sqlite3_soft_heap_limit(softHeapLimit);
+}
+
+static jint nativeReleaseMemory(JNIEnv* env, jclass clazz, jint bytesToFree) {
+    return sqlite3_release_memory(bytesToFree);
+}
+
+static JNINativeMethod sMethods[] =
+{
+    /* name, signature, funcPtr */
+    { "nativeConfig", "(ZI)V",
+            (void*)nativeConfig },
+    { "nativeReleaseMemory", "(I)I",
+            (void*)nativeReleaseMemory },
+};
+
+int register_android_database_SQLiteGlobal(JNIEnv *env)
+{
+    return AndroidRuntime::registerNativeMethods(env, "android/database/sqlite/SQLiteGlobal",
+            sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_database_SQLiteProgram.cpp b/core/jni/android_database_SQLiteProgram.cpp
deleted file mode 100644
index 2e34c00..0000000
--- a/core/jni/android_database_SQLiteProgram.cpp
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2006-2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "Cursor"
-
-#include <jni.h>
-#include <JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-
-#include <sqlite3.h>
-
-#include <utils/Log.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "sqlite3_exception.h"
-
-
-namespace android {
-
-static jfieldID gHandleField;
-static jfieldID gStatementField;
-
-
-#define GET_STATEMENT(env, object) \
-        (sqlite3_stmt *)env->GetIntField(object, gStatementField)
-#define GET_HANDLE(env, object) \
-        (sqlite3 *)env->GetIntField(object, gHandleField)
-
-static void native_compile(JNIEnv* env, jobject object, jstring sqlString)
-{
-    char buf[65];
-    strcpy(buf, "android_database_SQLiteProgram->native_compile() not implemented");
-    throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
-    return;
-}
-
-static void native_bind_null(JNIEnv* env, jobject object,
-                             jint index)
-{
-    int err;
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
-    err = sqlite3_bind_null(statement, index);
-    if (err != SQLITE_OK) {
-        char buf[32];
-        sprintf(buf, "handle %p", statement);
-        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
-        return;
-    }
-}
-
-static void native_bind_long(JNIEnv* env, jobject object,
-                             jint index, jlong value)
-{
-    int err;
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
-    err = sqlite3_bind_int64(statement, index, value);
-    if (err != SQLITE_OK) {
-        char buf[32];
-        sprintf(buf, "handle %p", statement);
-        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
-        return;
-    }
-}
-
-static void native_bind_double(JNIEnv* env, jobject object,
-                             jint index, jdouble value)
-{
-    int err;
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
-    err = sqlite3_bind_double(statement, index, value);
-    if (err != SQLITE_OK) {
-        char buf[32];
-        sprintf(buf, "handle %p", statement);
-        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
-        return;
-    }
-}
-
-static void native_bind_string(JNIEnv* env, jobject object,
-                               jint index, jstring sqlString)
-{
-    int err;
-    jchar const * sql;
-    jsize sqlLen;
-    sqlite3_stmt * statement= GET_STATEMENT(env, object);
-
-    sql = env->GetStringChars(sqlString, NULL);
-    sqlLen = env->GetStringLength(sqlString);
-    err = sqlite3_bind_text16(statement, index, sql, sqlLen * 2, SQLITE_TRANSIENT);
-    env->ReleaseStringChars(sqlString, sql);
-    if (err != SQLITE_OK) {
-        char buf[32];
-        sprintf(buf, "handle %p", statement);
-        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
-        return;
-    }
-}
-
-static void native_bind_blob(JNIEnv* env, jobject object,
-                               jint index, jbyteArray value)
-{
-    int err;
-    jchar const * sql;
-    jsize sqlLen;
-    sqlite3_stmt * statement= GET_STATEMENT(env, object);
-
-    jint len = env->GetArrayLength(value);
-    jbyte * bytes = env->GetByteArrayElements(value, NULL);
-
-    err = sqlite3_bind_blob(statement, index, bytes, len, SQLITE_TRANSIENT);
-    env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
-
-    if (err != SQLITE_OK) {
-        char buf[32];
-        sprintf(buf, "statement %p", statement);
-        throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
-        return;
-    }
-}
-
-static void native_clear_bindings(JNIEnv* env, jobject object)
-{
-    int err;
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-
-    err = sqlite3_clear_bindings(statement);
-    if (err != SQLITE_OK) {
-        throw_sqlite3_exception(env, GET_HANDLE(env, object));
-        return;
-    }
-}
-
-static void native_finalize(JNIEnv* env, jobject object)
-{
-    char buf[66];
-    strcpy(buf, "android_database_SQLiteProgram->native_finalize() not implemented");
-    throw_sqlite3_exception(env, GET_HANDLE(env, object), buf);
-    return;
-}
-
-
-static JNINativeMethod sMethods[] =
-{
-     /* name, signature, funcPtr */
-    {"native_bind_null", "(I)V", (void *)native_bind_null},
-    {"native_bind_long", "(IJ)V", (void *)native_bind_long},
-    {"native_bind_double", "(ID)V", (void *)native_bind_double},
-    {"native_bind_string", "(ILjava/lang/String;)V", (void *)native_bind_string},
-    {"native_bind_blob", "(I[B)V", (void *)native_bind_blob},
-    {"native_clear_bindings", "()V", (void *)native_clear_bindings},
-};
-
-int register_android_database_SQLiteProgram(JNIEnv * env)
-{
-    jclass clazz;
-
-    clazz = env->FindClass("android/database/sqlite/SQLiteProgram");
-    if (clazz == NULL) {
-        ALOGE("Can't find android/database/sqlite/SQLiteProgram");
-        return -1;
-    }
-
-    gHandleField = env->GetFieldID(clazz, "nHandle", "I");
-    gStatementField = env->GetFieldID(clazz, "nStatement", "I");
-
-    if (gHandleField == NULL || gStatementField == NULL) {
-        ALOGE("Error locating fields");
-        return -1;
-    }
-
-    return AndroidRuntime::registerNativeMethods(env,
-        "android/database/sqlite/SQLiteProgram", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteQuery.cpp b/core/jni/android_database_SQLiteQuery.cpp
deleted file mode 100644
index da7ccf0..0000000
--- a/core/jni/android_database_SQLiteQuery.cpp
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SqliteCursor.cpp"
-
-#include <jni.h>
-#include <JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-
-#include <sqlite3.h>
-
-#include <utils/Log.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "binder/CursorWindow.h"
-#include "sqlite3_exception.h"
-
-
-namespace android {
-
-enum CopyRowResult {
-    CPR_OK,
-    CPR_FULL,
-    CPR_ERROR,
-};
-
-static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
-        sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
-    // Allocate a new field directory for the row. This pointer is not reused
-    // since it may be possible for it to be relocated on a call to alloc() when
-    // the field data is being allocated.
-    status_t status = window->allocRow();
-    if (status) {
-        LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
-                startPos, addedRows, status);
-        return CPR_FULL;
-    }
-
-    // Pack the row into the window.
-    CopyRowResult result = CPR_OK;
-    for (int i = 0; i < numColumns; i++) {
-        int type = sqlite3_column_type(statement, i);
-        if (type == SQLITE_TEXT) {
-            // TEXT data
-            const char* text = reinterpret_cast<const char*>(
-                    sqlite3_column_text(statement, i));
-            // SQLite does not include the NULL terminator in size, but does
-            // ensure all strings are NULL terminated, so increase size by
-            // one to make sure we store the terminator.
-            size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
-            status = window->putString(addedRows, i, text, sizeIncludingNull);
-            if (status) {
-                LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
-                        sizeIncludingNull, startPos + addedRows, i, status);
-                result = CPR_FULL;
-                break;
-            }
-            LOG_WINDOW("%d,%d is TEXT with %u bytes",
-                    startPos + addedRows, i, sizeIncludingNull);
-        } else if (type == SQLITE_INTEGER) {
-            // INTEGER data
-            int64_t value = sqlite3_column_int64(statement, i);
-            status = window->putLong(addedRows, i, value);
-            if (status) {
-                LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
-                        i, status);
-                result = CPR_FULL;
-                break;
-            }
-            LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
-        } else if (type == SQLITE_FLOAT) {
-            // FLOAT data
-            double value = sqlite3_column_double(statement, i);
-            status = window->putDouble(addedRows, i, value);
-            if (status) {
-                LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
-                        i, status);
-                result = CPR_FULL;
-                break;
-            }
-            LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
-        } else if (type == SQLITE_BLOB) {
-            // BLOB data
-            const void* blob = sqlite3_column_blob(statement, i);
-            size_t size = sqlite3_column_bytes(statement, i);
-            status = window->putBlob(addedRows, i, blob, size);
-            if (status) {
-                LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
-                        size, startPos + addedRows, i, status);
-                result = CPR_FULL;
-                break;
-            }
-            LOG_WINDOW("%d,%d is Blob with %u bytes",
-                    startPos + addedRows, i, size);
-        } else if (type == SQLITE_NULL) {
-            // NULL field
-            status = window->putNull(addedRows, i);
-            if (status) {
-                LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
-                        i, status);
-                result = CPR_FULL;
-                break;
-            }
-
-            LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
-        } else {
-            // Unknown data
-            ALOGE("Unknown column type when filling database window");
-            throw_sqlite3_exception(env, "Unknown column type when filling window");
-            result = CPR_ERROR;
-            break;
-        }
-    }
-
-    // Free the last row if if was not successfully copied.
-    if (result != CPR_OK) {
-        window->freeLastRow();
-    }
-    return result;
-}
-
-static jlong nativeFillWindow(JNIEnv* env, jclass clazz, jint databasePtr,
-        jint statementPtr, jint windowPtr, jint offsetParam,
-        jint startPos, jint requiredPos, jboolean countAllRows) {
-    sqlite3* database = reinterpret_cast<sqlite3*>(databasePtr);
-    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
-    CursorWindow* window = reinterpret_cast<CursorWindow*>(windowPtr);
-
-    // Only do the binding if there is a valid offsetParam. If no binding needs to be done
-    // offsetParam will be set to 0, an invalid value.
-    if (offsetParam > 0) {
-        // Bind the offset parameter, telling the program which row to start with
-        // If an offset parameter is used, we cannot simply clear the window if it
-        // turns out that the requiredPos won't fit because the result set may
-        // depend on startPos, so we set startPos to requiredPos.
-        startPos = requiredPos;
-        int err = sqlite3_bind_int(statement, offsetParam, startPos);
-        if (err != SQLITE_OK) {
-            ALOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
-            throw_sqlite3_exception(env, database);
-            return 0;
-        }
-        LOG_WINDOW("Bound offset position to startPos %d", startPos);
-    }
-
-    // We assume numRows is initially 0.
-    LOG_WINDOW("Window: numRows = %d, size = %d, freeSpace = %d",
-            window->getNumRows(), window->size(), window->freeSpace());
-
-    int numColumns = sqlite3_column_count(statement);
-    status_t status = window->setNumColumns(numColumns);
-    if (status) {
-        ALOGE("Failed to change column count from %d to %d", window->getNumColumns(), numColumns);
-        jniThrowException(env, "java/lang/IllegalStateException", "numColumns mismatch");
-        return 0;
-    }
-
-    int retryCount = 0;
-    int totalRows = 0;
-    int addedRows = 0;
-    bool windowFull = false;
-    bool gotException = false;
-    while (!gotException && (!windowFull || countAllRows)) {
-        int err = sqlite3_step(statement);
-        if (err == SQLITE_ROW) {
-            LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
-            retryCount = 0;
-            totalRows += 1;
-
-            // Skip the row if the window is full or we haven't reached the start position yet.
-            if (startPos >= totalRows || windowFull) {
-                continue;
-            }
-
-            CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
-            if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {
-                // We filled the window before we got to the one row that we really wanted.
-                // Clear the window and start filling it again from here.
-                // TODO: Would be nicer if we could progressively replace earlier rows.
-                window->clear();
-                window->setNumColumns(numColumns);
-                startPos += addedRows;
-                addedRows = 0;
-                cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
-            }
-
-            if (cpr == CPR_OK) {
-                addedRows += 1;
-            } else if (cpr == CPR_FULL) {
-                windowFull = true;
-            } else {
-                gotException = true;
-            }
-        } else if (err == SQLITE_DONE) {
-            // All rows processed, bail
-            LOG_WINDOW("Processed all rows");
-            break;
-        } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
-            // The table is locked, retry
-            LOG_WINDOW("Database locked, retrying");
-            if (retryCount > 50) {
-                ALOGE("Bailing on database busy retry");
-                throw_sqlite3_exception(env, database, "retrycount exceeded");
-                gotException = true;
-            } else {
-                // Sleep to give the thread holding the lock a chance to finish
-                usleep(1000);
-                retryCount++;
-            }
-        } else {
-            throw_sqlite3_exception(env, database);
-            gotException = true;
-        }
-    }
-
-    LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
-            "to the window in %d bytes",
-            statement, totalRows, addedRows, window->size() - window->freeSpace());
-    sqlite3_reset(statement);
-
-    // Report the total number of rows on request.
-    if (startPos > totalRows) {
-        ALOGE("startPos %d > actual rows %d", startPos, totalRows);
-    }
-    jlong result = jlong(startPos) << 32 | jlong(totalRows);
-    return result;
-}
-
-static jint nativeColumnCount(JNIEnv* env, jclass clazz, jint statementPtr) {
-    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
-    return sqlite3_column_count(statement);
-}
-
-static jstring nativeColumnName(JNIEnv* env, jclass clazz, jint statementPtr,
-        jint columnIndex) {
-    sqlite3_stmt* statement = reinterpret_cast<sqlite3_stmt*>(statementPtr);
-    const char* name = sqlite3_column_name(statement, columnIndex);
-    return env->NewStringUTF(name);
-}
-
-
-static JNINativeMethod sMethods[] =
-{
-     /* name, signature, funcPtr */
-    { "nativeFillWindow", "(IIIIIIZ)J",
-            (void*)nativeFillWindow },
-    { "nativeColumnCount", "(I)I",
-            (void*)nativeColumnCount},
-    { "nativeColumnName", "(II)Ljava/lang/String;",
-            (void*)nativeColumnName},
-};
-
-int register_android_database_SQLiteQuery(JNIEnv * env)
-{
-    return AndroidRuntime::registerNativeMethods(env,
-        "android/database/sqlite/SQLiteQuery", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/android_database_SQLiteStatement.cpp b/core/jni/android_database_SQLiteStatement.cpp
deleted file mode 100644
index e376258..0000000
--- a/core/jni/android_database_SQLiteStatement.cpp
+++ /dev/null
@@ -1,286 +0,0 @@
-/* //device/libs/android_runtime/android_database_SQLiteCursor.cpp
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-#undef LOG_TAG
-#define LOG_TAG "SQLiteStatementCpp"
-
-#include "android_util_Binder.h"
-
-#include <jni.h>
-#include <JNIHelp.h>
-#include <android_runtime/AndroidRuntime.h>
-
-#include <sqlite3.h>
-
-#include <cutils/ashmem.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-
-#include "sqlite3_exception.h"
-
-namespace android {
-
-
-sqlite3_stmt * compile(JNIEnv* env, jobject object,
-                       sqlite3 * handle, jstring sqlString);
-
-static jfieldID gHandleField;
-static jfieldID gStatementField;
-
-
-#define GET_STATEMENT(env, object) \
-        (sqlite3_stmt *)env->GetIntField(object, gStatementField)
-#define GET_HANDLE(env, object) \
-        (sqlite3 *)env->GetIntField(object, gHandleField)
-
-
-static jint native_execute(JNIEnv* env, jobject object)
-{
-    int err;
-    sqlite3 * handle = GET_HANDLE(env, object);
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-    int numChanges = -1;
-
-    // Execute the statement
-    err = sqlite3_step(statement);
-
-    // Throw an exception if an error occurred
-    if (err == SQLITE_ROW) {
-        throw_sqlite3_exception(env,
-                "Queries can be performed using SQLiteDatabase query or rawQuery methods only.");
-    } else if (err != SQLITE_DONE) {
-        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
-    } else {
-        numChanges = sqlite3_changes(handle);
-    }
-
-    // Reset the statement so it's ready to use again
-    sqlite3_reset(statement);
-    return numChanges;
-}
-
-static jlong native_executeInsert(JNIEnv* env, jobject object)
-{
-    sqlite3 * handle = GET_HANDLE(env, object);
-    jint numChanges = native_execute(env, object);
-    if (numChanges > 0) {
-        return sqlite3_last_insert_rowid(handle);
-    } else {
-        return -1;
-    }
-}
-
-static jlong native_1x1_long(JNIEnv* env, jobject object)
-{
-    int err;
-    sqlite3 * handle = GET_HANDLE(env, object);
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-    jlong value = -1;
-
-    // Execute the statement
-    err = sqlite3_step(statement);
-
-    // Handle the result
-    if (err == SQLITE_ROW) {
-        // No errors, read the data and return it
-        value = sqlite3_column_int64(statement, 0);
-    } else {
-        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
-    }
-
-    // Reset the statment so it's ready to use again
-    sqlite3_reset(statement);
-
-    return value;
-}
-
-static jstring native_1x1_string(JNIEnv* env, jobject object)
-{
-    int err;
-    sqlite3 * handle = GET_HANDLE(env, object);
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-    jstring value = NULL;
-
-    // Execute the statement
-    err = sqlite3_step(statement);
-
-    // Handle the result
-    if (err == SQLITE_ROW) {
-        // No errors, read the data and return it
-        char const * text = (char const *)sqlite3_column_text(statement, 0);
-        value = env->NewStringUTF(text);
-    } else {
-        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
-    }
-
-    // Reset the statment so it's ready to use again
-    sqlite3_reset(statement);
-
-    return value;
-}
-
-static jobject createParcelFileDescriptor(JNIEnv * env, int fd)
-{
-    // Create FileDescriptor object
-    jobject fileDesc = jniCreateFileDescriptor(env, fd);
-    if (fileDesc == NULL) {
-        // FileDescriptor constructor has thrown an exception
-        close(fd);
-        return NULL;
-    }
-
-    // Wrap it in a ParcelFileDescriptor
-    jobject parcelFileDesc = newParcelFileDescriptor(env, fileDesc);
-    if (parcelFileDesc == NULL) {
-        // ParcelFileDescriptor constructor has thrown an exception
-        close(fd);
-        return NULL;
-    }
-
-    return parcelFileDesc;
-}
-
-// Creates an ashmem area, copies some data into it, and returns
-// a ParcelFileDescriptor for the ashmem area.
-static jobject create_ashmem_region_with_data(JNIEnv * env,
-                                              const void * data, int length)
-{
-    // Create ashmem area
-    int fd = ashmem_create_region(NULL, length);
-    if (fd < 0) {
-        ALOGE("ashmem_create_region failed: %s", strerror(errno));
-        jniThrowIOException(env, errno);
-        return NULL;
-    }
-
-    if (length > 0) {
-        // mmap the ashmem area
-        void * ashmem_ptr =
-                mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
-        if (ashmem_ptr == MAP_FAILED) {
-            ALOGE("mmap failed: %s", strerror(errno));
-            jniThrowIOException(env, errno);
-            close(fd);
-            return NULL;
-        }
-
-        // Copy data to ashmem area
-        memcpy(ashmem_ptr, data, length);
-
-        // munmap ashmem area
-        if (munmap(ashmem_ptr, length) < 0) {
-            ALOGE("munmap failed: %s", strerror(errno));
-            jniThrowIOException(env, errno);
-            close(fd);
-            return NULL;
-        }
-    }
-
-    // Make ashmem area read-only
-    if (ashmem_set_prot_region(fd, PROT_READ) < 0) {
-        ALOGE("ashmem_set_prot_region failed: %s", strerror(errno));
-        jniThrowIOException(env, errno);
-        close(fd);
-        return NULL;
-    }
-
-    // Wrap it in a ParcelFileDescriptor
-    return createParcelFileDescriptor(env, fd);
-}
-
-static jobject native_1x1_blob_ashmem(JNIEnv* env, jobject object)
-{
-    int err;
-    sqlite3 * handle = GET_HANDLE(env, object);
-    sqlite3_stmt * statement = GET_STATEMENT(env, object);
-    jobject value = NULL;
-
-    // Execute the statement
-    err = sqlite3_step(statement);
-
-    // Handle the result
-    if (err == SQLITE_ROW) {
-        // No errors, read the data and return it
-        const void * blob = sqlite3_column_blob(statement, 0);
-        if (blob != NULL) {
-            int len = sqlite3_column_bytes(statement, 0);
-            if (len >= 0) {
-                value = create_ashmem_region_with_data(env, blob, len);
-            }
-        }
-    } else {
-        throw_sqlite3_exception_errcode(env, err, sqlite3_errmsg(handle));
-    }
-
-    // Reset the statment so it's ready to use again
-    sqlite3_reset(statement);
-
-    return value;
-}
-
-static void native_executeSql(JNIEnv* env, jobject object, jstring sql)
-{
-    char const* sqlString = env->GetStringUTFChars(sql, NULL);
-    sqlite3 * handle = GET_HANDLE(env, object);
-    int err = sqlite3_exec(handle, sqlString, NULL, NULL, NULL);
-    if (err != SQLITE_OK) {
-        throw_sqlite3_exception(env, handle);
-    }
-    env->ReleaseStringUTFChars(sql, sqlString);
-}
-
-static JNINativeMethod sMethods[] =
-{
-     /* name, signature, funcPtr */
-    {"native_execute", "()I", (void *)native_execute},
-    {"native_executeInsert", "()J", (void *)native_executeInsert},
-    {"native_1x1_long", "()J", (void *)native_1x1_long},
-    {"native_1x1_string", "()Ljava/lang/String;", (void *)native_1x1_string},
-    {"native_1x1_blob_ashmem", "()Landroid/os/ParcelFileDescriptor;", (void *)native_1x1_blob_ashmem},
-    {"native_executeSql", "(Ljava/lang/String;)V", (void *)native_executeSql},
-};
-
-int register_android_database_SQLiteStatement(JNIEnv * env)
-{
-    jclass clazz;
-
-    clazz = env->FindClass("android/database/sqlite/SQLiteStatement");
-    if (clazz == NULL) {
-        ALOGE("Can't find android/database/sqlite/SQLiteStatement");
-        return -1;
-    }
-
-    gHandleField = env->GetFieldID(clazz, "nHandle", "I");
-    gStatementField = env->GetFieldID(clazz, "nStatement", "I");
-
-    if (gHandleField == NULL || gStatementField == NULL) {
-        ALOGE("Error locating fields");
-        return -1;
-    }
-
-    return AndroidRuntime::registerNativeMethods(env,
-        "android/database/sqlite/SQLiteStatement", sMethods, NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/core/jni/sqlite3_exception.h b/core/jni/sqlite3_exception.h
deleted file mode 100644
index 13735a1..0000000
--- a/core/jni/sqlite3_exception.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* //device/libs/include/android_runtime/sqlite3_exception.h
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
-
-#ifndef _SQLITE3_EXCEPTION_H
-#define _SQLITE3_EXCEPTION_H 1
-
-#include <jni.h>
-#include <JNIHelp.h>
-//#include <android_runtime/AndroidRuntime.h>
-
-#include <sqlite3.h>
-
-namespace android {
-
-/* throw a SQLiteException with a message appropriate for the error in handle */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle);
-
-/* throw a SQLiteException with the given message */
-void throw_sqlite3_exception(JNIEnv* env, const char* message);
-
-/* throw a SQLiteException with a message appropriate for the error in handle
-   concatenated with the given message
- */
-void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle, const char* message);
-
-/* throw a SQLiteException for a given error code */
-void throw_sqlite3_exception_errcode(JNIEnv* env, int errcode, const char* message);
-
-void throw_sqlite3_exception(JNIEnv* env, int errcode,
-                             const char* sqlite3Message, const char* message);
-}
-
-#endif // _SQLITE3_EXCEPTION_H
diff --git a/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java b/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java
deleted file mode 100644
index 525dd2d..0000000
--- a/core/tests/coretests/src/android/database/sqlite/DatabaseConnectionPoolTest.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class DatabaseConnectionPoolTest extends AndroidTestCase {
-    private static final String TAG = "DatabaseConnectionPoolTest";
-
-    private static final int MAX_CONN = 5;
-    private static final String TEST_SQL = "select * from test where i = ? AND j = 1";
-    private static final String[] TEST_SQLS = new String[] {
-        TEST_SQL, TEST_SQL + 1, TEST_SQL + 2, TEST_SQL + 3, TEST_SQL + 4
-    };
-
-    private SQLiteDatabase mDatabase;
-    private File mDatabaseFile;
-    private DatabaseConnectionPool mTestPool;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
-        mDatabaseFile = new File(dbDir, "database_test.db");
-        if (mDatabaseFile.exists()) {
-            mDatabaseFile.delete();
-        }
-        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
-        assertNotNull(mDatabase);
-        mDatabase.execSQL("create table test (i int, j int);");
-        mTestPool = new DatabaseConnectionPool(mDatabase);
-        assertNotNull(mTestPool);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mTestPool.close();
-        mDatabase.close();
-        mDatabaseFile.delete();
-        super.tearDown();
-    }
-
-    @SmallTest
-    public void testGetAndRelease() {
-        mTestPool.setMaxPoolSize(MAX_CONN);
-        // connections should be lazily created.
-        assertEquals(0, mTestPool.getSize());
-        // MAX pool size should be set to MAX_CONN
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // get a connection
-        SQLiteDatabase db = mTestPool.get(TEST_SQL);
-        // pool size should be one - since only one should be allocated for the above get()
-        assertEquals(1, mTestPool.getSize());
-        assertEquals(mDatabase, db.mParentConnObj);
-        // no free connections should be available
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertFalse(mTestPool.isDatabaseObjFree(db));
-        // release the connection
-        mTestPool.release(db);
-        assertEquals(1, mTestPool.getFreePoolSize());
-        assertEquals(1, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        assertTrue(mTestPool.isDatabaseObjFree(db));
-        // release the same object again and expect IllegalStateException
-        try {
-            mTestPool.release(db);
-            fail("illegalStateException expected");
-        } catch (IllegalStateException e ) {
-            // expected.
-        }
-    }
-
-    /**
-     * get all connections from the pool and ask for one more.
-     * should get one of the connections already got so far. 
-     */
-    @SmallTest
-    public void testGetAllConnAndOneMore() {
-        mTestPool.setMaxPoolSize(MAX_CONN);
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        ArrayList<SQLiteDatabase> dbObjs = new ArrayList<SQLiteDatabase>();
-        for (int i = 0; i < MAX_CONN; i++) {
-            SQLiteDatabase db = mTestPool.get(TEST_SQL);
-            assertFalse(dbObjs.contains(db));
-            dbObjs.add(db);
-            assertEquals(mDatabase, db.mParentConnObj);
-        }
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // pool is maxed out and no free connections. ask for one more connection
-        SQLiteDatabase db1 = mTestPool.get(TEST_SQL);
-        // make sure db1 is one of the existing ones
-        assertTrue(dbObjs.contains(db1));
-        // pool size should remain at MAX_CONN
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // release db1 but since it is allocated 2 times, it should still remain 'busy'
-        mTestPool.release(db1);
-        assertFalse(mTestPool.isDatabaseObjFree(db1));
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // release all connections
-        for (int i = 0; i < MAX_CONN; i++) {
-            mTestPool.release(dbObjs.get(i));
-        }
-        // all objects in the pool should be freed now
-        assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-    }
-    
-    /**
-     * same as above except that each connection has different SQL statement associated with it. 
-     */
-    @SmallTest
-    public void testConnRetrievalForPreviouslySeenSql() {
-        mTestPool.setMaxPoolSize(MAX_CONN);
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>();
-        for (int i = 0; i < MAX_CONN; i++) {
-            SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
-            executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
-            assertFalse(dbObjs.values().contains(db));
-            dbObjs.put(TEST_SQLS[i], db);
-        }
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // pool is maxed out and no free connections. ask for one more connection
-        // use a previously seen SQL statement
-        String testSql = TEST_SQLS[MAX_CONN - 1];
-        SQLiteDatabase db1 = mTestPool.get(testSql);
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // make sure db1 is one of the existing ones
-        assertTrue(dbObjs.values().contains(db1));
-        assertEquals(db1, dbObjs.get(testSql));
-        // do the same again
-        SQLiteDatabase db2 = mTestPool.get(testSql);
-        // make sure db1 is one of the existing ones
-        assertEquals(db2, dbObjs.get(testSql));
-
-        // pool size should remain at MAX_CONN
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        // release db1 but since the same connection is allocated 3 times,
-        // it should still remain 'busy'
-        mTestPool.release(db1);
-        assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql)));
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        // release db2 but since the same connection is allocated 2 times,
-        // it should still remain 'busy'
-        mTestPool.release(db2);
-        assertFalse(mTestPool.isDatabaseObjFree(dbObjs.get(testSql)));
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        // release all connections
-        for (int i = 0; i < MAX_CONN; i++) {
-            mTestPool.release(dbObjs.get(TEST_SQLS[i]));
-        }
-        // all objects in the pool should be freed now
-        assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-    }
-
-    private void executeSqlOnDatabaseConn(SQLiteDatabase db, String sql) {
-        // get the given sql be compiled on the given database connection.
-        // this will help DatabaseConenctionPool figure out if a given SQL statement
-        // is already cached by a database connection.
-        ClassToTestSqlCompilationAndCaching c =
-                ClassToTestSqlCompilationAndCaching.create(db, sql);
-        c.close();
-    }
-
-    /**
-     * get a connection for a SQL statement 'blah'. (connection_s)
-     * make sure the pool has at least one free connection even after this get().
-     * and get a connection for the same SQL again.
-     *    this connection should be different from connection_s.
-     *    even though there is a connection with the given SQL pre-compiled, since is it not free
-     *    AND since the pool has free connections available, should get a new connection.
-     */
-    @SmallTest
-    public void testGetConnForTheSameSql() {
-        mTestPool.setMaxPoolSize(MAX_CONN);
-
-        SQLiteDatabase db = mTestPool.get(TEST_SQL);
-        executeSqlOnDatabaseConn(db, TEST_SQL);
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(1, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        assertFalse(mTestPool.isDatabaseObjFree(db));
-
-        SQLiteDatabase db1 = mTestPool.get(TEST_SQL);
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(2, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        assertFalse(mTestPool.isDatabaseObjFree(db1));
-        assertFalse(db1.equals(db));
-
-        mTestPool.release(db);
-        assertEquals(1, mTestPool.getFreePoolSize());
-        assertEquals(2, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        mTestPool.release(db1);
-        assertEquals(2, mTestPool.getFreePoolSize());
-        assertEquals(2, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-    }
-
-    /**
-     * get the same connection N times and release it N times.
-     * this tests DatabaseConnectionPool.PoolObj.mNumHolders
-     */
-    @SmallTest
-    public void testGetSameConnNtimesAndReleaseItNtimes() {
-        mTestPool.setMaxPoolSize(MAX_CONN);
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        HashMap<String, SQLiteDatabase> dbObjs = new HashMap<String, SQLiteDatabase>();
-        for (int i = 0; i < MAX_CONN; i++) {
-            SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
-            executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
-            assertFalse(dbObjs.values().contains(db));
-            dbObjs.put(TEST_SQLS[i], db);
-        }
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // every connection in the pool should have numHolders = 1
-        for (int i = 0; i < MAX_CONN; i ++) {
-            assertEquals(1, mTestPool.getPool().get(i).getNumHolders());
-        }
-        // pool is maxed out and no free connections. ask for one more connection
-        // use a previously seen SQL statement
-        String testSql = TEST_SQLS[MAX_CONN - 1];
-        SQLiteDatabase db1 = mTestPool.get(testSql);
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // make sure db1 is one of the existing ones
-        assertTrue(dbObjs.values().contains(db1));
-        assertEquals(db1, dbObjs.get(testSql));
-        assertFalse(mTestPool.isDatabaseObjFree(db1));
-        DatabaseConnectionPool.PoolObj poolObj = mTestPool.getPool().get(db1.mConnectionNum - 1);
-        int numHolders = poolObj.getNumHolders();
-        assertEquals(2, numHolders);
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // get the same connection N times more
-        int N = 100;
-        for (int i = 0; i < N; i++) {
-            SQLiteDatabase db2 = mTestPool.get(testSql);
-            assertEquals(db1, db2);
-            assertFalse(mTestPool.isDatabaseObjFree(db2));
-            // numHolders for this object should be now up by 1
-            int prev = numHolders;
-            numHolders = poolObj.getNumHolders();
-            assertEquals(prev + 1, numHolders);
-        }
-        // release it N times
-        for (int i = 0; i < N; i++) {
-            mTestPool.release(db1);
-            int prev = numHolders;
-            numHolders = poolObj.getNumHolders();
-            assertEquals(prev - 1, numHolders);
-            assertFalse(mTestPool.isDatabaseObjFree(db1));
-        }
-        // the connection should still have 2 more holders
-        assertFalse(mTestPool.isDatabaseObjFree(db1));
-        assertEquals(2, poolObj.getNumHolders());
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // release 2 more times
-        mTestPool.release(db1);
-        mTestPool.release(db1);
-        assertEquals(0, poolObj.getNumHolders());
-        assertEquals(1, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        assertTrue(mTestPool.isDatabaseObjFree(db1));
-    }
-
-    @SmallTest
-    public void testStressTest() {
-        mTestPool.setMaxPoolSize(MAX_CONN);
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-
-        HashMap<SQLiteDatabase, Integer> dbMap = new HashMap<SQLiteDatabase, Integer>();
-        for (int i = 0; i < MAX_CONN; i++) {
-            SQLiteDatabase db = mTestPool.get(TEST_SQLS[i]);
-            assertFalse(dbMap.containsKey(db));
-            dbMap.put(db, 1);
-            executeSqlOnDatabaseConn(db, TEST_SQLS[i]);
-        }
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // ask for lot more connections but since the pool is maxed out, we should start receiving
-        // connections that we already got so far
-        for (int i = MAX_CONN; i < 1000; i++) {
-            SQLiteDatabase db = mTestPool.get(TEST_SQL + i);
-            assertTrue(dbMap.containsKey(db));
-            int k = dbMap.get(db);
-            dbMap.put(db, ++k);
-        }
-        assertEquals(0, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-        // print the distribution of the database connection handles received, should be uniform.
-        for (SQLiteDatabase d : dbMap.keySet()) {
-            Log.i(TAG, "connection # " + d.mConnectionNum + ", numHolders: " + dbMap.get(d));
-        }
-        // print the pool info
-        Log.i(TAG, mTestPool.toString());
-        // release all
-        for (SQLiteDatabase d : dbMap.keySet()) {
-            int num = dbMap.get(d);
-            for (int i = 0; i < num; i++) {
-                mTestPool.release(d);
-            }
-        }
-        assertEquals(MAX_CONN, mTestPool.getFreePoolSize());
-        assertEquals(MAX_CONN, mTestPool.getSize());
-        assertEquals(MAX_CONN, mTestPool.getMaxPoolSize());
-    }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
index f6b1d04..9ccc6e8 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteCursorTest.java
@@ -21,8 +21,6 @@
 import android.database.Cursor;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 
 import java.io.File;
 import java.util.HashSet;
@@ -54,52 +52,8 @@
         super.tearDown();
     }
 
-    @SmallTest
-    public void testQueryObjReassignment() {
-        mDatabase.enableWriteAheadLogging();
-        // have a few connections in the database connection pool
-        DatabaseConnectionPool pool = mDatabase.mConnectionPool;
-        pool.setMaxPoolSize(5);
-        SQLiteCursor cursor =
-                (SQLiteCursor) mDatabase.rawQuery("select * from " + TABLE_NAME, null);
-        assertNotNull(cursor);
-        // it should use a pooled database connection
-        SQLiteDatabase db = cursor.getDatabase();
-        assertTrue(db.mConnectionNum > 0);
-        assertFalse(mDatabase.equals(db));
-        assertEquals(mDatabase, db.mParentConnObj);
-        assertTrue(pool.getConnectionList().contains(db));
-        assertTrue(db.isOpen());
-        // do a requery. cursor should continue to use the above pooled connection
-        cursor.requery();
-        SQLiteDatabase dbAgain = cursor.getDatabase();
-        assertEquals(db, dbAgain);
-        // disable WAL so that the pooled connection held by the above cursor is closed
-        mDatabase.disableWriteAheadLogging();
-        assertFalse(db.isOpen());
-        assertNull(mDatabase.mConnectionPool);
-        // requery - which should make the cursor use mDatabase connection since the pooled
-        // connection is no longer available
-        cursor.requery();
-        SQLiteDatabase db1 = cursor.getDatabase();
-        assertTrue(db1.mConnectionNum == 0);
-        assertEquals(mDatabase, db1);
-        assertNull(mDatabase.mConnectionPool);
-        assertTrue(db1.isOpen());
-        assertFalse(mDatabase.equals(db));
-        // enable WAL and requery - this time a pooled connection should be used
-        mDatabase.enableWriteAheadLogging();
-        cursor.requery();
-        db = cursor.getDatabase();
-        assertTrue(db.mConnectionNum > 0);
-        assertFalse(mDatabase.equals(db));
-        assertEquals(mDatabase, db.mParentConnObj);
-        assertTrue(mDatabase.mConnectionPool.getConnectionList().contains(db));
-        assertTrue(db.isOpen());
-    }
-
     /**
-     * this test could take a while to execute. so, designate it as LargetTest
+     * this test could take a while to execute. so, designate it as LargeTest
      */
     @LargeTest
     public void testFillWindow() {
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
deleted file mode 100644
index 5ef8d11..0000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ /dev/null
@@ -1,971 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DatabaseErrorHandler;
-import android.database.DatabaseUtils;
-import android.database.DefaultDatabaseErrorHandler;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteDatabase.CursorFactory;
-import android.database.sqlite.SQLiteStatement;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
-import android.util.Log;
-import android.util.Pair;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-public class SQLiteDatabaseTest extends AndroidTestCase {
-    private static final String TAG = "DatabaseGeneralTest";
-    private static final String TEST_TABLE = "test";
-    private static final int CURRENT_DATABASE_VERSION = 42;
-    private SQLiteDatabase mDatabase;
-    private File mDatabaseFile;
-    private static final int INSERT = 1;
-    private static final int UPDATE = 2;
-    private static final int DELETE = 3;
-    private static final String DB_NAME = "database_test.db";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        dbSetUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        dbTeardown();
-        super.tearDown();
-    }
-
-    private void dbTeardown() throws Exception {
-        mDatabase.close();
-        mDatabaseFile.delete();
-    }
-
-    private void dbSetUp() throws Exception {
-        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
-        mDatabaseFile = new File(dbDir, DB_NAME);
-        if (mDatabaseFile.exists()) {
-            mDatabaseFile.delete();
-        }
-        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null, null);
-        assertNotNull(mDatabase);
-        mDatabase.setVersion(CURRENT_DATABASE_VERSION);
-    }
-
-    @SmallTest
-    public void testEnableWriteAheadLogging() {
-        mDatabase.disableWriteAheadLogging();
-        assertNull(mDatabase.mConnectionPool);
-        mDatabase.enableWriteAheadLogging();
-        DatabaseConnectionPool pool = mDatabase.mConnectionPool;
-        assertNotNull(pool);
-        // make the same call again and make sure the pool already setup is not re-created
-        mDatabase.enableWriteAheadLogging();
-        assertEquals(pool, mDatabase.mConnectionPool);
-    }
-
-    @SmallTest
-    public void testDisableWriteAheadLogging() {
-        mDatabase.execSQL("create table test (i int);");
-        mDatabase.enableWriteAheadLogging();
-        assertNotNull(mDatabase.mConnectionPool);
-        // get a pooled database connection
-        SQLiteDatabase db = mDatabase.getDbConnection("select * from test");
-        assertNotNull(db);
-        assertFalse(mDatabase.equals(db));
-        assertTrue(db.isOpen());
-        // disable WAL - which should close connection pool and all pooled connections
-        mDatabase.disableWriteAheadLogging();
-        assertNull(mDatabase.mConnectionPool);
-        assertFalse(db.isOpen());
-    }
-
-    @SmallTest
-    public void testCursorsWithClosedDbConnAfterDisableWriteAheadLogging() {
-        mDatabase.disableWriteAheadLogging();
-        mDatabase.beginTransactionNonExclusive();
-        mDatabase.execSQL("create table test (i int);");
-        mDatabase.execSQL("insert into test values(1);");
-        mDatabase.setTransactionSuccessful();
-        mDatabase.endTransaction();
-        mDatabase.enableWriteAheadLogging();
-        assertNotNull(mDatabase.mConnectionPool);
-        assertEquals(0, mDatabase.mConnectionPool.getSize());
-        assertEquals(0, mDatabase.mConnectionPool.getFreePoolSize());
-        // get a cursor which should use pooled database connection
-        Cursor c = mDatabase.rawQuery("select * from test", null);
-        assertEquals(1, c.getCount());
-        assertEquals(1, mDatabase.mConnectionPool.getSize());
-        assertEquals(1, mDatabase.mConnectionPool.getFreePoolSize());
-        SQLiteDatabase db = mDatabase.mConnectionPool.getConnectionList().get(0);
-        assertTrue(mDatabase.mConnectionPool.isDatabaseObjFree(db));
-        // disable WAL - which should close connection pool and all pooled connections
-        mDatabase.disableWriteAheadLogging();
-        assertNull(mDatabase.mConnectionPool);
-        assertFalse(db.isOpen());
-        // cursor data should still be accessible because it is fetching data from CursorWindow
-        c.moveToNext();
-        assertEquals(1, c.getInt(0));
-        c.requery();
-        assertEquals(1, c.getCount());
-        c.moveToNext();
-        assertEquals(1, c.getInt(0));
-        c.close();
-    }
-
-    /**
-     * a transaction should be started before a standalone-update/insert/delete statement
-     */
-    @SmallTest
-    public void testStartXactBeforeUpdateSql() throws InterruptedException {
-        runTestForStartXactBeforeUpdateSql(INSERT);
-        runTestForStartXactBeforeUpdateSql(UPDATE);
-        runTestForStartXactBeforeUpdateSql(DELETE);
-    }
-    private void runTestForStartXactBeforeUpdateSql(int stmtType) throws InterruptedException {
-        createTableAndClearCache();
-
-        ContentValues values = new ContentValues();
-        // make some changes to data in TEST_TABLE
-        for (int i = 0; i < 5; i++) {
-            values.put("i", i);
-            values.put("j", "i" + System.currentTimeMillis());
-            mDatabase.insert(TEST_TABLE, null, values);
-            switch (stmtType) {
-                case UPDATE:
-                    values.put("j", "u" + System.currentTimeMillis());
-                    mDatabase.update(TEST_TABLE, values, "i = " + i, null);
-                    break;
-                case DELETE:
-                    mDatabase.delete(TEST_TABLE, "i = 1", null);
-                    break;
-            }
-        }
-        // do a query. even though query uses a different database connection,
-        // it should still see the above changes to data because the above standalone
-        // insert/update/deletes are done in transactions automatically.
-        String sql = "select count(*) from " + TEST_TABLE;
-        SQLiteStatement stmt = mDatabase.compileStatement(sql);
-        final int expectedValue = (stmtType == DELETE) ? 4 : 5;
-        assertEquals(expectedValue, stmt.simpleQueryForLong());
-        stmt.close();
-        Cursor c = mDatabase.rawQuery(sql, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(expectedValue, c.getLong(0));
-        c.close();
-
-        // do 5 more changes in a transaction but do a query before and after the commit
-        mDatabase.beginTransaction();
-        for (int i = 10; i < 15; i++) {
-            values.put("i", i);
-            values.put("j", "i" + System.currentTimeMillis());
-            mDatabase.insert(TEST_TABLE, null, values);
-            switch (stmtType) {
-                case UPDATE:
-                    values.put("j", "u" + System.currentTimeMillis());
-                    mDatabase.update(TEST_TABLE, values, "i = " + i, null);
-                    break;
-                case DELETE:
-                    mDatabase.delete(TEST_TABLE, "i = 1", null);
-                    break;
-            }
-        }
-        mDatabase.setTransactionSuccessful();
-        // do a query before commit - should still have 5 rows
-        // this query should run in a different thread to force it to use a different database
-        // connection
-        Thread t = new Thread() {
-            @Override public void run() {
-                String sql = "select count(*) from " + TEST_TABLE;
-                SQLiteStatement stmt = getDb().compileStatement(sql);
-                assertEquals(expectedValue, stmt.simpleQueryForLong());
-                stmt.close();
-                Cursor c = getDb().rawQuery(sql, null);
-                assertEquals(1, c.getCount());
-                c.moveToFirst();
-                assertEquals(expectedValue, c.getLong(0));
-                c.close();
-            }
-        };
-        t.start();
-        // wait until the above thread is done
-        t.join();
-        // commit and then query. should see changes from the transaction
-        mDatabase.endTransaction();
-        stmt = mDatabase.compileStatement(sql);
-        final int expectedValue2 = (stmtType == DELETE) ? 9 : 10;
-        assertEquals(expectedValue2, stmt.simpleQueryForLong());
-        stmt.close();
-        c = mDatabase.rawQuery(sql, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(expectedValue2, c.getLong(0));
-        c.close();
-    }
-    private synchronized SQLiteDatabase getDb() {
-        return mDatabase;
-    }
-
-    /**
-     * Test to ensure that readers are able to read the database data (old versions)
-     * EVEN WHEN the writer is in a transaction on the same database.
-     *<p>
-     * This test starts 1 Writer and 2 Readers and sets up connection pool for readers
-     * by calling the method {@link SQLiteDatabase#enableWriteAheadLogging()}.
-     * <p>
-     * Writer does the following in a tight loop
-     * <pre>
-     *     begin transaction
-     *     insert into table_1
-     *     insert into table_2
-     *     commit
-     * </pre>
-     * <p>
-     * As long a the writer is alive, Readers do the following in a tight loop at the same time
-     * <pre>
-     *     Reader_K does "select count(*) from table_K"  where K = 1 or 2
-     * </pre>
-     * <p>
-     * The test is run for TIME_TO_RUN_WAL_TEST_FOR sec.
-     * <p>
-     * The test is repeated for different connection-pool-sizes (1..3)
-     * <p>
-     * And at the end of of each test, the following statistics are printed
-     * <ul>
-     *    <li>connection-pool-size</li>
-     *    <li>number-of-transactions by writer</li>
-     *    <li>number of reads by reader_K while the writer is IN or NOT-IN xaction</li>
-     * </ul>
-     */
-    @LargeTest
-    @Suppress // run this test only if you need to collect the numbers from this test
-    public void testConcurrencyEffectsOfConnPool() throws Exception {
-        // run the test with sqlite WAL enable
-        runConnectionPoolTest(true);
-
-        // run the same test WITHOUT sqlite WAL enabled
-        runConnectionPoolTest(false);
-    }
-
-    private void runConnectionPoolTest(boolean useWal) throws Exception {
-        int M = 3;
-        StringBuilder[] buff = new StringBuilder[M];
-        for (int i = 0; i < M; i++) {
-            if (useWal) {
-                // set up connection pool
-                mDatabase.enableWriteAheadLogging();
-                mDatabase.mConnectionPool.setMaxPoolSize(i + 1);
-            } else {
-                mDatabase.disableWriteAheadLogging();
-            }
-            mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
-            mDatabase.execSQL("CREATE TABLE t2 (i int, j int);");
-            mDatabase.beginTransaction();
-            for (int k = 0; k < 5; k++) {
-                mDatabase.execSQL("insert into t1 values(?,?);", new String[] {k+"", k+""});
-                mDatabase.execSQL("insert into t2 values(?,?);", new String[] {k+"", k+""});
-            }
-            mDatabase.setTransactionSuccessful();
-            mDatabase.endTransaction();
-
-            // start a writer
-            Writer w = new Writer(mDatabase);
-
-            // initialize an array of counters to be passed to the readers
-            Reader r1 = new Reader(mDatabase, "t1", w, 0);
-            Reader r2 = new Reader(mDatabase, "t2", w, 1);
-            w.start();
-            r1.start();
-            r2.start();
-
-            // wait for all threads to die
-            w.join();
-            r1.join();
-            r2.join();
-
-            // print the stats
-            int[][] counts = getCounts();
-            buff[i] = new StringBuilder();
-            buff[i].append("connpool-size = ");
-            buff[i].append(i + 1);
-            buff[i].append(", num xacts by writer = ");
-            buff[i].append(getNumXacts());
-            buff[i].append(", num-reads-in-xact/NOT-in-xact by reader1 = ");
-            buff[i].append(counts[0][1] + "/" + counts[0][0]);
-            buff[i].append(", by reader2 = ");
-            buff[i].append(counts[1][1] + "/" + counts[1][0]);
-
-            Log.i(TAG, "done testing for conn-pool-size of " + (i+1));
-
-            dbTeardown();
-            dbSetUp();
-        }
-        Log.i(TAG, "duration of test " + TIME_TO_RUN_WAL_TEST_FOR + " sec");
-        for (int i = 0; i < M; i++) {
-            Log.i(TAG, buff[i].toString());
-        }
-    }
-
-    private boolean inXact = false;
-    private int numXacts;
-    private static final int TIME_TO_RUN_WAL_TEST_FOR = 15; // num sec this test should run
-    private int[][] counts = new int[2][2];
-
-    private synchronized boolean inXact() {
-        return inXact;
-    }
-
-    private synchronized void setInXactFlag(boolean flag) {
-        inXact = flag;
-    }
-
-    private synchronized void setCounts(int readerNum, int[] numReads) {
-        counts[readerNum][0] = numReads[0];
-        counts[readerNum][1] = numReads[1];
-    }
-
-    private synchronized int[][] getCounts() {
-        return counts;
-    }
-
-    private synchronized void setNumXacts(int num) {
-        numXacts = num;
-    }
-
-    private synchronized int getNumXacts() {
-        return numXacts;
-    }
-
-    private class Writer extends Thread {
-        private SQLiteDatabase db = null;
-        public Writer(SQLiteDatabase db) {
-            this.db = db;
-        }
-        @Override public void run() {
-            // in a loop, for N sec, do the following
-            //    BEGIN transaction
-            //    insert into table t1, t2
-            //    Commit
-            long now = System.currentTimeMillis();
-            int k;
-            for (k = 0;(System.currentTimeMillis() - now) / 1000 < TIME_TO_RUN_WAL_TEST_FOR; k++) {
-                db.beginTransactionNonExclusive();
-                setInXactFlag(true);
-                for (int i = 0; i < 10; i++) {
-                    db.execSQL("insert into t1 values(?,?);", new String[] {i+"", i+""});
-                    db.execSQL("insert into t2 values(?,?);", new String[] {i+"", i+""});
-                }
-                db.setTransactionSuccessful();
-                setInXactFlag(false);
-                db.endTransaction();
-            }
-            setNumXacts(k);
-        }
-    }
-
-    private class Reader extends Thread {
-        private SQLiteDatabase db = null;
-        private String table = null;
-        private Writer w = null;
-        private int readerNum;
-        private int[] numReads = new int[2];
-        public Reader(SQLiteDatabase db, String table, Writer w, int readerNum) {
-            this.db = db;
-            this.table = table;
-            this.w = w;
-            this.readerNum = readerNum;
-        }
-        @Override public void run() {
-            // while the write is alive, in a loop do the query on a table
-            while (w.isAlive()) {
-                for (int i = 0; i < 10; i++) {
-                    DatabaseUtils.longForQuery(db, "select count(*) from " + this.table, null);
-                    // update count of reads
-                    numReads[inXact() ? 1 : 0] += 1;
-                }
-            }
-            setCounts(readerNum, numReads);
-        }
-    }
-
-    public static class ClassToTestSqlCompilationAndCaching extends SQLiteProgram {
-        private ClassToTestSqlCompilationAndCaching(SQLiteDatabase db, String sql) {
-            super(db, sql);
-        }
-        public static ClassToTestSqlCompilationAndCaching create(SQLiteDatabase db, String sql) {
-            db.lock();
-            try {
-                return new ClassToTestSqlCompilationAndCaching(db, sql);
-            } finally {
-                db.unlock();
-            }
-        }
-    }
-
-    @SmallTest
-    public void testLruCachingOfSqliteCompiledSqlObjs() {
-        createTableAndClearCache();
-        // set cache size
-        int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
-        mDatabase.setMaxSqlCacheSize(N);
-
-        // do N+1 queries - and when the 0th entry is removed from LRU cache due to the
-        // insertion of (N+1)th entry, make sure 0th entry is closed
-        ArrayList<Integer> stmtObjs = new ArrayList<Integer>();
-        ArrayList<String> sqlStrings = new ArrayList<String>();
-        int stmt0 = 0;
-        for (int i = 0; i < N+1; i++) {
-            String s = "insert into test values(" + i + ",?);";
-            sqlStrings.add(s);
-            ClassToTestSqlCompilationAndCaching c =
-                    ClassToTestSqlCompilationAndCaching.create(mDatabase, s);
-            int n = c.getSqlStatementId();
-            stmtObjs.add(i, n);
-            if (i == 0) {
-                // save the statementId of this obj. we want to make sure it is thrown out of
-                // the cache at the end of this test.
-                stmt0 = n;
-            }
-            c.close();
-        }
-        // is 0'th entry out of the cache? it should be in the list of statementIds
-        // corresponding to the pre-compiled sql statements to be finalized.
-        assertTrue(mDatabase.getQueuedUpStmtList().contains(stmt0));
-        for (int i = 1; i < N+1; i++) {
-            SQLiteCompiledSql compSql = mDatabase.getCompiledStatementForSql(sqlStrings.get(i));
-            assertNotNull(compSql);
-            assertTrue(stmtObjs.contains(compSql.nStatement));
-        }
-    }
-
-    @MediumTest
-    public void testDbCloseReleasingAllCachedSql() {
-        mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
-                "num1 INTEGER, num2 INTEGER, image BLOB);");
-        final String statement = "DELETE FROM test WHERE _id=?;";
-        SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement);
-        statementDoNotClose.bindLong(1, 1);
-        /* do not close statementDoNotClose object.
-         * That should leave it in SQLiteDatabase.mPrograms.
-         * mDatabase.close() in tearDown() should release it.
-         */
-    }
-
-    private void createTableAndClearCache() {
-        mDatabase.disableWriteAheadLogging();
-        mDatabase.execSQL("DROP TABLE IF EXISTS " + TEST_TABLE);
-        mDatabase.execSQL("CREATE TABLE " + TEST_TABLE + " (i int, j int);");
-        mDatabase.enableWriteAheadLogging();
-        mDatabase.lock();
-        // flush the above statement from cache and close all the pending statements to be released
-        mDatabase.deallocCachedSqlStatements();
-        mDatabase.closePendingStatements();
-        mDatabase.unlock();
-        assertEquals(0, mDatabase.getQueuedUpStmtList().size());
-    }
-
-    /**
-     * test to make sure the statement finalizations are not done right away but
-     * piggy-backed onto the next sql statement execution on the same database.
-     */
-    @SmallTest
-    public void testStatementClose() {
-        createTableAndClearCache();
-        // fill up statement cache in mDatabase
-        int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
-        mDatabase.setMaxSqlCacheSize(N);
-        SQLiteStatement stmt;
-        int stmt0Id = 0;
-        for (int i = 0; i < N; i ++) {
-            ClassToTestSqlCompilationAndCaching c =
-                    ClassToTestSqlCompilationAndCaching.create(mDatabase,
-                            "insert into test values(" + i + ", ?);");
-            // keep track of 0th entry
-            if (i == 0) {
-                stmt0Id = c.getSqlStatementId();
-            }
-            c.close();
-        }
-
-        // add one more to the cache - and the above 'stmt0Id' should fall out of cache
-        ClassToTestSqlCompilationAndCaching stmt1 =
-                ClassToTestSqlCompilationAndCaching.create(mDatabase,
-                        "insert into test values(100, ?);");
-        stmt1.close();
-
-        // the above close() should have queuedUp the statement for finalization
-        ArrayList<Integer> statementIds = mDatabase.getQueuedUpStmtList();
-        assertTrue(statementIds.contains(stmt0Id));
-
-        // execute something to see if this statement gets finalized
-        mDatabase.execSQL("delete from test where i = 10;");
-        statementIds = mDatabase.getQueuedUpStmtList();
-        assertFalse(statementIds.contains(stmt0Id));
-    }
-
-    /**
-     * same as above - except that the statement to be finalized is from Thread # 1.
-     * and it is eventually finalized in Thread # 2 when it executes a SQL statement.
-     * @throws InterruptedException
-     */
-    @LargeTest
-    public void testStatementCloseDiffThread() throws InterruptedException {
-        createTableAndClearCache();
-        final int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
-        mDatabase.setMaxSqlCacheSize(N);
-        // fill up statement cache in mDatabase in a thread
-        Thread t1 = new Thread() {
-            @Override public void run() {
-                SQLiteStatement stmt;
-                for (int i = 0; i < N; i++) {
-                    ClassToTestSqlCompilationAndCaching c =
-                        ClassToTestSqlCompilationAndCaching.create(getDb(),
-                                "insert into test values(" + i + ", ?);");
-                    // keep track of 0th entry
-                    if (i == 0) {
-                        stmt0Id = c.getSqlStatementId();
-                    }
-                    c.close();
-                }
-            }
-        };
-        t1.start();
-        // wait for the thread to finish
-        t1.join();
-        // mDatabase shouldn't have any statements to be released
-        assertEquals(0, mDatabase.getQueuedUpStmtList().size());
-
-        // add one more to the cache - and the above 'stmt0Id' should fall out of cache
-        // just for the heck of it, do it in a separate thread
-        Thread t2 = new Thread() {
-            @Override public void run() {
-                ClassToTestSqlCompilationAndCaching stmt1 =
-                    ClassToTestSqlCompilationAndCaching.create(getDb(),
-                            "insert into test values(100, ?);");
-                stmt1.bindLong(1, 1);
-                stmt1.close();
-            }
-        };
-        t2.start();
-        t2.join();
-
-        // close() in the above thread should have queuedUp the stmt0Id for finalization
-        ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
-        assertTrue(statementIds.contains(getStmt0Id()));
-        assertEquals(1, statementIds.size());
-
-        // execute something to see if this statement gets finalized
-        // again do it in a separate thread
-        Thread t3 = new Thread() {
-            @Override public void run() {
-                getDb().execSQL("delete from test where i = 10;");
-            }
-        };
-        t3.start();
-        t3.join();
-
-        // is the statement finalized?
-        statementIds = getDb().getQueuedUpStmtList();
-        assertFalse(statementIds.contains(getStmt0Id()));
-    }
-
-    private volatile int stmt0Id = 0;
-    private synchronized int getStmt0Id() {
-        return this.stmt0Id;
-    }
-
-    /**
-     * same as above - except that the queue of statements to be finalized are finalized
-     * by database close() operation.
-     */
-    @LargeTest
-    public void testStatementCloseByDbClose() throws InterruptedException {
-        createTableAndClearCache();
-        // fill up statement cache in mDatabase in a thread
-        Thread t1 = new Thread() {
-            @Override public void run() {
-                int N = SQLiteDatabase.MAX_SQL_CACHE_SIZE;
-                getDb().setMaxSqlCacheSize(N);
-                SQLiteStatement stmt;
-                for (int i = 0; i < N; i ++) {
-                    ClassToTestSqlCompilationAndCaching c =
-                            ClassToTestSqlCompilationAndCaching.create(getDb(),
-                                    "insert into test values(" + i + ", ?);");
-                    // keep track of 0th entry
-                    if (i == 0) {
-                        stmt0Id = c.getSqlStatementId();
-                    }
-                    c.close();
-                }
-            }
-        };
-        t1.start();
-        // wait for the thread to finish
-        t1.join();
-
-        // add one more to the cache - and the above 'stmt0Id' should fall out of cache
-        // just for the heck of it, do it in a separate thread
-        Thread t2 = new Thread() {
-            @Override public void run() {
-                ClassToTestSqlCompilationAndCaching stmt1 =
-                        ClassToTestSqlCompilationAndCaching.create(getDb(),
-                                "insert into test values(100, ?);");
-                stmt1.bindLong(1, 1);
-                stmt1.close();
-            }
-        };
-        t2.start();
-        t2.join();
-
-        // close() in the above thread should have queuedUp the statement for finalization
-        ArrayList<Integer> statementIds = getDb().getQueuedUpStmtList();
-        assertTrue(getStmt0Id() > 0);
-        assertTrue(statementIds.contains(stmt0Id));
-        assertEquals(1, statementIds.size());
-
-        // close the database. everything from mClosedStatementIds in mDatabase
-        // should be finalized and cleared from the list
-        // again do it in a separate thread
-        Thread t3 = new Thread() {
-            @Override public void run() {
-                getDb().close();
-            }
-        };
-        t3.start();
-        t3.join();
-
-        // check mClosedStatementIds in mDatabase. it should be empty
-        statementIds = getDb().getQueuedUpStmtList();
-        assertEquals(0, statementIds.size());
-    }
-
-    /**
-     * This test tests usage execSQL() to begin transaction works in the following way
-     *   Thread #1 does
-     *       execSQL("begin transaction");
-     *       insert()
-     *   Thread # 2
-     *       query()
-     *   Thread#1 ("end transaction")
-     * Thread # 2 query will execute - because java layer will not have locked the SQLiteDatabase
-     * object and sqlite will consider this query to be part of the transaction.
-     *
-     * but if thread # 1 uses beginTransaction() instead of execSQL() to start transaction,
-     * then Thread # 2's query will have been blocked by java layer
-     * until Thread#1 ends transaction.
-     *
-     * @throws InterruptedException
-     */
-    @SmallTest
-    public void testExecSqlToStartAndEndTransaction() throws InterruptedException {
-        runExecSqlToStartAndEndTransaction("END");
-        // same as above, instead now do "COMMIT" or "ROLLBACK" instead of "END" transaction
-        runExecSqlToStartAndEndTransaction("COMMIT");
-        runExecSqlToStartAndEndTransaction("ROLLBACK");
-    }
-    private void runExecSqlToStartAndEndTransaction(String str) throws InterruptedException {
-        createTableAndClearCache();
-        // disable WAL just so queries and updates use the same database connection
-        mDatabase.disableWriteAheadLogging();
-        mDatabase.execSQL("BEGIN transaction");
-        // even though mDatabase.beginTransaction() is not called to start transaction,
-        // mDatabase connection should now be in transaction as a result of
-        // mDatabase.execSQL("BEGIN transaction")
-        // but mDatabase.mLock should not be held by any thread
-        assertTrue(mDatabase.inTransaction());
-        assertFalse(mDatabase.isDbLockedByCurrentThread());
-        assertFalse(mDatabase.isDbLockedByOtherThreads());
-        assertTrue(mDatabase.amIInTransaction());
-        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
-        assertTrue(mDatabase.inTransaction());
-        assertFalse(mDatabase.isDbLockedByCurrentThread());
-        assertFalse(mDatabase.isDbLockedByOtherThreads());
-        assertTrue(mDatabase.amIInTransaction());
-        Thread t = new Thread() {
-            @Override public void run() {
-                assertTrue(mDatabase.amIInTransaction());
-                assertEquals(999, DatabaseUtils.longForQuery(getDb(),
-                        "select j from " + TEST_TABLE + " WHERE i = 10", null));
-                assertTrue(getDb().inTransaction());
-                assertFalse(getDb().isDbLockedByCurrentThread());
-                assertFalse(getDb().isDbLockedByOtherThreads());
-                assertTrue(mDatabase.amIInTransaction());
-            }
-        };
-        t.start();
-        t.join();
-        assertTrue(mDatabase.amIInTransaction());
-        assertTrue(mDatabase.inTransaction());
-        assertFalse(mDatabase.isDbLockedByCurrentThread());
-        assertFalse(mDatabase.isDbLockedByOtherThreads());
-        mDatabase.execSQL(str);
-        assertFalse(mDatabase.amIInTransaction());
-        assertFalse(mDatabase.inTransaction());
-        assertFalse(mDatabase.isDbLockedByCurrentThread());
-        assertFalse(mDatabase.isDbLockedByOtherThreads());
-    }
-
-    /**
-     * test the following
-     * http://b/issue?id=2871037
-     *          Cursor cursor = db.query(...);
-     *          // with WAL enabled, the above uses a pooled database connection
-     *          db.beginTransaction()
-     *          try {
-     *            db.insert(......);
-     *            cursor.requery();
-     *            // since the cursor uses pooled database connection, the above requery
-     *            // will not return the results that were inserted above since the insert is
-     *            // done using main database connection AND the transaction is not committed yet.
-     *            // fix is to make the above cursor use the main database connection - and NOT
-     *            // the pooled database connection
-     *            db.setTransactionSuccessful()
-     *          } finally {
-     *            db.endTransaction()
-     *          }
-     *
-     * @throws InterruptedException
-     */
-    @SmallTest
-    public void testTransactionAndWalInterplay1() throws InterruptedException {
-        createTableAndClearCache();
-        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
-        String sql = "select * from " + TEST_TABLE;
-        Cursor c = mDatabase.rawQuery(sql, null);
-        // should have 1 row in the table
-        assertEquals(1, c.getCount());
-        mDatabase.beginTransactionNonExclusive();
-        try {
-            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
-            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
-                    "select count(*) from " + TEST_TABLE, null));
-            // requery on the previously opened cursor
-            // cursor should now use the main database connection and see 2 rows
-            c.requery();
-            assertEquals(2, c.getCount());
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            mDatabase.endTransaction();
-        }
-        c.close();
-
-        // do the same test but now do the requery in a separate thread.
-        createTableAndClearCache();
-        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
-        final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
-        // should have 1 row in the table
-        assertEquals(1, c1.getCount());
-        mDatabase.beginTransactionNonExclusive();
-        try {
-            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
-            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
-                    "select count(*) from " + TEST_TABLE, null));
-            // query in a different thread. that causes the cursor to use a pooled connection
-            // and since this thread hasn't committed its changes, the cursor should still see only
-            // 1 row
-            Thread t = new Thread() {
-                @Override public void run() {
-                    c1.requery();
-                    assertEquals(1, c1.getCount());
-                }
-            };
-            t.start();
-            t.join();
-            // should be 2 rows now - including the the row inserted above
-            mDatabase.setTransactionSuccessful();
-        } finally {
-            mDatabase.endTransaction();
-        }
-        c1.close();
-    }
-
-    /**
-     * This test is same as {@link #testTransactionAndWalInterplay1()} except the following:
-     * instead of mDatabase.beginTransactionNonExclusive(), use execSQL("BEGIN transaction")
-     * and instead of mDatabase.endTransaction(), use execSQL("END");
-     */
-    @SmallTest
-    public void testTransactionAndWalInterplay2() throws InterruptedException {
-        createTableAndClearCache();
-        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
-        String sql = "select * from " + TEST_TABLE;
-        Cursor c = mDatabase.rawQuery(sql, null);
-        // should have 1 row in the table
-        assertEquals(1, c.getCount());
-        mDatabase.execSQL("BEGIN transaction");
-        try {
-            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
-            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
-                    "select count(*) from " + TEST_TABLE, null));
-            // requery on the previously opened cursor
-            // cursor should now use the main database connection and see 2 rows
-            c.requery();
-            assertEquals(2, c.getCount());
-        } finally {
-            mDatabase.execSQL("commit;");
-        }
-        c.close();
-
-        // do the same test but now do the requery in a separate thread.
-        createTableAndClearCache();
-        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
-        final Cursor c1 = mDatabase.rawQuery("select count(*) from " + TEST_TABLE, null);
-        // should have 1 row in the table
-        assertEquals(1, c1.getCount());
-        mDatabase.execSQL("BEGIN transaction");
-        try {
-            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
-            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
-                    "select count(*) from " + TEST_TABLE, null));
-            // query in a different thread. but since the transaction is started using
-            // execSQ() instead of beginTransaction(), cursor's query is considered part of
-            // the same transaction - and hence it should see the above inserted row
-            Thread t = new Thread() {
-                @Override public void run() {
-                    c1.requery();
-                    assertEquals(1, c1.getCount());
-                }
-            };
-            t.start();
-            t.join();
-            // should be 2 rows now - including the the row inserted above
-        } finally {
-            mDatabase.execSQL("commit");
-        }
-        c1.close();
-    }
-
-    /**
-     * This test is same as {@link #testTransactionAndWalInterplay2()} except the following:
-     * instead of committing the data, do rollback and make sure the data seen by the query
-     * within the transaction is now gone.
-     */
-    @SmallTest
-    public void testTransactionAndWalInterplay3() {
-        createTableAndClearCache();
-        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
-        String sql = "select * from " + TEST_TABLE;
-        Cursor c = mDatabase.rawQuery(sql, null);
-        // should have 1 row in the table
-        assertEquals(1, c.getCount());
-        mDatabase.execSQL("BEGIN transaction");
-        try {
-            mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(100, 9909);");
-            assertEquals(2, DatabaseUtils.longForQuery(mDatabase,
-                    "select count(*) from " + TEST_TABLE, null));
-            // requery on the previously opened cursor
-            // cursor should now use the main database connection and see 2 rows
-            c.requery();
-            assertEquals(2, c.getCount());
-        } finally {
-            // rollback the change
-            mDatabase.execSQL("rollback;");
-        }
-        // since the change is rolled back, do the same query again and should now find only 1 row
-        c.requery();
-        assertEquals(1, c.getCount());
-        assertEquals(1, DatabaseUtils.longForQuery(mDatabase,
-                "select count(*) from " + TEST_TABLE, null));
-        c.close();
-    }
-
-    @SmallTest
-    public void testAttachDb() {
-        String newDb = "/sdcard/mydata.db";
-        File f = new File(newDb);
-        if (f.exists()) {
-            f.delete();
-        }
-        assertFalse(f.exists());
-        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(newDb, null);
-        db.execSQL("create table test1 (i int);");
-        db.execSQL("insert into test1 values(1);");
-        db.execSQL("insert into test1 values(11);");
-        Cursor c = null;
-        try {
-            c = db.rawQuery("select * from test1", null);
-            int count = c.getCount();
-            Log.i(TAG, "count: " + count);
-            assertEquals(2, count);
-        } finally {
-            c.close();
-            db.close();
-            c = null;
-        }
-
-        mDatabase.execSQL("attach database ? as newDb" , new String[]{newDb});
-        Cursor c1 = null;
-        try {
-            c1 = mDatabase.rawQuery("select * from newDb.test1", null);
-            assertEquals(2, c1.getCount());
-        } catch (Exception e) {
-            fail("unexpected exception: " + e.getMessage());
-        } finally {
-            if (c1 != null) {
-                c1.close();
-            }
-        }
-        List<Pair<String, String>> dbs = mDatabase.getAttachedDbs();
-        for (Pair<String, String> p: dbs) {
-            Log.i(TAG, "attached dbs: " + p.first + " : " + p.second);
-        }
-        assertEquals(2, dbs.size());
-     }
-
-    /**
-     * http://b/issue?id=2943028
-     * SQLiteOpenHelper maintains a Singleton even if it is in bad state.
-     */
-    @SmallTest
-    public void testCloseAndReopen() {
-        mDatabase.close();
-        TestOpenHelper helper = new TestOpenHelper(getContext(), DB_NAME, null,
-                CURRENT_DATABASE_VERSION, new DefaultDatabaseErrorHandler());
-        mDatabase = helper.getWritableDatabase();
-        createTableAndClearCache();
-        mDatabase.execSQL("INSERT into " + TEST_TABLE + " values(10, 999);");
-        Cursor c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
-        assertEquals(1, c.getCount());
-        c.close();
-        mDatabase.close();
-        assertFalse(mDatabase.isOpen());
-        mDatabase = helper.getReadableDatabase();
-        assertTrue(mDatabase.isOpen());
-        c = mDatabase.query(TEST_TABLE, new String[]{"i", "j"}, null, null, null, null, null);
-        assertEquals(1, c.getCount());
-        c.close();
-    }
-    private class TestOpenHelper extends SQLiteOpenHelper {
-        public TestOpenHelper(Context context, String name, CursorFactory factory, int version,
-                DatabaseErrorHandler errorHandler) {
-            super(context, name, factory, version, errorHandler);
-        }
-        @Override public void onCreate(SQLiteDatabase db) {}
-        @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
-    }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
deleted file mode 100644
index 955336a..0000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteStatementTest.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.Context;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.io.File;
-import java.util.Random;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class SQLiteStatementTest extends AndroidTestCase {
-    private SQLiteDatabase mDatabase;
-    private File mDatabaseFile;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
-        mDatabaseFile = new File(dbDir, "database_test.db");
-        if (mDatabaseFile.exists()) {
-            mDatabaseFile.delete();
-        }
-        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
-        assertNotNull(mDatabase);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mDatabase.close();
-        mDatabaseFile.delete();
-        super.tearDown();
-    }
-
-    /**
-     * Start 2 threads to repeatedly execute the above SQL statement.
-     * Even though 2 threads are executing the same SQL, they each should get their own copy of
-     * prepared SQL statement id and there SHOULD NOT be an error from sqlite or android.
-     * @throws InterruptedException thrown if the test threads started by this test are interrupted
-     */
-    @LargeTest
-    public void testUseOfSameSqlStatementBy2Threads() throws InterruptedException {
-        mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
-        final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
-        class RunStmtThread extends Thread {
-            @Override public void run() {
-                // do it enough times to make sure there are no corner cases going untested
-                for (int i = 0; i < 1000; i++) {
-                    SQLiteStatement s1 = mDatabase.compileStatement(stmt);
-                    s1.bindLong(1, i);
-                    s1.execute();
-                    s1.close();
-                 }
-            }
-        }
-        RunStmtThread t1 = new RunStmtThread();
-        t1.start();
-        RunStmtThread t2 = new RunStmtThread();
-        t2.start();
-         while (t1.isAlive() || t2.isAlive()) {
-             Thread.sleep(10);
-         }
-     }
-
-    /**
-     * A simple test: start 2 threads to repeatedly execute the same {@link SQLiteStatement}.
-     * The 2 threads take turns to use the {@link SQLiteStatement}; i.e., it is NOT in use
-     * by both the threads at the same time.
-     *
-     * @throws InterruptedException thrown if the test threads started by this test are interrupted
-     */
-    @LargeTest
-    public void testUseOfSameSqliteStatementBy2Threads() throws InterruptedException {
-        mDatabase.execSQL("CREATE TABLE test_pstmt (i INTEGER PRIMARY KEY, j text);");
-        final String stmt = "SELECT * FROM test_pstmt WHERE i = ?";
-        final SQLiteStatement s1 = mDatabase.compileStatement(stmt);
-        class RunStmtThread extends Thread {
-            @Override public void run() {
-                // do it enough times to make sure there are no corner cases going untested
-                for (int i = 0; i < 1000; i++) {
-                    lock();
-                    try {
-                        s1.bindLong(1, i);
-                        s1.execute();
-                    } finally {
-                        unlock();
-                    }
-                    Thread.yield();
-                }
-            }
-        }
-        RunStmtThread t1 = new RunStmtThread();
-        t1.start();
-        RunStmtThread t2 = new RunStmtThread();
-        t2.start();
-        while (t1.isAlive() || t2.isAlive()) {
-            Thread.sleep(10);
-        }
-    }
-    /** Synchronize on this when accessing the SqliteStatemet in the above */
-    private final ReentrantLock mLock = new ReentrantLock(true);
-    private void lock() {
-        mLock.lock();
-    }
-    private void unlock() {
-        mLock.unlock();
-    }
-
-    /**
-     * Tests the following: a {@link SQLiteStatement} object should not refer to a
-     * pre-compiled SQL statement id except in during the period of binding the arguments
-     * and executing the SQL statement.
-     */
-    @LargeTest
-    public void testReferenceToPrecompiledStatementId() {
-        mDatabase.execSQL("create table t (i int, j text);");
-        verifyReferenceToPrecompiledStatementId(false);
-        verifyReferenceToPrecompiledStatementId(true);
-
-        // a small stress test to make sure there are no side effects of
-        // the acquire & release of pre-compiled statement id by SQLiteStatement object.
-        for (int i = 0; i < 100; i++) {
-            verifyReferenceToPrecompiledStatementId(false);
-            verifyReferenceToPrecompiledStatementId(true);
-        }
-    }
-
-    @SuppressWarnings("deprecation")
-    private void verifyReferenceToPrecompiledStatementId(boolean wal) {
-        if (wal) {
-            mDatabase.enableWriteAheadLogging();
-        } else {
-            mDatabase.disableWriteAheadLogging();
-        }
-        // test with INSERT statement - doesn't use connection pool, if WAL is set
-        SQLiteStatement stmt = mDatabase.compileStatement("insert into t values(?,?);");
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-        // sql statement should not be compiled yet
-        assertEquals(0, stmt.nStatement);
-        assertEquals(0, stmt.getSqlStatementId());
-        int colValue = new Random().nextInt();
-        stmt.bindLong(1, colValue);
-        // verify that the sql statement is still not compiled
-        assertEquals(0, stmt.getSqlStatementId());
-        // should still be using the mDatabase connection - verify
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-        stmt.bindString(2, "blah" + colValue);
-        // verify that the sql statement is still not compiled
-        assertEquals(0, stmt.getSqlStatementId());
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-        stmt.executeInsert();
-        // now that the statement is executed, pre-compiled statement should be released
-        assertEquals(0, stmt.nStatement);
-        assertEquals(0, stmt.getSqlStatementId());
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-        stmt.close();
-        // pre-compiled SQL statement should still remain released from this object
-        assertEquals(0, stmt.nStatement);
-        assertEquals(0, stmt.getSqlStatementId());
-        // but the database handle should still be the same
-        assertEquals(mDatabase, stmt.mDatabase);
-
-        // test with a SELECT statement - uses connection pool if WAL is set
-        stmt = mDatabase.compileStatement("select i from t where j=?;");
-        // sql statement should not be compiled yet
-        assertEquals(0, stmt.nStatement);
-        assertEquals(0, stmt.getSqlStatementId());
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-        stmt.bindString(1, "blah" + colValue);
-        // verify that the sql statement is still not compiled
-        assertEquals(0, stmt.nStatement);
-        assertEquals(0, stmt.getSqlStatementId());
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-        // execute the statement
-        Long l = stmt.simpleQueryForLong();
-        assertEquals(colValue, l.intValue());
-        // now that the statement is executed, pre-compiled statement should be released
-        assertEquals(0, stmt.nStatement);
-        assertEquals(0, stmt.getSqlStatementId());
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-        stmt.close();
-        // pre-compiled SQL statement should still remain released from this object
-        assertEquals(0, stmt.nStatement);
-        assertEquals(0, stmt.getSqlStatementId());
-        // but the database handle should still remain attached to the statement
-        assertEquals(mDatabase.mNativeHandle, stmt.nHandle);
-        assertEquals(mDatabase, stmt.mDatabase);
-    }
-}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
deleted file mode 100644
index cd2005d..0000000
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteUnfinalizedExceptionTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.database.sqlite;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabaseTest.ClassToTestSqlCompilationAndCaching;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import java.io.File;
-
-public class SQLiteUnfinalizedExceptionTest extends AndroidTestCase {
-    private SQLiteDatabase mDatabase;
-    private File mDatabaseFile;
-    private static final String TABLE_NAME = "testCursor";
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
-        mDatabaseFile = new File(dbDir, "UnfinalizedExceptionTest.db");
-        if (mDatabaseFile.exists()) {
-            mDatabaseFile.delete();
-        }
-        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
-        assertNotNull(mDatabase);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mDatabase.close();
-        mDatabaseFile.delete();
-        super.tearDown();
-    }
-
-    @SmallTest
-    public void testUnfinalizedExceptionNotExcpected() {
-        mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
-        // the above statement should be in SQLiteDatabase.mPrograms
-        // and should automatically be finalized when database is closed
-        mDatabase.lock();
-        try {
-            mDatabase.closeDatabase();
-        } finally {
-            mDatabase.unlock();
-        }
-    }
-
-    @SmallTest
-    public void testUnfinalizedException() {
-        mDatabase.execSQL("CREATE TABLE " + TABLE_NAME + " (i int, j int);");
-        mDatabase.lock();
-        mDatabase.closePendingStatements(); // clears the above from finalizer queue in mdatabase
-        mDatabase.unlock();
-        ClassToTestSqlCompilationAndCaching.create(mDatabase, "select * from "  + TABLE_NAME);
-        // since the above is NOT closed, closing database should fail
-        mDatabase.lock();
-        try {
-            mDatabase.closeDatabase();
-            fail("exception expected");
-        } catch (SQLiteUnfinalizedObjectsException e) {
-            // expected
-        } finally {
-            mDatabase.unlock();
-        }
-    }
-}