Project: android-database-sqlcipher
/*
 * 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 net.sqlcipher.database; 
 
import net.sqlcipher.CrossProcessCursorWrapper; 
import net.sqlcipher.DatabaseUtils; 
import net.sqlcipher.SQLException; 
import net.sqlcipher.database.SQLiteDebug.DbStats; 
import net.sqlcipher.database.SQLiteDatabaseHook; 
 
import java.io.File; 
import java.io.FileOutputStream; 
import java.io.OutputStream; 
import java.lang.ref.WeakReference; 
import java.text.SimpleDateFormat; 
import java.util.ArrayList; 
import java.util.HashMap; 
import java.util.HashSet; 
import java.util.Iterator; 
import java.util.Locale; 
import java.util.Map; 
import java.util.Random; 
import java.util.Set; 
import java.util.WeakHashMap; 
import java.util.concurrent.locks.ReentrantLock; 
import java.util.regex.Pattern; 
import java.util.zip.ZipInputStream; 
 
import android.content.ContentValues; 
 
import android.content.Context; 
import android.database.Cursor; 
import android.os.Debug; 
import android.os.SystemClock; 
import android.text.TextUtils; 
import android.util.Config; 
import android.util.Log; 
import android.util.Pair; 
 
import com.google.common.collect.Maps; 
 
/**
 * Exposes methods to manage a SQLite database. 
 * <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 
 * and managing a database. 
 * <p> Database names must be unique within an application, not across all 
 * applications. 
 * 
 * <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. 
 */
 
public class SQLiteDatabase extends SQLiteClosable { 
    private static final String TAG = "Database"
    private static final int EVENT_DB_OPERATION = 52000
    private static final int EVENT_DB_CORRUPT = 75004
 
    public int status(int operation, boolean reset){ 
        return native_status(operation, reset); 
    } 
     
    public static void upgradeDatabaseFormatFromVersion1To2(File databaseToMigrate, String password) throws Exception { 
 
        File newDatabasePath = null
        boolean renameDatabase = false
        SQLiteDatabaseHook hook = new SQLiteDatabaseHook(){ 
          public void preKey(SQLiteDatabase database){ 
            database.execSQL("PRAGMA cipher_default_use_hmac = off"); 
          } 
          public void postKey(SQLiteDatabase database){ 
            database.execSQL("PRAGMA cipher_default_use_hmac = on"); 
          } 
        }; 
  
        try { 
            newDatabasePath = File.createTempFile("temp""db", databaseToMigrate.getParentFile()); 
            SQLiteDatabase source = SQLiteDatabase.openOrCreateDatabase(databaseToMigrate, password, null, hook); 
            source.rawExecSQL(String.format("ATTACH DATABASE '%s' as newdb", newDatabasePath.getAbsolutePath())); 
            source.rawExecSQL("SELECT sqlcipher_export('newdb')"); 
            source.rawExecSQL("DETACH DATABASE newdb"); 
            source.close(); 
            renameDatabase = true
        } catch(Exception e){ 
            throw e; 
        } 
        if(renameDatabase){ 
            databaseToMigrate.delete(); 
            newDatabasePath.renameTo(databaseToMigrate); 
        } 
    } 
 
    private static void loadICUData(Context context, File workingDir) { 
 
        try { 
            File icuDir = new File(workingDir, "icu"); 
            if(!icuDir.exists()) icuDir.mkdirs(); 
            File icuDataFile = new File(icuDir, "icudt46l.dat"); 
            if(!icuDataFile.exists()) { 
                ZipInputStream in = new ZipInputStream(context.getAssets().open("icudt46l.zip")); 
                in.getNextEntry(); 
              
                OutputStream out =  new FileOutputStream(icuDataFile); 
                byte[] buf = new byte[1024]; 
                int len; 
                while ((len = in.read(buf)) > 0) { 
                    out.write(buf, 0, len); 
                } 
                in.close(); 
                out.flush(); 
                out.close(); 
            } 
        } 
        catch (Exception e) { 
            Log.e(TAG, "Error copying icu data file" + e.getMessage()); 
        } 
    } 
 
    public static void loadLibs (Context context) { 
        loadLibs(context, context.getFilesDir()); 
    } 
 
    public static void loadLibs (Context context, File workingDir) 
    { 
        System.loadLibrary("stlport_shared"); 
        System.loadLibrary("sqlcipher_android"); 
        System.loadLibrary("database_sqlcipher"); 
 
        boolean systemICUFileExists = new File("/system/usr/icu/icudt46l.dat").exists(); 
 
        String icuRootPath = systemICUFileExists ? "/system/usr" : workingDir.getAbsolutePath(); 
        setICURoot(icuRootPath); 
         
        if(!systemICUFileExists){ 
            loadICUData(context, workingDir); 
        } 
    } 
 
    /**
     * Algorithms used in ON CONFLICT clause 
     * http://www.sqlite.org/lang_conflict.html 
     */
 
    /**
     *  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. 
     */
 
    public static final int CONFLICT_ROLLBACK = 1
 
    /**
     * When a constraint violation occurs,no ROLLBACK is executed 
     * so changes from prior commands within the same transaction 
     * are preserved. This is the default behavior. 
     */
 
    public static final int CONFLICT_ABORT = 2
 
    /**
     * When a constraint violation occurs, the command aborts with a return 
     * code SQLITE_CONSTRAINT. But any changes to the database that 
     * the command made prior to encountering the constraint violation 
     * are preserved and are not backed out. 
     */
 
    public static final int CONFLICT_FAIL = 3
 
    /**
     * When a constraint violation occurs, the one row that contains 
     * the constraint violation is not inserted or changed. 
     * But the command continues executing normally. Other rows before and 
     * after the row that contained the constraint violation continue to be 
     * inserted or updated normally. No error is returned. 
     */
 
    public static final int CONFLICT_IGNORE = 4
 
    /**
     * When a UNIQUE constraint violation occurs, the pre-existing rows that 
     * are causing the constraint violation are removed prior to inserting 
     * or updating the current row. Thus the insert or update always occurs. 
     * The command continues executing normally. No error is returned. 
     * If a NOT NULL constraint violation occurs, the NULL value is replaced 
     * by the default value for that column. If the column has no default 
     * value, then the ABORT algorithm is used. If a CHECK constraint 
     * 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. 
     */
 
    public static final int CONFLICT_REPLACE = 5
 
    /**
     * 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 "}; 
 
    /**
     * Maximum Length Of A LIKE Or GLOB Pattern 
     * The pattern matching algorithm used in the default LIKE and GLOB implementation 
     * of SQLite can exhibit O(N^2) performance (where N is the number of characters in 
     * the pattern) for certain pathological cases. To avoid denial-of-service attacks 
     * the length of the LIKE or GLOB pattern is limited to SQLITE_MAX_LIKE_PATTERN_LENGTH bytes. 
     * The default value of this limit is 50000. A modern workstation can evaluate 
     * even a pathological LIKE or GLOB pattern of 50000 bytes relatively quickly. 
     * The denial of service problem only comes into play when the pattern length gets 
     * into millions of bytes. Nevertheless, since most useful LIKE or GLOB patterns 
     * are at most a few dozen bytes in length, paranoid application developers may 
     * want to reduce this parameter to something in the range of a few hundred 
     * if they know that external users are able to generate arbitrary patterns. 
     */
 
    public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000
 
    /**
     * 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. 
     */
 
    public static final int OPEN_READWRITE = 0x00000000;          // update native code if changing 
 
    /**
     * 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 
 
    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. 
     * 
     * {@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 
     * created with.  If this is set, {@link #setLocale} will do nothing. 
     */
 
    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. 
     */
 
    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; 
 
    /** Synchronize on this when accessing the database */ 
    private final ReentrantLock mLock = new ReentrantLock(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 final Random mRandom = new Random(); 
    private String mLastSqlStatement = null
 
    // 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 */ 
    /* package */ int mNativeHandle = 0
 
    /** Used to make temp table names unique */ 
    /* package */ int mTempTableSequence = 0
 
    /** The path for the database file */ 
    private 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 int mFlags; 
 
    /** The optional factory to use when creating new Cursors */ 
    private CursorFactory mFactory; 
 
    private WeakHashMap<SQLiteClosable, Object> mPrograms; 
 
    /**
     * for each instance of this class, a 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. 
     * 
     * 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 has an upper limit of mMaxSqlCacheSize (settable by calling the method 
     * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because 
     * most of the apps don't use "?" syntax in their sql, caching is not useful for them. 
     */
 
    /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap(); 
    /**
     * @hide 
     */
 
    public static final int MAX_SQL_CACHE_SIZE = 250
    private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance 
    private int mCacheFullWarnings; 
    private static final int MAX_WARNINGS_ON_CACHESIZE_CONDITION = 1
 
    /** maintain stats about number of cache hits and misses */ 
    private int mNumCacheHits; 
    private int mNumCacheMisses; 
 
    /** the following 2 members maintain the time when a database is opened and closed */ 
    private String mTimeOpened = null
    private String mTimeClosed = null
 
    /** Used to find out where this object was created in case it never got closed. */ 
    private Throwable mStackTrace = null
 
    // System property that enables logging of slow queries. Specify the threshold in ms. 
    private static final String LOG_SLOW_QUERIES_PROPERTY = "db.log.slow_query_threshold"
    private final int mSlowQueryThreshold; 
 
    /**
     * @param closable 
     */
 
    void addSQLiteClosable(SQLiteClosable closable) { 
        lock(); 
        try { 
            mPrograms.put(closable, null); 
        } finally { 
            unlock(); 
        } 
    } 
 
    void removeSQLiteClosable(SQLiteClosable closable) { 
        lock(); 
        try { 
            mPrograms.remove(closable); 
        } finally { 
            unlock(); 
        } 
    } 
 
    @Override 
    protected void onAllReferencesReleased() { 
        if (isOpen()) { 
            if (SQLiteDebug.DEBUG_SQL_CACHE) { 
                mTimeClosed = getTime(); 
            } 
            dbclose(); 
        } 
    } 
 
    /**
     * Attempts to release memory that SQLite holds but does not require to 
     * operate properly. Typically this memory will come from the page cache. 
     * 
     * @return the number of bytes actually released 
     */
 
    static public native int releaseMemory(); 
 
    /**
     * Control whether or not the SQLiteDatabase is made thread-safe by using locks 
     * around critical sections. This is pretty expensive, so if you know that your 
     * 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 
     */
 
    public void setLockingEnabled(boolean lockingEnabled) { 
        mLockingEnabled = lockingEnabled; 
    } 
 
    /**
     * If set then the SQLiteDatabase is made thread-safe by using locks 
     * around critical sections 
     */
 
    private boolean mLockingEnabled = true
 
    /* package */ void onCorruption() { 
        Log.e(TAG, "Removing corrupt database: " + mPath); 
        //    EventLog.writeEvent(EVENT_DB_CORRUPT, mPath); 
        try { 
            // Close the database (if we can), which will cause subsequent operations to fail. 
            close(); 
        } finally { 
            // Delete the corrupt file.  Don't re-create it now -- that would just confuse people 
            // -- but the next time someone tries to open it, they can set it up from scratch. 
            if (!mPath.equalsIgnoreCase(":memory")) { 
                // delete is only for non-memory database files 
                new File(mPath).delete(); 
            } 
        } 
    } 
 
    /**
     * 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. 
     * 
     * @see #unlock() 
     */
 
    /* package */ void lock() { 
        if (!mLockingEnabled) return
        mLock.lock(); 
        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(); 
            } 
        } 
    } 
 
    /**
     * 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. 
     * 
     * @see #unlockForced() 
     */
 
    private void lockForced() { 
        mLock.lock(); 
        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(); 
            } 
        } 
    } 
 
    /**
     * 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); 
                } 
            } 
        } 
    } 
 
    /**
     * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of 
     * the work done in that transaction and all of the nested transactions will be committed or 
     * rolled back. The changes will be rolled back if any transaction is ended without being 
     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. 
     * 
     * <p>Here is the standard idiom for transactions: 
     * 
     * <pre> 
     *   db.beginTransaction(); 
     *   try { 
     *     ... 
     *     db.setTransactionSuccessful(); 
     *   } finally { 
     *     db.endTransaction(); 
     *   } 
     * </pre> 
     */
 
    public void beginTransaction() { 
        beginTransactionWithListener(null /* transactionStatusCallback */); 
    } 
 
    /**
     * Begins a transaction. Transactions can be nested. When the outer transaction is ended all of 
     * the work done in that transaction and all of the nested transactions will be committed or 
     * rolled back. The changes will be rolled back if any transaction is ended without being 
     * marked as clean (by calling setTransactionSuccessful). Otherwise they will be committed. 
     * 
     * <p>Here is the standard idiom for transactions: 
     * 
     * <pre> 
     *   db.beginTransactionWithListener(listener); 
     *   try { 
     *     ... 
     *     db.setTransactionSuccessful(); 
     *   } finally { 
     *     db.endTransaction(); 
     *   } 
     * </pre> 
     * @param transactionListener listener that should be notified when the transaction begins, 
     * commits, or is rolled back, either explicitly or by a call to 
     * {@link #yieldIfContendedSafely}. 
     */
 
    public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) { 
        lockForced(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        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. 
            execSQL("BEGIN EXCLUSIVE;"); 
            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(); 
            } 
        } 
    } 
 
    /**
     * End a transaction. See beginTransaction for notes about how to use this and when transactions 
     * are committed and rolled back. 
     */
 
    public void endTransaction() { 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        if (!mLock.isHeldByCurrentThread()) { 
            throw new IllegalStateException("no transaction pending"); 
        } 
        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); 
            } else { 
                try { 
                    execSQL("ROLLBACK;"); 
                    if (savedException != null) { 
                        throw savedException; 
                    } 
                } catch (SQLException e) { 
                    if (Config.LOGD) { 
                        Log.d(TAG, "exception during rollback, maybe the DB previously " 
                              + "performed an auto-rollback"); 
                    } 
                } 
            } 
        } finally { 
            mTransactionListener = null
            unlockForced(); 
            if (Config.LOGV) { 
                Log.v(TAG, "unlocked " + Thread.currentThread() 
                      + ", holdCount is " + mLock.getHoldCount()); 
            } 
        } 
    } 
 
    /**
     * Marks the current transaction as successful. Do not do any more database work between 
     * calling this and calling endTransaction. Do as little non-database work as possible in that 
     * situation too. If any errors are encountered between this and endTransaction the transaction 
     * will still be committed. 
     * 
     * @throws IllegalStateException if the current thread is not in a transaction or the 
     * transaction is already marked as successful. 
     */
 
    public void setTransactionSuccessful() { 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        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
    } 
 
    /**
     * return true if there is a transaction pending 
     */
 
    public boolean inTransaction() { 
        return mLock.getHoldCount() > 0
    } 
 
    /**
     * Checks if the database lock is held by this thread. 
     * 
     * @return true, if this thread is holding the database lock. 
     */
 
    public boolean isDbLockedByCurrentThread() { 
        return mLock.isHeldByCurrentThread(); 
    } 
 
    /**
     * 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. 
     * 
     * @return true, if the database is locked by another thread 
     */
 
    public boolean isDbLockedByOtherThreads() { 
        return !mLock.isHeldByCurrentThread() && mLock.isLocked(); 
    } 
 
    /**
     * Temporarily end the transaction to let other threads run. The transaction is assumed to be 
     * successful so far. Do not call setTransactionSuccessful before calling this. When this 
     * returns a new transaction will have been created but not marked as successful. 
     * @return true if the transaction was yielded 
     * @deprecated if the db is locked more than once (becuase of nested transactions) then the lock 
     *   will not be yielded. Use yieldIfContendedSafely instead. 
     */
 
    @Deprecated 
    public boolean yieldIfContended() { 
        return yieldIfContendedHelper(false /* do not check yielding */
                                      -1 /* sleepAfterYieldDelay */); 
    } 
 
    /**
     * Temporarily end the transaction to let other threads run. The transaction is assumed to be 
     * successful so far. Do not call setTransactionSuccessful before calling this. When this 
     * returns a new transaction will have been created but not marked as successful. This assumes 
     * that there are no nested transactions (beginTransaction has only been called once) and will 
     * throw an exception if that is not the case. 
     * @return true if the transaction was yielded 
     */
 
    public boolean yieldIfContendedSafely() { 
        return yieldIfContendedHelper(true /* check yielding */, -1 /* sleepAfterYieldDelay*/); 
    } 
 
    /**
     * Temporarily end the transaction to let other threads run. The transaction is assumed to be 
     * successful so far. Do not call setTransactionSuccessful before calling this. When this 
     * returns a new transaction will have been created but not marked as successful. This assumes 
     * that there are no nested transactions (beginTransaction has only been called once) and will 
     * throw an exception if that is not the case. 
     * @param sleepAfterYieldDelay if > 0, sleep this long before starting a new transaction if 
     *   the lock was actually yielded. This will allow other background threads to make some 
     *   more progress than they would if we started the transaction immediately. 
     * @return true if the transaction was yielded 
     */
 
    public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) { 
        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
    } 
 
    /** Maps table names to info about what to which _sync_time column to set     * to NULL on an update. This is used to support syncing. */ 
    private final Map<String, SyncUpdateInfo> mSyncUpdateInfo = 
        new HashMap<String, SyncUpdateInfo>(); 
 
    public Map<String, String> getSyncedTables() { 
        synchronized(mSyncUpdateInfo) { 
            HashMap<String, String> tables = new HashMap<String, String>(); 
            for (String table : mSyncUpdateInfo.keySet()) { 
                SyncUpdateInfo info = mSyncUpdateInfo.get(table); 
                if (info.deletedTable != null) { 
                    tables.put(table, info.deletedTable); 
                } 
            } 
            return tables; 
        } 
    } 
 
    /**
     * Internal class used to keep track what needs to be marked as changed 
     * when an update occurs. This is used for syncing, so the sync engine 
     * knows what data has been updated locally. 
     */
 
    static private class SyncUpdateInfo { 
        /**
         * Creates the SyncUpdateInfo class. 
         * 
         * @param masterTable The table to set _sync_time to NULL in 
         * @param deletedTable The deleted table that corresponds to the 
         *          master table 
         * @param foreignKey The key that refers to the primary key in table 
         */
 
        SyncUpdateInfo(String masterTable, String deletedTable, 
                       String foreignKey) { 
            this.masterTable = masterTable; 
            this.deletedTable = deletedTable; 
            this.foreignKey = foreignKey; 
        } 
 
        /** The table containing the _sync_time column */ 
        String masterTable; 
 
        /** The deleted table that corresponds to the master table */ 
        String deletedTable; 
 
        /** The key in the local table the row in table. It may be _id, if table         * is the local table. */ 
        String foreignKey; 
    } 
 
    /**
     * Used to allow returning sub-classes of {@link Cursor} when calling query. 
     */
 
    public interface CursorFactory { 
        /**
         * See 
         * {@link SQLiteCursor#SQLiteCursor(SQLiteDatabase, 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}. 
     * 
     * <p>Sets the locale of the database to the  the system's current locale. 
     * Call {@link #setLocale} if you would like something else.</p> 
     * 
     * @param path to database file to open and/or create 
     * @param factory an optional factory class that is called to instantiate a 
     *            cursor when query is called, or null for default 
     * @param flags to control database access mode 
     * @return the newly opened database 
     * @throws SQLiteException if the database cannot be opened 
     */
 
    public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) { 
        SQLiteDatabase sqliteDatabase = null
        try { 
            // Open the database. 
            sqliteDatabase = new SQLiteDatabase(path, password, factory, flags, databaseHook); 
            if (SQLiteDebug.DEBUG_SQL_STATEMENTS) { 
                sqliteDatabase.enableSqlTracing(path); 
            } 
            if (SQLiteDebug.DEBUG_SQL_TIME) { 
                sqliteDatabase.enableSqlProfiling(path); 
            } 
        } catch (SQLiteDatabaseCorruptException e) { 
            // Try to recover from this, if we can. 
            // TODO: should we do this for other open failures? 
            Log.e(TAG, "Deleting and re-creating corrupt database " + path, e); 
            // EventLog.writeEvent(EVENT_DB_CORRUPT, path); 
            if (!path.equalsIgnoreCase(":memory")) { 
                // delete is only for non-memory database files 
                new File(path).delete(); 
            } 
            sqliteDatabase = new SQLiteDatabase(path, password, factory, flags, databaseHook); 
        } 
        ActiveDatabases.getInstance().mActiveDatabases.add( 
                                                           new WeakReference<SQLiteDatabase>(sqliteDatabase)); 
        return sqliteDatabase; 
    } 
     
    public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook){ 
        return openOrCreateDatabase(file.getPath(), password, factory, databaseHook); 
    } 
 
    public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) { 
        return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook); 
    } 
     
    /**
     * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY). 
     */
 
    public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory) { 
        return openOrCreateDatabase(file.getPath(), password, factory, null); 
    } 
 
    /**
     * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY). 
     */
 
     
    public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory) { 
        return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null); 
    } 
 
    public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags) { 
        return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null); 
    } 
 
    /**
     * Create a memory backed SQLite database.  Its contents will be destroyed 
     * when the database is closed. 
     * 
     * <p>Sets the locale of the database to the  the system's current locale. 
     * Call {@link #setLocale} if you would like something else.</p> 
     * 
     * @param factory an optional factory class that is called to instantiate a 
     *            cursor when query is called 
     * @return a SQLiteDatabase object, or null if the database can't be created 
     */
 
    public static SQLiteDatabase create(CursorFactory factory, String password) { 
        // This is a magic string with special meaning for SQLite. 
        return openDatabase(":memory:", password, factory, CREATE_IF_NECESSARY); 
    } 
 
    /**
     * Close the database. 
     */
 
    public void close() { 
         
        if (!isOpen()) { 
            return// already closed 
        } 
        lock(); 
        try { 
            closeClosable(); 
            // close this database instance - regardless of its reference count value 
            onAllReferencesReleased(); 
        } 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(); 
            } 
        } 
    } 
 
    /**
     * Native call to close the database. 
     */
 
    private native void dbclose(); 
 
    /**
     * Gets the database version. 
     * 
     * @return the database version 
     */
 
    public int getVersion() { 
        SQLiteStatement prog = null
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        try { 
            prog = new SQLiteStatement(this"PRAGMA user_version;"); 
            long version = prog.simpleQueryForLong(); 
            return (int) version; 
        } finally { 
            if (prog != null) prog.close(); 
            unlock(); 
        } 
    } 
 
    /**
     * Sets the database version. 
     * 
     * @param version the new database version 
     */
 
    public void setVersion(int version) { 
        execSQL("PRAGMA user_version = " + version); 
    } 
 
    /**
     * Returns the maximum size the database may grow to. 
     * 
     * @return the new maximum database size 
     */
 
    public long getMaximumSize() { 
        SQLiteStatement prog = null
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        try { 
            prog = new SQLiteStatement(this
                                       "PRAGMA max_page_count;"); 
            long pageCount = prog.simpleQueryForLong(); 
            return pageCount * getPageSize(); 
        } finally { 
            if (prog != null) prog.close(); 
            unlock(); 
        } 
    } 
 
    /**
     * Sets the maximum size the database will grow to. The maximum size cannot 
     * be set below the current size. 
     * 
     * @param numBytes the maximum database size, in bytes 
     * @return the new maximum database size 
     */
 
    public long setMaximumSize(long numBytes) { 
        SQLiteStatement prog = null
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        try { 
            long pageSize = getPageSize(); 
            long numPages = numBytes / pageSize; 
            // If numBytes isn't a multiple of pageSize, bump up a page 
            if ((numBytes % pageSize) != 0) { 
                numPages++; 
            } 
            prog = new SQLiteStatement(this
                                       "PRAGMA max_page_count = " + numPages); 
            long newPageCount = prog.simpleQueryForLong(); 
            return newPageCount * pageSize; 
        } finally { 
            if (prog != null) prog.close(); 
            unlock(); 
        } 
    } 
 
    /**
     * Returns the current database page size, in bytes. 
     * 
     * @return the database page size, in bytes 
     */
 
    public long getPageSize() { 
        SQLiteStatement prog = null
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        try { 
            prog = new SQLiteStatement(this
                                       "PRAGMA page_size;"); 
            long size = prog.simpleQueryForLong(); 
            return size; 
        } finally { 
            if (prog != null) prog.close(); 
            unlock(); 
        } 
    } 
 
    /**
     * Sets the database page size. The page size must be a power of two. This 
     * method does not work if any data has been written to the database file, 
     * and must be called right after the database has been created. 
     * 
     * @param numBytes the database page size, in bytes 
     */
 
    public void setPageSize(long numBytes) { 
        execSQL("PRAGMA page_size = " + numBytes); 
    } 
 
    /**
     * Mark this table as syncable. When an update occurs in this table the 
     * _sync_dirty field will be set to ensure proper syncing operation. 
     * 
     * @param table the table to mark as syncable 
     * @param deletedTable The deleted table that corresponds to the 
     *          syncable table 
     */
 
    public void markTableSyncable(String table, String deletedTable) { 
        markTableSyncable(table, "_id", table, deletedTable); 
    } 
 
    /**
     * Mark this table as syncable, with the _sync_dirty residing in another 
     * table. When an update occurs in this table the _sync_dirty field of the 
     * row in updateTable with the _id in foreignKey will be set to 
     * ensure proper syncing operation. 
     * 
     * @param table an update on this table will trigger a sync time removal 
     * @param foreignKey this is the column in table whose value is an _id in 
     *          updateTable 
     * @param updateTable this is the table that will have its _sync_dirty 
     */
 
    public void markTableSyncable(String table, String foreignKey, 
                                  String updateTable) { 
        markTableSyncable(table, foreignKey, updateTable, null); 
    } 
 
    /**
     * Mark this table as syncable, with the _sync_dirty residing in another 
     * table. When an update occurs in this table the _sync_dirty field of the 
     * row in updateTable with the _id in foreignKey will be set to 
     * ensure proper syncing operation. 
     * 
     * @param table an update on this table will trigger a sync time removal 
     * @param foreignKey this is the column in table whose value is an _id in 
     *          updateTable 
     * @param updateTable this is the table that will have its _sync_dirty 
     * @param deletedTable The deleted table that corresponds to the 
     *          updateTable 
     */
 
    private void markTableSyncable(String table, String foreignKey, 
                                   String updateTable, String deletedTable) { 
        lock(); 
        try { 
            native_execSQL("SELECT _sync_dirty FROM " + updateTable 
                           + " LIMIT 0"); 
            native_execSQL("SELECT " + foreignKey + " FROM " + table 
                           + " LIMIT 0"); 
        } finally { 
            unlock(); 
        } 
 
        SyncUpdateInfo info = new SyncUpdateInfo(updateTable, deletedTable, 
                                                 foreignKey); 
        synchronized (mSyncUpdateInfo) { 
            mSyncUpdateInfo.put(table, info); 
        } 
    } 
 
    /**
     * Call for each row that is updated in a cursor. 
     * 
     * @param table the table the row is in 
     * @param rowId the row ID of the updated row 
     */
 
    /* package */ void rowUpdated(String table, long rowId) { 
        SyncUpdateInfo info; 
        synchronized (mSyncUpdateInfo) { 
            info = mSyncUpdateInfo.get(table); 
        } 
        if (info != null) { 
            execSQL("UPDATE " + info.masterTable 
                    + " SET _sync_dirty=1 WHERE _id=(SELECT " + info.foreignKey 
                    + " FROM " + table + " WHERE _id=" + rowId + ")"); 
        } 
    } 
 
    /**
     * Finds the name of the first table, which is editable. 
     * 
     * @param tables a list of tables 
     * @return the first table listed 
     */
 
    public static String findEditTable(String tables) { 
        if (!TextUtils.isEmpty(tables)) { 
            // find the first word terminated by either a space or a comma 
            int spacepos = tables.indexOf(' '); 
            int commapos = tables.indexOf(','); 
 
            if (spacepos > 0 && (spacepos < commapos || commapos < 0)) { 
                return tables.substring(0, spacepos); 
            } else if (commapos > 0 && (commapos < spacepos || spacepos < 0) ) { 
                return tables.substring(0, commapos); 
            } 
            return tables; 
        } else { 
            throw new IllegalStateException("Invalid tables"); 
        } 
    } 
 
    /**
     * Compiles an SQL statement into a reusable pre-compiled statement object. 
     * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the 
     * statement and fill in those values with {@link SQLiteProgram#bindString} 
     * and {@link SQLiteProgram#bindLong} each time you want to run the 
     * statement. Statements may not return result sets larger than 1x1. 
     * 
     * @param sql The raw SQL statement, may contain ? for unknown values to be 
     *            bound later. 
     * @return A pre-compiled {@link SQLiteStatement} object. Note that 
     * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. 
     */
 
    public SQLiteStatement compileStatement(String sql) throws SQLException { 
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        try { 
            return new SQLiteStatement(this, sql); 
        } finally { 
            unlock(); 
        } 
    } 
 
    /**
     * Query the given URL, returning a {@link Cursor} over the result set. 
     * 
     * @param distinct true if you want each row to be unique, false otherwise. 
     * @param table The table name to compile the query against. 
     * @param columns A list of which columns to return. Passing null will 
     *            return all columns, which is discouraged to prevent reading 
     *            data from storage that isn't going to be used. 
     * @param selection A filter declaring which rows to return, formatted as an 
     *            SQL WHERE clause (excluding the WHERE itself). Passing null 
     *            will return all rows for the given table. 
     * @param selectionArgs You may include ?s in selection, which will be 
     *         replaced by the values from selectionArgs, in order that they 
     *         appear in the selection. The values will be bound as Strings. 
     * @param groupBy A filter declaring how to group rows, formatted as an SQL 
     *            GROUP BY clause (excluding the GROUP BY itself). Passing null 
     *            will cause the rows to not be grouped. 
     * @param having A filter declare which row groups to include in the cursor, 
     *            if row grouping is being used, formatted as an SQL HAVING 
     *            clause (excluding the HAVING itself). Passing null will cause 
     *            all row groups to be included, and is required when row 
     *            grouping is not being used. 
     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause 
     *            (excluding the ORDER BY itself). Passing null will use the 
     *            default sort order, which may be unordered. 
     * @param limit Limits the number of rows returned by the query, 
     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause. 
     * @return A {@link Cursor} object, which is positioned before the first entry. Note that 
     * {@link Cursor}s are not synchronized, see the documentation for more details. 
     * @see Cursor 
     */
 
    public Cursor query(boolean distinct, String table, String[] columns, 
                        String selection, String[] selectionArgs, String groupBy, 
                        String having, String orderBy, String limit) { 
        return queryWithFactory(null, distinct, table, columns, selection, selectionArgs, 
                                groupBy, having, orderBy, limit); 
    } 
 
    /**
     * Query the given URL, returning a {@link Cursor} over the result set. 
     * 
     * @param cursorFactory the cursor factory to use, or null for the default factory 
     * @param distinct true if you want each row to be unique, false otherwise. 
     * @param table The table name to compile the query against. 
     * @param columns A list of which columns to return. Passing null will 
     *            return all columns, which is discouraged to prevent reading 
     *            data from storage that isn't going to be used. 
     * @param selection A filter declaring which rows to return, formatted as an 
     *            SQL WHERE clause (excluding the WHERE itself). Passing null 
     *            will return all rows for the given table. 
     * @param selectionArgs You may include ?s in selection, which will be 
     *         replaced by the values from selectionArgs, in order that they 
     *         appear in the selection. The values will be bound as Strings. 
     * @param groupBy A filter declaring how to group rows, formatted as an SQL 
     *            GROUP BY clause (excluding the GROUP BY itself). Passing null 
     *            will cause the rows to not be grouped. 
     * @param having A filter declare which row groups to include in the cursor, 
     *            if row grouping is being used, formatted as an SQL HAVING 
     *            clause (excluding the HAVING itself). Passing null will cause 
     *            all row groups to be included, and is required when row 
     *            grouping is not being used. 
     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause 
     *            (excluding the ORDER BY itself). Passing null will use the 
     *            default sort order, which may be unordered. 
     * @param limit Limits the number of rows returned by the query, 
     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause. 
     * @return A {@link Cursor} object, which is positioned before the first entry. Note that 
     * {@link Cursor}s are not synchronized, see the documentation for more details. 
     * @see Cursor 
     */
 
    public Cursor queryWithFactory(CursorFactory cursorFactory, 
                                   boolean distinct, String table, String[] columns, 
                                   String selection, String[] selectionArgs, String groupBy, 
                                   String having, String orderBy, String limit) { 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        String sql = SQLiteQueryBuilder.buildQueryString( 
                                                         distinct, table, columns, selection, groupBy, having, orderBy, limit); 
 
        return rawQueryWithFactory( 
                                   cursorFactory, sql, selectionArgs, findEditTable(table)); 
    } 
 
    /**
     * Query the given table, returning a {@link Cursor} over the result set. 
     * 
     * @param table The table name to compile the query against. 
     * @param columns A list of which columns to return. Passing null will 
     *            return all columns, which is discouraged to prevent reading 
     *            data from storage that isn't going to be used. 
     * @param selection A filter declaring which rows to return, formatted as an 
     *            SQL WHERE clause (excluding the WHERE itself). Passing null 
     *            will return all rows for the given table. 
     * @param selectionArgs You may include ?s in selection, which will be 
     *         replaced by the values from selectionArgs, in order that they 
     *         appear in the selection. The values will be bound as Strings. 
     * @param groupBy A filter declaring how to group rows, formatted as an SQL 
     *            GROUP BY clause (excluding the GROUP BY itself). Passing null 
     *            will cause the rows to not be grouped. 
     * @param having A filter declare which row groups to include in the cursor, 
     *            if row grouping is being used, formatted as an SQL HAVING 
     *            clause (excluding the HAVING itself). Passing null will cause 
     *            all row groups to be included, and is required when row 
     *            grouping is not being used. 
     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause 
     *            (excluding the ORDER BY itself). Passing null will use the 
     *            default sort order, which may be unordered. 
     * @return A {@link Cursor} object, which is positioned before the first entry. Note that 
     * {@link Cursor}s are not synchronized, see the documentation for more details. 
     * @see Cursor 
     */
 
    public Cursor query(String table, String[] columns, String selection, 
                        String[] selectionArgs, String groupBy, String having, 
                        String orderBy) { 
 
        return query(false, table, columns, selection, selectionArgs, groupBy, 
                     having, orderBy, null /* limit */); 
    } 
 
    /**
     * Query the given table, returning a {@link Cursor} over the result set. 
     * 
     * @param table The table name to compile the query against. 
     * @param columns A list of which columns to return. Passing null will 
     *            return all columns, which is discouraged to prevent reading 
     *            data from storage that isn't going to be used. 
     * @param selection A filter declaring which rows to return, formatted as an 
     *            SQL WHERE clause (excluding the WHERE itself). Passing null 
     *            will return all rows for the given table. 
     * @param selectionArgs You may include ?s in selection, which will be 
     *         replaced by the values from selectionArgs, in order that they 
     *         appear in the selection. The values will be bound as Strings. 
     * @param groupBy A filter declaring how to group rows, formatted as an SQL 
     *            GROUP BY clause (excluding the GROUP BY itself). Passing null 
     *            will cause the rows to not be grouped. 
     * @param having A filter declare which row groups to include in the cursor, 
     *            if row grouping is being used, formatted as an SQL HAVING 
     *            clause (excluding the HAVING itself). Passing null will cause 
     *            all row groups to be included, and is required when row 
     *            grouping is not being used. 
     * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause 
     *            (excluding the ORDER BY itself). Passing null will use the 
     *            default sort order, which may be unordered. 
     * @param limit Limits the number of rows returned by the query, 
     *            formatted as LIMIT clause. Passing null denotes no LIMIT clause. 
     * @return A {@link Cursor} object, which is positioned before the first entry. Note that 
     * {@link Cursor}s are not synchronized, see the documentation for more details. 
     * @see Cursor 
     */
 
    public Cursor query(String table, String[] columns, String selection, 
                        String[] selectionArgs, String groupBy, String having, 
                        String orderBy, String limit) { 
 
        return query(false, table, columns, selection, selectionArgs, groupBy, 
                     having, orderBy, limit); 
    } 
 
    /**
     * Runs the provided SQL and returns a {@link Cursor} over the result set. 
     * 
     * @param sql the SQL query. The SQL string must not be ; terminated 
     * @param selectionArgs You may include ?s in where clause in the query, 
     *     which will be replaced by the values from selectionArgs. The 
     *     values will be bound as Strings. 
     * @return A {@link Cursor} object, which is positioned before the first entry. Note that 
     * {@link Cursor}s are not synchronized, see the documentation for more details. 
     */
 
    public Cursor rawQuery(String sql, String[] selectionArgs) { 
        return rawQueryWithFactory(null, sql, selectionArgs, null); 
    } 
 
    /**
     * Runs the provided SQL and returns a cursor over the result set. 
     * 
     * @param cursorFactory the cursor factory to use, or null for the default factory 
     * @param sql the SQL query. The SQL string must not be ; terminated 
     * @param selectionArgs You may include ?s in where clause in the query, 
     *     which will be replaced by the values from selectionArgs. The 
     *     values will be bound as Strings. 
     * @param editTable the name of the first table, which is editable 
     * @return A {@link Cursor} object, which is positioned before the first entry. Note that 
     * {@link Cursor}s are not synchronized, see the documentation for more details. 
     */
 
    public Cursor rawQueryWithFactory
                                      CursorFactory cursorFactory, String sql, String[] selectionArgs, 
                                      String editTable) { 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        long timeStart = 0
 
        if (Config.LOGV || mSlowQueryThreshold != -1) { 
            timeStart = System.currentTimeMillis(); 
        } 
 
        SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable); 
 
        Cursor cursor = null
        try { 
            cursor = driver.query( 
                                  cursorFactory != null ? cursorFactory : mFactory, 
                                  selectionArgs); 
        } finally { 
            if (Config.LOGV || mSlowQueryThreshold != -1) { 
 
                // Force query execution 
                int count = -1
                if (cursor != null) { 
                    count = cursor.getCount(); 
                } 
 
                long duration = System.currentTimeMillis() - timeStart; 
 
                if (Config.LOGV || duration >= mSlowQueryThreshold) { 
                    Log.v(TAG, 
                          "query (" + duration + " ms): " + driver.toString() + ", args are " 
                          + (selectionArgs != null 
                             ? TextUtils.join(",", selectionArgs) 
                             : "<null>")  + ", count is " + count); 
                } 
            } 
        } 
        return new CrossProcessCursorWrapper(cursor); 
    } 
 
    /**
     * Runs the provided SQL and returns a cursor over the result set. 
     * The cursor will read an initial set of rows and the return to the caller. 
     * It will continue to read in batches and send data changed notifications 
     * when the later batches are ready. 
     * @param sql the SQL query. The SQL string must not be ; terminated 
     * @param selectionArgs You may include ?s in where clause in the query, 
     *     which will be replaced by the values from selectionArgs. The 
     *     values will be bound as Strings. 
     * @param initialRead set the initial count of items to read from the cursor 
     * @param maxRead set the count of items to read on each iteration after the first 
     * @return A {@link Cursor} object, which is positioned before the first entry. Note that 
     * {@link Cursor}s are not synchronized, see the documentation for more details. 
     * 
     * This work is incomplete and not fully tested or reviewed, so currently 
     * hidden. 
     * @hide 
     */
 
    public Cursor rawQuery(String sql, String[] selectionArgs, 
                           int initialRead, int maxRead) { 
        SQLiteCursor c = (SQLiteCursor)rawQueryWithFactory( 
                                                           null, sql, selectionArgs, null); 
        c.setLoadStyle(initialRead, maxRead); 
         
        return c; 
    } 
 
    /**
     * Convenience method for inserting a row into the database. 
     * 
     * @param table the table to insert the row into 
     * @param nullColumnHack SQL doesn't allow inserting a completely empty row, 
     *            so if initialValues is empty this column will explicitly be 
     *            assigned a NULL value 
     * @param values this map contains the initial column values for the 
     *            row. The keys should be the column names and the values the 
     *            column values 
     * @return the row ID of the newly inserted row, or -1 if an error occurred 
     */
 
    public long insert(String table, String nullColumnHack, ContentValues values) { 
        try { 
            return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); 
        } catch (SQLException e) { 
            Log.e(TAG, "Error inserting " + values, e); 
            return -1
        } 
    } 
 
    /**
     * Convenience method for inserting a row into the database. 
     * 
     * @param table the table to insert the row into 
     * @param nullColumnHack SQL doesn't allow inserting a completely empty row, 
     *            so if initialValues is empty this column will explicitly be 
     *            assigned a NULL value 
     * @param values this map contains the initial column values for the 
     *            row. The keys should be the column names and the values the 
     *            column values 
     * @throws SQLException 
     * @return the row ID of the newly inserted row, or -1 if an error occurred 
     */
 
    public long insertOrThrow(String table, String nullColumnHack, ContentValues values) 
        throws SQLException { 
        return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); 
    } 
 
    /**
     * Convenience method for replacing a row in the database. 
     * 
     * @param table the table in which to replace the row 
     * @param nullColumnHack SQL doesn't allow inserting a completely empty row, 
     *            so if initialValues is empty this row will explicitly be 
     *            assigned a NULL value 
     * @param initialValues this map contains the initial column values for 
     *   the row. The key 
     * @return the row ID of the newly inserted row, or -1 if an error occurred 
     */
 
    public long replace(String table, String nullColumnHack, ContentValues initialValues) { 
        try { 
            return insertWithOnConflict(table, nullColumnHack, initialValues, 
                                        CONFLICT_REPLACE); 
        } catch (SQLException e) { 
            Log.e(TAG, "Error inserting " + initialValues, e); 
            return -1
        } 
    } 
 
    /**
     * Convenience method for replacing a row in the database. 
     * 
     * @param table the table in which to replace the row 
     * @param nullColumnHack SQL doesn't allow inserting a completely empty row, 
     *            so if initialValues is empty this row will explicitly be 
     *            assigned a NULL value 
     * @param initialValues this map contains the initial column values for 
     *   the row. The key 
     * @throws SQLException 
     * @return the row ID of the newly inserted row, or -1 if an error occurred 
     */
 
    public long replaceOrThrow(String table, String nullColumnHack, 
                               ContentValues initialValues) throws SQLException { 
        return insertWithOnConflict(table, nullColumnHack, initialValues, 
                                    CONFLICT_REPLACE); 
    } 
 
    /**
     * General method for inserting a row into the database. 
     * 
     * @param table the table to insert the row into 
     * @param nullColumnHack SQL doesn't allow inserting a completely empty row, 
     *            so if initialValues is empty this column will explicitly be 
     *            assigned a NULL value 
     * @param initialValues this map contains the initial column values for the 
     *            row. The keys should be the column names and the values the 
     *            column values 
     * @param conflictAlgorithm for insert conflict resolver 
     * @return the row ID of the newly inserted row 
     * OR the primary key of the existing row if the input param 'conflictAlgorithm' = 
     * {@link #CONFLICT_IGNORE} 
     * OR -1 if any error 
     */
 
    public long insertWithOnConflict(String table, String nullColumnHack, 
                                     ContentValues initialValues, int conflictAlgorithm) { 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
 
        // Measurements show most sql lengths <= 152 
        StringBuilder sql = new StringBuilder(152); 
        sql.append("INSERT"); 
        sql.append(CONFLICT_VALUES[conflictAlgorithm]); 
        sql.append(" INTO "); 
        sql.append(table); 
        // Measurements show most values lengths < 40 
        StringBuilder values = new StringBuilder(40); 
 
        Set<Map.Entry<String, Object>> entrySet = null
        if (initialValues != null && initialValues.size() > 0) { 
            entrySet = initialValues.valueSet(); 
            Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator(); 
            sql.append('('); 
 
            boolean needSeparator = false
            while (entriesIter.hasNext()) { 
                if (needSeparator) { 
                    sql.append(", "); 
                    values.append(", "); 
                } 
                needSeparator = true
                Map.Entry<String, Object> entry = entriesIter.next(); 
                sql.append(entry.getKey()); 
                values.append('?'); 
            } 
 
            sql.append(')'); 
        } else { 
            sql.append("(" + nullColumnHack + ") "); 
            values.append("NULL"); 
        } 
 
        sql.append(" VALUES("); 
        sql.append(values); 
        sql.append(");"); 
 
        lock(); 
        SQLiteStatement statement = null
        try { 
            statement = compileStatement(sql.toString()); 
 
            // Bind the values 
            if (entrySet != null) { 
                int size = entrySet.size(); 
                Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator(); 
                for (int i = 0; i < size; i++) { 
                    Map.Entry<String, Object> entry = entriesIter.next(); 
                    DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue()); 
 
                } 
            } 
 
            // Run the program and then cleanup 
            statement.execute(); 
 
            long insertedRowId = lastInsertRow(); 
            if (insertedRowId == -1) { 
                Log.e(TAG, "Error inserting " + initialValues + " using " + sql); 
            } else { 
                if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) { 
                    Log.v(TAG, "Inserting row " + insertedRowId + " from " 
                          + initialValues + " using " + sql); 
                } 
            } 
            return insertedRowId; 
        } catch (SQLiteDatabaseCorruptException e) { 
            onCorruption(); 
            throw e; 
        } finally { 
            if (statement != null) { 
                statement.close(); 
            } 
            unlock(); 
        } 
    } 
 
    /**
     * Convenience method for deleting rows in the database. 
     * 
     * @param table the table to delete from 
     * @param whereClause the optional WHERE clause to apply when deleting. 
     *            Passing null will delete all rows. 
     * @return the number of rows affected if a whereClause is passed in, 0 
     *         otherwise. To remove all rows and get a count pass "1" as the 
     *         whereClause. 
     */
 
    public int delete(String table, String whereClause, String[] whereArgs) { 
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        SQLiteStatement statement = null
        try { 
            statement = compileStatement("DELETE FROM " + table 
                                         + (!TextUtils.isEmpty(whereClause) 
                                            ? " WHERE " + whereClause : "")); 
            if (whereArgs != null) { 
                int numArgs = whereArgs.length; 
                for (int i = 0; i < numArgs; i++) { 
                    DatabaseUtils.bindObjectToProgram(statement, i + 1, whereArgs[i]); 
                } 
            } 
            statement.execute(); 
            return lastChangeCount(); 
        } catch (SQLiteDatabaseCorruptException e) { 
            onCorruption(); 
            throw e; 
        } finally { 
            if (statement != null) { 
                statement.close(); 
            } 
            unlock(); 
        } 
    } 
 
    /**
     * Convenience method for updating rows in the database. 
     * 
     * @param table the table to update in 
     * @param values a map from column names to new column values. null is a 
     *            valid value that will be translated to NULL. 
     * @param whereClause the optional WHERE clause to apply when updating. 
     *            Passing null will update all rows. 
     * @return the number of rows affected 
     */
 
    public int update(String table, ContentValues values, String whereClause, String[] whereArgs) { 
        return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); 
    } 
 
    /**
     * Convenience method for updating rows in the database. 
     * 
     * @param table the table to update in 
     * @param values a map from column names to new column values. null is a 
     *            valid value that will be translated to NULL. 
     * @param whereClause the optional WHERE clause to apply when updating. 
     *            Passing null will update all rows. 
     * @param conflictAlgorithm for update conflict resolver 
     * @return the number of rows affected 
     */
 
    public int updateWithOnConflict(String table, ContentValues values, 
                                    String whereClause, String[] whereArgs, int conflictAlgorithm) { 
        if (values == null || values.size() == 0) { 
            throw new IllegalArgumentException("Empty values"); 
        } 
 
        StringBuilder sql = new StringBuilder(120); 
        sql.append("UPDATE "); 
        sql.append(CONFLICT_VALUES[conflictAlgorithm]); 
        sql.append(table); 
        sql.append(" SET "); 
 
        Set<Map.Entry<String, Object>> entrySet = values.valueSet(); 
        Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator(); 
 
        while (entriesIter.hasNext()) { 
            Map.Entry<String, Object> entry = entriesIter.next(); 
            sql.append(entry.getKey()); 
            sql.append("=?"); 
            if (entriesIter.hasNext()) { 
                sql.append(", "); 
            } 
        } 
 
        if (!TextUtils.isEmpty(whereClause)) { 
            sql.append(" WHERE "); 
            sql.append(whereClause); 
        } 
 
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        SQLiteStatement statement = null
        try { 
            statement = compileStatement(sql.toString()); 
 
            // Bind the values 
            int size = entrySet.size(); 
            entriesIter = entrySet.iterator(); 
            int bindArg = 1
            for (int i = 0; i < size; i++) { 
                Map.Entry<String, Object> entry = entriesIter.next(); 
                DatabaseUtils.bindObjectToProgram(statement, bindArg, entry.getValue()); 
                bindArg++; 
            } 
 
            if (whereArgs != null) { 
                size = whereArgs.length; 
                for (int i = 0; i < size; i++) { 
                    statement.bindString(bindArg, whereArgs[i]); 
                    bindArg++; 
                } 
            } 
 
            // Run the program and then cleanup 
            statement.execute(); 
            int numChangedRows = lastChangeCount(); 
            if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) { 
                Log.v(TAG, "Updated " + numChangedRows + " using " + values + " and " + sql); 
            } 
            return numChangedRows; 
        } catch (SQLiteDatabaseCorruptException e) { 
            onCorruption(); 
            throw e; 
        } catch (SQLException e) { 
            Log.e(TAG, "Error updating " + values + " using " + sql); 
            throw e; 
        } finally { 
            if (statement != null) { 
                statement.close(); 
            } 
            unlock(); 
        } 
    } 
 
    /**
     * Execute a single SQL statement that is not a query. For example, CREATE 
     * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not 
     * supported. it takes a write lock 
     * 
     * @throws SQLException If the SQL string is invalid for some reason 
     */
 
    public void execSQL(String sql) throws SQLException { 
        long timeStart = SystemClock.uptimeMillis(); 
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX); 
        try { 
            native_execSQL(sql); 
        } catch (SQLiteDatabaseCorruptException e) { 
            onCorruption(); 
            throw e; 
        } finally { 
            unlock(); 
        } 
 
        // Log commit statements along with the most recently executed 
        // SQL statement for disambiguation.  Note that instance 
        // equality to COMMIT_SQL is safe here. 
        if (sql == COMMIT_SQL) { 
            logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL); 
        } else { 
            logTimeStat(sql, timeStart, null); 
        } 
    } 
 
    public void rawExecSQL(String sql){ 
        long timeStart = SystemClock.uptimeMillis(); 
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        logTimeStat(mLastSqlStatement, timeStart, GET_LOCK_LOG_PREFIX); 
        try { 
            native_rawExecSQL(sql); 
        } catch (SQLiteDatabaseCorruptException e) { 
            onCorruption(); 
            throw e; 
        } finally { 
            unlock(); 
        } 
 
        // Log commit statements along with the most recently executed 
        // SQL statement for disambiguation.  Note that instance 
        // equality to COMMIT_SQL is safe here. 
        if (sql == COMMIT_SQL) { 
            logTimeStat(mLastSqlStatement, timeStart, COMMIT_SQL); 
        } else { 
            logTimeStat(sql, timeStart, null); 
        } 
    } 
     
    /**
     * Execute a single SQL statement that is not a query. For example, CREATE 
     * TABLE, DELETE, INSERT, etc. Multiple statements separated by ;s are not 
     * supported. it takes a write lock, 
     * 
     * @param sql 
     * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. 
     * @throws SQLException If the SQL string is invalid for some reason 
     */
 
    public void execSQL(String sql, Object[] bindArgs) throws SQLException { 
        if (bindArgs == null) { 
            throw new IllegalArgumentException("Empty bindArgs"); 
        } 
        long timeStart = SystemClock.uptimeMillis(); 
        lock(); 
        if (!isOpen()) { 
            throw new IllegalStateException("database not open"); 
        } 
        SQLiteStatement statement = null
        try { 
            statement = compileStatement(sql); 
            if (bindArgs != null) { 
                int numArgs = bindArgs.length; 
                for (int i = 0; i < numArgs; i++) { 
                    DatabaseUtils.bindObjectToProgram(statement, i + 1, bindArgs[i]); 
                } 
            } 
            statement.execute(); 
        } catch (SQLiteDatabaseCorruptException e) { 
            onCorruption(); 
            throw e; 
        } finally { 
            if (statement != null) { 
                statement.close(); 
            } 
            unlock(); 
        } 
        logTimeStat(sql, timeStart); 
    } 
 
    @Override 
    protected void finalize() { 
        if (isOpen()) { 
            Log.e(TAG, "close() was never explicitly called on database '" + 
                  mPath + "' ", mStackTrace); 
            closeClosable(); 
            onAllReferencesReleased(); 
        } 
    } 
 
    public SQLiteDatabase(String path, String password, CursorFactory factory, int flags) { 
        this(path, password, factory, flags, null); 
    } 
     
    /**
     * Private constructor. See {@link #create} and {@link #openDatabase}. 
     * 
     * @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. 
     */
 
    public SQLiteDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) { 
 
        if (path == null) { 
            throw new IllegalArgumentException("path should not be null"); 
        } 
        mFlags = flags; 
        mPath = path; 
        mSlowQueryThreshold = -1;//SystemProperties.getInt(LOG_SLOW_QUERIES_PROPERTY, -1); 
        mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); 
        mFactory = factory; 
        dbopen(mPath, mFlags); 
         
        if(databaseHook != null){ 
            databaseHook.preKey(this); 
        } 
         
        execSQL("PRAGMA key = '" + password + "'"); 
 
        if(databaseHook != null){ 
            databaseHook.postKey(this); 
        } 
 
        if (SQLiteDebug.DEBUG_SQL_CACHE) { 
            mTimeOpened = getTime(); 
        } 
        mPrograms = new WeakHashMap<SQLiteClosable,Object>(); 
        try { 
            setLocale(Locale.getDefault()); 
        } catch (RuntimeException e) { 
            Log.e(TAG, "Failed to setLocale() when constructing, closing the database", e); 
            dbclose(); 
            if (SQLiteDebug.DEBUG_SQL_CACHE) { 
                mTimeClosed = getTime(); 
            } 
            throw e; 
        } 
    } 
 
    private String getTime() { 
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ").format(System.currentTimeMillis()); 
    } 
 
    /**
     * return whether the DB is opened as read only. 
     * @return true if DB is opened as read only 
     */
 
    public boolean isReadOnly() { 
        return (mFlags & OPEN_READ_MASK) == OPEN_READONLY; 
    } 
 
    /**
     * @return true if the DB is currently open (has not been closed) 
     */
 
    public boolean isOpen() { 
        return mNativeHandle != 0
    } 
 
    public boolean needUpgrade(int newVersion) { 
        return newVersion > getVersion(); 
    } 
 
    /**
     * Getter for the path to the database file. 
     * 
     * @return the path to our database file. 
     */
 
    public final String getPath() { 
        return mPath; 
    } 
 
    /* package */ void logTimeStat(String sql, long beginMillis) { 
        logTimeStat(sql, beginMillis, null); 
    } 
 
    /* package */ void logTimeStat(String sql, long beginMillis, String prefix) { 
        // Keep track of the last statement executed here, as this is 
        // the common funnel through which all methods of hitting 
        // libsqlite eventually flow. 
        mLastSqlStatement = sql; 
 
        // 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 = 500;//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. 
 
        //TODO get the current package name 
        String blockingPackage = "unknown";//ActivityThread.currentPackageName(); 
        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. 
     * @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(); 
        } 
    } 
 
    /*
     * ============================================================================ 
     * 
     *       The following methods deal with compiled-sql cache 
     * ============================================================================ 
     */
 
    /**
     * adds the given sql and its compiled-statement-id-returned-by-sqlite to the 
     * cache of compiledQueries attached to 'this'. 
     * 
     * 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 */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { 
        if (mMaxSqlCacheSize == 0) { 
            // for this database, there is no cache of compiled sql. 
            if (SQLiteDebug.DEBUG_SQL_CACHE) { 
                Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql); 
            } 
            return
        } 
 
        SQLiteCompiledSql compiledSql = null
        synchronized(mCompiledQueries) { 
            // don't insert the new mapping if a mapping already exists 
            compiledSql = mCompiledQueries.get(sql); 
            if (compiledSql != null) { 
                return
            } 
            // add this <sql, compiledStatement> to the cache 
            if (mCompiledQueries.size() == mMaxSqlCacheSize) { 
                /*
                 * cache size of {@link #mMaxSqlCacheSize} is not enough for this app. 
                 * log a warning MAX_WARNINGS_ON_CACHESIZE_CONDITION times 
                 * chances are it is NOT using ? for bindargs - so caching is useless. 
                 * TODO: either let the callers set max cchesize for their app, or intelligently 
                 * figure out what should be cached for a given app. 
                 */
 
                if (++mCacheFullWarnings == MAX_WARNINGS_ON_CACHESIZE_CONDITION) { 
                    Log.w(TAG, "Reached MAX size for compiled-sql statement cache for database " + 
                          getPath() + "; i.e., NO space for this sql statement in cache: " + 
                          sql + ". Please change your sql statements to use '?' for " + 
                          "bindargs, instead of using actual values"); 
                } 
                // don't add this entry to cache 
            } else { 
                // cache is NOT full. add this to cache. 
                mCompiledQueries.put(sql, compiledStatement); 
                if (SQLiteDebug.DEBUG_SQL_CACHE) { 
                    Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + 
                          mCompiledQueries.size() + "|" + sql); 
                } 
            } 
        } 
        return
    } 
 
 
    private void deallocCachedSqlStatements() { 
        synchronized (mCompiledQueries) { 
            for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { 
                compiledSql.releaseSqlStatement(); 
            } 
            mCompiledQueries.clear(); 
        } 
    } 
 
    /**
     * from the compiledQueries cache, returns the compiled-statement-id for the given sql. 
     * returns null, if not found in the cache. 
     */
 
    /* package */ SQLiteCompiledSql getCompiledStatementForSql(String sql) { 
        SQLiteCompiledSql compiledStatement = null
        boolean cacheHit; 
        synchronized(mCompiledQueries) { 
            if (mMaxSqlCacheSize == 0) { 
                // for this database, there is no cache of compiled sql. 
                if (SQLiteDebug.DEBUG_SQL_CACHE) { 
                    Log.v(TAG, "|cache NOT found|" + getPath()); 
                } 
                return null
            } 
            cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null
        } 
        if (cacheHit) { 
            mNumCacheHits++; 
        } else { 
            mNumCacheMisses++; 
        } 
 
        if (SQLiteDebug.DEBUG_SQL_CACHE) { 
            Log.v(TAG, "|cache_stats|" + 
                  getPath() + "|" + mCompiledQueries.size() + 
                  "|" + mNumCacheHits + "|" + mNumCacheMisses + 
                  "|" + cacheHit + "|" + mTimeOpened + "|" + mTimeClosed + "|" + sql); 
        } 
        return compiledStatement; 
    } 
 
    /**
     * returns true if the given sql is cached in compiled-sql cache. 
     * @hide 
     */
 
    public boolean isInCompiledSqlCache(String sql) { 
        synchronized(mCompiledQueries) { 
            return mCompiledQueries.containsKey(sql); 
        } 
    } 
 
    /**
     * purges the given sql from the compiled-sql cache. 
     * @hide 
     */
 
    public void purgeFromCompiledSqlCache(String sql) { 
        synchronized(mCompiledQueries) { 
            mCompiledQueries.remove(sql); 
        } 
    } 
 
    /**
     * remove everything from the compiled sql cache 
     * @hide 
     */
 
    public void resetCompiledSqlCache() { 
        synchronized(mCompiledQueries) { 
            mCompiledQueries.clear(); 
        } 
    } 
 
    /**
     * return the current maxCacheSqlCacheSize 
     * @hide 
     */
 
    public synchronized int getMaxSqlCacheSize() { 
        return mMaxSqlCacheSize; 
    } 
 
    /**
     * set the max size of the compiled sql cache for this database after purging the cache. 
     * (size of the cache = number of compiled-sql-statements stored in the cache). 
     * 
     * max cache size can ONLY be increased from its current size (default = 0). 
     * if this method is called with smaller size than the current value of mMaxSqlCacheSize, 
     * then IllegalStateException is thrown 
     * 
     * synchronized because we don't want t threads to change cache size at the same time. 
     * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE) 
     * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or 
     * < the value set with previous setMaxSqlCacheSize() call. 
     * 
     * @hide 
     */
 
    public synchronized void setMaxSqlCacheSize(int cacheSize) { 
        if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { 
            throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE); 
        } else if (cacheSize < mMaxSqlCacheSize) { 
            throw new IllegalStateException("cannot set cacheSize to a value less than the value " + 
                                            "set with previous setMaxSqlCacheSize() call."); 
        } 
        mMaxSqlCacheSize = cacheSize; 
    } 
 
    static class ActiveDatabases { 
        private static final ActiveDatabases activeDatabases = new ActiveDatabases(); 
        private HashSet<WeakReference<SQLiteDatabase>> mActiveDatabases = 
            new HashSet<WeakReference<SQLiteDatabase>>(); 
        private ActiveDatabases() {} // disable instantiation of this class 
        static ActiveDatabases getInstance() {return activeDatabases;} 
    } 
 
    /**
     * this method is used to collect data about ALL open databases in the current process. 
     * bugreport is a user of this data. 
     */
 
    /* package */ static ArrayList<DbStats> getDbStats() { 
        ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>(); 
        for (WeakReference<SQLiteDatabase> w : ActiveDatabases.getInstance().mActiveDatabases) { 
            SQLiteDatabase db = w.get(); 
            if (db == null || !db.isOpen()) { 
                continue
            } 
            // 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 
            ArrayList<Pair<String, String>> attachedDbs = getAttachedDbs(db); 
            if (attachedDbs == null) { 
                continue
            } 
            for (int i = 0; i < attachedDbs.size(); i++) { 
                Pair<String, String> p = attachedDbs.get(i); 
                long pageCount = getPragmaVal(db, p.first + ".page_count;"); 
 
                // 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)); 
                } 
            } 
        } 
        return dbStatsList; 
    } 
 
    /**
     * get the specified pragma value from sqlite for the specified database. 
     * only handles pragma's that return int/long. 
     * NO JAVA locks are held in this method. 
     * TODO: use this to do all pragma's in this class 
     */
 
    private static long getPragmaVal(SQLiteDatabase db, String pragma) { 
        if (!db.isOpen()) { 
            return 0
        } 
        SQLiteStatement prog = null
        try { 
            prog = new SQLiteStatement(db, "PRAGMA " + pragma); 
            long val = prog.simpleQueryForLong(); 
            return val; 
        } finally { 
            if (prog != null) prog.close(); 
        } 
    } 
 
    /**
     * returns list of full pathnames of all attached databases 
     * including the main database 
     * TODO: move this to {@link DatabaseUtils} 
     */
 
    private static ArrayList<Pair<String, String>> getAttachedDbs(SQLiteDatabase dbObj) { 
        if (!dbObj.isOpen()) { 
            return null
        } 
        ArrayList<Pair<String, String>> attachedDbs = new ArrayList<Pair<String, String>>(); 
        Cursor c = dbObj.rawQuery("pragma database_list;"null); 
        while (c.moveToNext()) { 
            attachedDbs.add(new Pair<String, String>(c.getString(1), c.getString(2))); 
        } 
        c.close(); 
        return attachedDbs; 
    } 
 
    /**
     * Sets the root directory to search for the ICU data file 
     */
 
    public static native void setICURoot(String path); 
     
    /**
     * Native call to open the database. 
     * 
     * @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 
     * 
     * @param path the full path to the database 
     */
 
    private native void enableSqlTracing(String path); 
 
    /**
     * 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. 
     * 
     * @param path the full path to the database 
     */
 
    private native void enableSqlProfiling(String path); 
 
    /**
     * Native call to execute a raw SQL statement. {@link #lock} must be held 
     * when calling this method. 
     * 
     * @param sql The raw SQL string 
     * @throws SQLException 
     */
 
    /* package */ native void native_execSQL(String sql) throws SQLException; 
 
    /**
     * Native call to set the locale.  {@link #lock} must be held when calling 
     * this method. 
     * @throws SQLException 
     */
 
    /* package */ native void native_setLocale(String loc, int flags); 
 
    /**
     * Returns the row ID of the last row inserted into the database. 
     * 
     * @return the row ID of the last row inserted into the database. 
     */
 
    /* package */ native long lastInsertRow(); 
 
    /**
     * Returns the number of changes made in the last statement executed. 
     * 
     * @return the number of changes made in the last statement executed. 
     */
 
    /* package */ native int lastChangeCount(); 
 
    /**
     * 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 
     */
 
    private native int native_getDbLookaside(); 
 
    private native void native_rawExecSQL(String sql); 
 
    private native int native_status(int operation, boolean reset); 
}