> additionalValues) {
if (!supportsUpdates()) {
+ if(BuildConfig.DEBUG){
Log.e(TAG, "commitUpdates not supported on this cursor, did you "
- + "include the _id column?");
- return false;
+ + "include the _id column?");
+ }
+ return false;
}
/*
@@ -496,13 +530,13 @@ public boolean commitUpdates(Map extends Long,
}
private void deactivateCommon() {
- if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
+ if(BuildConfig.DEBUG) Log.v(TAG, "<<< Releasing cursor " + this);
mCursorState = 0;
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
- if (Config.LOGV) Log.v("DatabaseWindow", "closing window in release()");
+ if(BuildConfig.DEBUG) Log.v("DatabaseWindow", "closing window in release()");
}
@Override
@@ -553,21 +587,21 @@ public boolean requery() {
mDatabase.unlock();
}
- if (Config.LOGV) {
- Log.v("DatabaseWindow", "closing window in requery()");
- Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
+ if(BuildConfig.DEBUG){
+ Log.v("DatabaseWindow", "closing window in requery()");
+ Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
}
boolean result = super.requery();
- if (Config.LOGV) {
- long timeEnd = System.currentTimeMillis();
- Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
+ if(BuildConfig.DEBUG){
+ long timeEnd = System.currentTimeMillis();
+ Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
}
return result;
}
@Override
- public void setWindow(CursorWindow window) {
+ public void setWindow(CursorWindow window) {
if (mWindow != null) {
mCursorState++;
queryThreadLock();
@@ -597,16 +631,18 @@ protected void finalize() {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
int len = mQuery.mSql.length();
- Log.e(TAG, "Finalizing a Cursor that has not been deactivated or closed. " +
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Finalizing a Cursor that has not been deactivated or closed. " +
"database = " + mDatabase.getPath() + ", table = " + mEditTable +
", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len),
mStackTrace);
+ }
close();
SQLiteDebug.notifyActiveCursorFinalized();
} else {
- if (Config.LOGV) {
- Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() +
- ", table = " + mEditTable + ", query = " + mQuery.mSql);
+ if(BuildConfig.DEBUG) {
+ Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() +
+ ", table = " + mEditTable + ", query = " + mQuery.mSql);
}
}
} finally {
@@ -615,42 +651,50 @@ protected void finalize() {
}
-
+
@Override
- public void fillWindow(int startPos, android.database.CursorWindow window) {
-
- /*
- window.setStartPosition(startPos);
- mCount = mQuery.fillWindow((net.sqlcipher.database.CursorWindow)window, mInitialRead, 0);
- // return -1 means not finished
- if (mCount == NO_COUNT){
- mCount = startPos + mInitialRead;
- Thread t = new Thread(new QueryThread(mCursorState), "query thread");
- t.start();
- } */
-
- if (mWindow == null) {
- // If there isn't a window set already it will only be accessed locally
- mWindow = new CursorWindow(true /* the window is local only */);
- } else {
- mCursorState++;
- queryThreadLock();
- try {
- mWindow.clear();
- } finally {
- queryThreadUnlock();
- }
- }
- mWindow.setStartPosition(startPos);
- mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
- // return -1 means not finished
- if (mCount == NO_COUNT){
- mCount = startPos + mInitialRead;
- Thread t = new Thread(new QueryThread(mCursorState), "query thread");
- t.start();
+ public void fillWindow(int requiredPos, android.database.CursorWindow window) {
+ int startPos = 0;
+ if (mWindow == null) {
+ // If there isn't a window set already it will only be accessed locally
+ mWindow = new CursorWindow(true /* the window is local only */);
+ } else {
+ mCursorState++;
+ queryThreadLock();
+ try {
+ mWindow.clear();
+ } finally {
+ queryThreadUnlock();
}
-
-
+ }
+ if(fillWindowForwardOnly) {
+ startPos = requiredPos;
+ } else {
+ startPos = mCount == NO_COUNT
+ ? cursorPickFillWindowStartPosition(requiredPos, 0)
+ : cursorPickFillWindowStartPosition(requiredPos, mCursorWindowCapacity);
+ }
+ mWindow.setStartPosition(startPos);
+ mWindow.setRequiredPosition(requiredPos);
+ if(BuildConfig.DEBUG) {
+ Log.v(TAG, String.format("Filling cursor window with start position:%d required position:%d",
+ startPos, requiredPos));
+ }
+ mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
+ if(mCursorWindowCapacity == 0) {
+ mCursorWindowCapacity = mWindow.getNumRows();
+ }
+ // return -1 means not finished
+ if (mCount == NO_COUNT){
+ mCount = startPos + mInitialRead;
+ Thread t = new Thread(new QueryThread(mCursorState), "query thread");
+ t.start();
+ }
}
+ public int cursorPickFillWindowStartPosition(
+ int cursorPosition, int cursorWindowCapacity) {
+ return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
+ }
+
}
diff --git a/src/net/sqlcipher/database/SQLiteCursorDriver.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java
similarity index 99%
rename from src/net/sqlcipher/database/SQLiteCursorDriver.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java
index 93ad1503..1ea66aba 100644
--- a/src/net/sqlcipher/database/SQLiteCursorDriver.java
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteCursorDriver.java
@@ -26,7 +26,7 @@
public interface SQLiteCursorDriver {
/**
* Executes the query returning a Cursor over the result set.
- *
+ *
* @param factory The CursorFactory to use when creating the Cursors, or
* null if standard SQLiteCursors should be returned.
* @return a Cursor over the result set
@@ -40,7 +40,7 @@ public interface SQLiteCursorDriver {
/**
* Called by a SQLiteCursor when it is requeryed.
- *
+ *
* @return The new count value.
*/
void cursorRequeried(android.database.Cursor cursor);
diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java
new file mode 100644
index 00000000..a4419653
--- /dev/null
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabase.java
@@ -0,0 +1,3272 @@
+/*
+ * 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.BuildConfig;
+import net.sqlcipher.Cursor;
+import net.sqlcipher.CrossProcessCursorWrapper;
+import net.sqlcipher.DatabaseUtils;
+import net.sqlcipher.DatabaseErrorHandler;
+import net.sqlcipher.DefaultDatabaseErrorHandler;
+import net.sqlcipher.database.SQLiteStatement;
+import net.sqlcipher.database.SQLiteDebug.DbStats;
+import net.sqlcipher.database.SQLiteDatabaseHook;
+import net.sqlcipher.database.SQLiteQueryStats;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+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.SQLException;
+import android.database.sqlite.SQLiteDatabaseCorruptException;
+import android.database.sqlite.SQLiteException;
+
+import android.os.CancellationSignal;
+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 java.io.UnsupportedEncodingException;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteQuery;
+
+/**
+ * Exposes methods to manage a SQLCipher database.
+ * SQLiteDatabase has methods to create, delete, execute SQL commands, and
+ * perform other common database management tasks.
+ *
A call to loadLibs(…)
should occur before attempting to
+ * create or open a database connection.
+ *
Database names must be unique within an application, not across all
+ * applications.
+ *
+ */
+public class SQLiteDatabase extends SQLiteClosable implements
+ SupportSQLiteDatabase {
+ private static final String TAG = "Database";
+ private static final int EVENT_DB_OPERATION = 52000;
+ private static final int EVENT_DB_CORRUPT = 75004;
+ private static final String KEY_ENCODING = "UTF-8";
+
+ private enum SQLiteDatabaseTransactionType {
+ Deferred,
+ Immediate,
+ Exclusive,
+ }
+
+ /**
+ * The version number of the SQLCipher for Android Java client library.
+ */
+ public static final String SQLCIPHER_ANDROID_VERSION = BuildConfig.VERSION_NAME;
+
+ // Stores reference to all databases opened in the current process.
+ // (The referent Object is not used at this time.)
+ // INVARIANT: Guarded by sActiveDatabases.
+ private static WeakHashMap sActiveDatabases =
+ new WeakHashMap();
+
+ public int status(int operation, boolean reset){
+ return native_status(operation, reset);
+ }
+
+ /**
+ * Change the password of the open database using sqlite3_rekey().
+ *
+ * @param password new database password
+ *
+ * @throws SQLiteException if there is an issue changing the password internally
+ * OR if the database is not open
+ *
+ * FUTURE @todo throw IllegalStateException if the database is not open and
+ * update the test suite
+ */
+ public void changePassword(String password) throws SQLiteException {
+ /* safeguard: */
+ if (!isOpen()) {
+ throw new SQLiteException("database not open");
+ }
+ if (password != null) {
+ byte[] keyMaterial = getBytes(password.toCharArray());
+ rekey(keyMaterial);
+ Arrays.fill(keyMaterial, (byte) 0);
+ }
+ }
+
+ /**
+ * Change the password of the open database using sqlite3_rekey().
+ *
+ * @param password new database password (char array)
+ *
+ * @throws SQLiteException if there is an issue changing the password internally
+ * OR if the database is not open
+ *
+ * FUTURE @todo throw IllegalStateException if the database is not open and
+ * update the test suite
+ */
+ public void changePassword(char[] password) throws SQLiteException {
+ /* safeguard: */
+ if (!isOpen()) {
+ throw new SQLiteException("database not open");
+ }
+ if (password != null) {
+ byte[] keyMaterial = getBytes(password);
+ rekey(keyMaterial);
+ Arrays.fill(keyMaterial, (byte) 0);
+ }
+ }
+
+ private static void loadICUData(Context context, File workingDir) {
+ OutputStream out = null;
+ ZipInputStream in = null;
+ File icuDir = new File(workingDir, "icu");
+ File icuDataFile = new File(icuDir, "icudt46l.dat");
+ try {
+ if(!icuDir.exists()) icuDir.mkdirs();
+ if(!icuDataFile.exists()) {
+ in = new ZipInputStream(context.getAssets().open("icudt46l.zip"));
+ in.getNextEntry();
+ out = new FileOutputStream(icuDataFile);
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ }
+ }
+ catch (Exception ex) {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Error copying icu dat file", ex);
+ }
+ if(icuDataFile.exists()){
+ icuDataFile.delete();
+ }
+ throw new RuntimeException(ex);
+ }
+ finally {
+ try {
+ if(in != null){
+ in.close();
+ }
+ if(out != null){
+ out.flush();
+ out.close();
+ }
+ } catch (IOException ioe){
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Error in closing streams IO streams after expanding ICU dat file", ioe);
+ }
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ /**
+ * Implement this interface to provide custom strategy for loading jni libraries.
+ */
+ public interface LibraryLoader {
+ /**
+ * Load jni libraries by given names.
+ * Straightforward implementation will be calling {@link System#loadLibrary(String name)}
+ * for every provided library name.
+ *
+ * @param libNames library names that sqlcipher need to load
+ */
+ void loadLibraries(String... libNames);
+ }
+
+ /**
+ * Loads the native SQLCipher library into the application process.
+ */
+ public static synchronized void loadLibs (Context context) {
+ loadLibs(context, context.getFilesDir());
+ }
+
+ /**
+ * Loads the native SQLCipher library into the application process.
+ */
+ public static synchronized void loadLibs (Context context, File workingDir) {
+ loadLibs(context, workingDir, new LibraryLoader() {
+ @Override
+ public void loadLibraries(String... libNames) {
+ for (String libName : libNames) {
+ System.loadLibrary(libName);
+ }
+ }
+ });
+ }
+
+ /**
+ * Loads the native SQLCipher library into the application process.
+ */
+ public static synchronized void loadLibs(Context context, LibraryLoader libraryLoader) {
+ loadLibs(context, context.getFilesDir(), libraryLoader);
+ }
+
+ /**
+ * Loads the native SQLCipher library into the application process.
+ */
+ public static synchronized void loadLibs (Context context, File workingDir, LibraryLoader libraryLoader) {
+ libraryLoader.loadLibraries("sqlcipher");
+
+ // 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 LOCALIZED
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
+
+ /**
+ * SQLite memory database name
+ */
+ public static final String MEMORY = ":memory:";
+
+ /**
+ * 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 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 */ long 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 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 mCompiledQueries = new HashMap();
+ /**
+ * @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;
+
+ /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors
+ * Corruption
+ * */
+ private final DatabaseErrorHandler mErrorHandler;
+
+ /** 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();
+
+ synchronized (sActiveDatabases) {
+ sActiveDatabases.remove(this);
+ }
+ }
+ }
+
+ /**
+ * 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() {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Calling error handler for corrupt database (detected) " + mPath);
+ }
+
+ // NOTE: DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database
+ mErrorHandler.onCorruption(this);
+ }
+
+ /**
+ * Locks the database for exclusive access. The database lock must be held when
+ * touch the native sqlite3* object since it is single threaded and uses
+ * a polling lock contention algorithm. The lock is recursive, and may be acquired
+ * multiple times by the same thread. This is a no-op if mLockingEnabled is false.
+ *
+ * @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) {
+ if(BuildConfig.DEBUG){
+ Log.d(TAG, msg, new Exception());
+ }
+ } else {
+ if(BuildConfig.DEBUG){
+ Log.d(TAG, msg);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs a PRAGMA integrity_check; command against the database.
+ * @return true if the integrity check is ok, otherwise false
+ */
+ public boolean isDatabaseIntegrityOk() {
+ Pair result = getResultFromPragma("PRAGMA integrity_check;");
+ return result.first ? result.second.equals("ok") : result.first;
+ }
+
+ /**
+ * Returns a list of attached databases including the main database
+ * by executing PRAGMA database_list
+ * @return a list of pairs of database name and filename
+ */
+ public List> getAttachedDbs() {
+ return getAttachedDbs(this);
+ }
+
+ /**
+ * Sets the journal mode of the database to WAL
+ * @return true if successful, false otherwise
+ */
+ public boolean enableWriteAheadLogging() {
+ if(inTransaction()) {
+ String message = "Write Ahead Logging cannot be enabled while in a transaction";
+ throw new IllegalStateException(message);
+ }
+ List> attachedDbs = getAttachedDbs(this);
+ if(attachedDbs != null && attachedDbs.size() > 1) return false;
+ if(isReadOnly() || getPath().equals(MEMORY)) return false;
+ String command = "PRAGMA journal_mode = WAL;";
+ rawExecSQL(command);
+ return true;
+ }
+
+ /**
+ * Sets the journal mode of the database to DELETE (the default mode)
+ */
+ public void disableWriteAheadLogging() {
+ if(inTransaction()) {
+ String message = "Write Ahead Logging cannot be disabled while in a transaction";
+ throw new IllegalStateException(message);
+ }
+ String command = "PRAGMA journal_mode = DELETE;";
+ rawExecSQL(command);
+ }
+
+ /**
+ * @return true if the journal mode is set to WAL, otherwise false
+ */
+ public boolean isWriteAheadLoggingEnabled() {
+ Pair result = getResultFromPragma("PRAGMA journal_mode;");
+ return result.first ? result.second.equals("wal") : result.first;
+ }
+
+ /**
+ * Enables or disables foreign key constraints
+ * @param enable used to determine whether or not foreign key constraints are on
+ */
+ public void setForeignKeyConstraintsEnabled(boolean enable) {
+ if(inTransaction()) {
+ String message = "Foreign key constraints may not be changed while in a transaction";
+ throw new IllegalStateException(message);
+ }
+ String command = String.format("PRAGMA foreign_keys = %s;",
+ enable ? "ON" : "OFF");
+ execSQL(command);
+ }
+
+ /**
+ * 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.
+ *
+ * Here is the standard idiom for transactions:
+ *
+ *
+ * db.beginTransaction();
+ * try {
+ * ...
+ * db.setTransactionSuccessful();
+ * } finally {
+ * db.endTransaction();
+ * }
+ *
+ *
+ * @throws IllegalStateException if the database is not open
+ */
+ public void beginTransaction() {
+ beginTransactionWithListener((SQLiteTransactionListener)null /* transactionStatusCallback */);
+ }
+
+ /**
+ * Begins a transaction in Exlcusive mode. 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.
+ *
+ * Here is the standard idiom for transactions:
+ *
+ *
+ * db.beginTransactionWithListener(listener);
+ * try {
+ * ...
+ * db.setTransactionSuccessful();
+ * } finally {
+ * db.endTransaction();
+ * }
+ *
+ * @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}.
+ *
+ * @throws IllegalStateException if the database is not open
+ */
+ public void beginTransactionWithListener(SQLiteTransactionListener transactionListener) {
+ beginTransactionWithListenerInternal(transactionListener,
+ SQLiteDatabaseTransactionType.Exclusive);
+ }
+
+ /**
+ * Begins a transaction in Immediate mode
+ */
+ public void beginTransactionNonExclusive() {
+ beginTransactionWithListenerInternal(null,
+ SQLiteDatabaseTransactionType.Immediate);
+ }
+
+ /**
+ * Begins a transaction in Immediate mode
+ * @param transactionListener is the listener used to report transaction events
+ */
+ public void beginTransactionWithListenerNonExclusive(SQLiteTransactionListener transactionListener) {
+ beginTransactionWithListenerInternal(transactionListener,
+ SQLiteDatabaseTransactionType.Immediate);
+ }
+
+ /**
+ * End a transaction. See beginTransaction for notes about how to use this and when transactions
+ * are committed and rolled back.
+ *
+ * @throws IllegalStateException if the database is not open or is not locked by the current thread
+ */
+ 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(BuildConfig.DEBUG){
+ Log.d(TAG, "exception during rollback, maybe the DB previously "
+ + "performed an auto-rollback");
+ }
+ }
+ }
+ } finally {
+ mTransactionListener = null;
+ unlockForced();
+ if(BuildConfig.DEBUG){
+ 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 database is not open, 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 transaction was yielded, false if queue was empty or database was not open
+ */
+ 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() {
+ /* safeguard: */
+ if (!isOpen()) return false;
+
+ 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, false if queue was empty or database was not open
+ */
+ public boolean yieldIfContendedSafely() {
+ /* safeguard: */
+ if (!isOpen()) return false;
+
+ 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, false if queue was empty or database was not open
+ *
+ * @throws IllegalStateException if the database is locked more than once by the current thread
+ * @throws InterruptedException if the thread was interrupted while sleeping
+ */
+ public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
+ /* safeguard: */
+ if (!isOpen()) return false;
+
+ 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 mSyncUpdateInfo =
+ new HashMap();
+
+ public Map getSyncedTables() {
+ synchronized(mSyncUpdateInfo) {
+ HashMap tables = new HashMap();
+ 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}.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path to database file to open and/or create
+ * @param password to use to open and/or create database file
+ * @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 and other options
+ *
+ * @return the newly opened database
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags) {
+ return openDatabase(path, password, factory, flags, null);
+ }
+
+ /**
+ * Open the database according to the flags {@link #OPEN_READWRITE}
+ * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
+ *
+ * Sets the locale of the database to the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path to database file to open and/or create
+ * @param password to use to open and/or create database file (char array)
+ * @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 and other options
+ *
+ * @return the newly opened database
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags) {
+ return openDatabase(path, password, factory, flags, null, null);
+ }
+
+ /**
+ * Open the database according to the flags {@link #OPEN_READWRITE}
+ * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
+ * with optional hook to run on pre/post key events.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path to database file to open and/or create
+ * @param password to use to open and/or create database file
+ * @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 and other options
+ * @param hook to run on pre/post key events
+ *
+ * @return the newly opened database
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) {
+ return openDatabase(path, password, factory, flags, hook, null);
+ }
+
+ /**
+ * Open the database according to the flags {@link #OPEN_READWRITE}
+ * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
+ * with optional hook to run on pre/post key events.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path to database file to open and/or create
+ * @param password to use to open and/or create database file (char array)
+ * @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 and other options
+ * @param hook to run on pre/post key events (may be null)
+ *
+ * @return the newly opened database
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) {
+ return openDatabase(path, password, factory, flags, hook, null);
+ }
+
+ /**
+ * Open the database according to the flags {@link #OPEN_READWRITE}
+ * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
+ * with optional hook to run on pre/post key events.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path to database file to open and/or create
+ * @param password to use to open and/or create database file
+ * @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 and other options
+ * @param hook to run on pre/post key events
+ * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
+ * corruption (or null for default).
+ *
+ * @return the newly opened database
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags,
+ SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) {
+ return openDatabase(path, password == null ? null : password.toCharArray(), factory, flags, hook, errorHandler);
+ }
+
+/**
+ * Open the database according to the flags {@link #OPEN_READWRITE}
+ * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
+ * with optional hook to run on pre/post key events.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path to database file to open and/or create
+ * @param password to use to open and/or create database file (char array)
+ * @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 and other options
+ * @param hook to run on pre/post key events (may be null)
+ * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
+ * corruption (or null for default).
+ *
+ * @return the newly opened database
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags,
+ SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) {
+ byte[] keyMaterial = getBytes(password);
+ return openDatabase(path, keyMaterial, factory, flags, hook, errorHandler);
+ }
+
+ /**
+ * Open the database according to the flags {@link #OPEN_READWRITE}
+ * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
+ * with optional hook to run on pre/post key events.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path to database file to open and/or create
+ * @param password to use to open and/or create database file (byte array)
+ * @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 and other options
+ * @param hook to run on pre/post key events (may be null)
+ * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
+ * corruption (or null for default).
+ *
+ * @return the newly opened database
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public static SQLiteDatabase openDatabase(String path, byte[] password, CursorFactory factory, int flags,
+ SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) {
+ SQLiteDatabase sqliteDatabase = null;
+ DatabaseErrorHandler myErrorHandler = (errorHandler != null) ? errorHandler : new DefaultDatabaseErrorHandler();
+
+ try {
+ // Open the database.
+ sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler);
+ sqliteDatabase.openDatabaseInternal(password, hook);
+ } catch (SQLiteDatabaseCorruptException e) {
+ // Try to recover from this, if possible.
+ // FUTURE TBD: should we consider this for other open failures?
+
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Calling error handler for corrupt database " + path, e);
+ }
+
+ // NOTE: if this errorHandler.onCorruption() throws the exception _should_
+ // bubble back to the original caller.
+ // DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database
+ myErrorHandler.onCorruption(sqliteDatabase);
+
+ // try *once* again:
+ sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler);
+ sqliteDatabase.openDatabaseInternal(password, hook);
+ }
+
+ if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
+ sqliteDatabase.enableSqlTracing(path);
+ }
+ if (SQLiteDebug.DEBUG_SQL_TIME) {
+ sqliteDatabase.enableSqlProfiling(path);
+ }
+
+ synchronized (sActiveDatabases) {
+ sActiveDatabases.put(sqliteDatabase, null);
+ }
+
+ return sqliteDatabase;
+ }
+
+ /**
+ * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY, databaseHook).
+ */
+ public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
+ return openOrCreateDatabase(file, password, factory, databaseHook, null);
+ }
+
+ /**
+ * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook).
+ */
+ public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
+ DatabaseErrorHandler errorHandler) {
+ return openOrCreateDatabase(file == null ? null : file.getPath(), password, factory, databaseHook, errorHandler);
+ }
+
+ /**
+ * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook).
+ */
+ public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook);
+ }
+
+ public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
+ DatabaseErrorHandler errorHandler) {
+ return openDatabase(path, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
+ }
+
+ public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook);
+ }
+
+ public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
+ DatabaseErrorHandler errorHandler) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
+ }
+
+ public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, null);
+ }
+
+ public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
+ DatabaseErrorHandler errorHandler) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
+ }
+
+ /**
+ * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY).
+ */
+ public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory) {
+ return openOrCreateDatabase(file, password, factory, null);
+ }
+
+ /**
+ * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY).
+ */
+ public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null);
+ }
+
+ /**
+ * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY).
+ */
+ public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null);
+ }
+
+ /**
+ * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY).
+ */
+ public static SQLiteDatabase openOrCreateDatabase(String path, byte[] password, CursorFactory factory) {
+ return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null, null);
+ }
+
+ /**
+ * Create a memory backed SQLite database. Its contents will be destroyed
+ * when the database is closed.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param factory an optional factory class that is called to instantiate a
+ * cursor when query is called
+ * @param password to use to open and/or create database file
+ *
+ * @return a SQLiteDatabase object, or null if the database can't be created
+ *
+ * @throws SQLiteException if the database cannot be opened
+ */
+ public static SQLiteDatabase create(CursorFactory factory, String password) {
+ // This is a magic string with special meaning for SQLite.
+ return openDatabase(MEMORY, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY);
+ }
+
+ /**
+ * Create a memory backed SQLite database. Its contents will be destroyed
+ * when the database is closed.
+ *
+ * Sets the locale of the database to the the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param factory an optional factory class that is called to instantiate a
+ * cursor when query is called
+ * @param password to use to open and/or create database file (char array)
+ *
+ * @return a SQLiteDatabase object, or null if the database can't be created
+ *
+ * @throws SQLiteException if the database cannot be opened
+ */
+ public static SQLiteDatabase create(CursorFactory factory, char[] password) {
+ 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> iter = mPrograms.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry 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
+ *
+ * @throws IllegalStateException if the database is not open
+ */
+ public int getVersion() {
+ SQLiteStatement prog = null;
+ lock();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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
+ *
+ * @throws SQLiteException if there is an issue executing the sql internally
+ * @throws IllegalStateException if the database is not open
+ */
+ 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();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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
+ *
+ * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable
+ * OR if the database is not open
+ *
+ * FUTURE @todo throw IllegalStateException if the database is not open and
+ * update the test suite
+ *
+ * NOTE: This method was deprecated by the AOSP in Android API 11.
+ */
+ public void markTableSyncable(String table, String deletedTable) {
+ /* safeguard: */
+ if (!isOpen()) {
+ throw new SQLiteException("database not open");
+ }
+
+ 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
+ *
+ * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable
+ *
+ * FUTURE @todo throw IllegalStateException if the database is not open and
+ * update the test suite
+ *
+ * NOTE: This method was deprecated by the AOSP in Android API 11.
+ */
+ public void markTableSyncable(String table, String foreignKey,
+ String updateTable) {
+ /* safeguard: */
+ if (!isOpen()) {
+ throw new SQLiteException("database not open");
+ }
+
+ 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
+ *
+ * @throws SQLiteException if there is an issue executing the sql
+ */
+ 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.
+ *
+ * @throws SQLException If the SQL string is invalid for some reason
+ * @throws IllegalStateException if the database is not open
+ */
+ public SQLiteStatement compileStatement(String sql) throws SQLException {
+ lock();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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.
+ *
+ * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
+ * @throws IllegalStateException if the database is not open
+ *
+ * @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.
+ *
+ * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
+ * @throws IllegalStateException if the database is not open
+ *
+ * @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.
+ *
+ * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
+ * @throws IllegalStateException if the database is not open
+ *
+ * @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.
+ *
+ * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
+ * @throws IllegalStateException if the database is not open
+ */
+ public Cursor rawQuery(String sql, String[] selectionArgs) {
+ return rawQueryWithFactory(null, sql, selectionArgs, null);
+ }
+
+ /**
+ * Determines the total size in bytes of the query results, and the largest
+ * single row in bytes for the query.
+ *
+ * @param sql the SQL query. The SQL string must a SELECT statement
+ * @param args the argments to bind to the query
+ *
+ * @return A {@link SQLiteQueryStats} based the provided SQL query.
+ */
+ public SQLiteQueryStats getQueryStats(String sql, Object[] args){
+ long totalPayload = 0L;
+ long largestIndividualPayload = 0L;
+ try {
+ String query = String.format("CREATE TABLE tempstat AS %s", sql);
+ execSQL(query, args);
+ Cursor cursor = rawQuery("SELECT sum(payload) FROM dbstat WHERE name = 'tempstat';", new Object[]{});
+ if(cursor == null) return new SQLiteQueryStats(totalPayload, largestIndividualPayload);
+ cursor.moveToFirst();
+ totalPayload = cursor.getLong(0);
+ cursor.close();
+ cursor = rawQuery("SELECT max(mx_payload) FROM dbstat WHERE name = 'tempstat';", new Object[]{});
+ if(cursor == null) return new SQLiteQueryStats(totalPayload, largestIndividualPayload);
+ cursor.moveToFirst();
+ largestIndividualPayload = cursor.getLong(0);
+ cursor.close();
+ execSQL("DROP TABLE tempstat;");
+ } catch(SQLiteException ex) {
+ execSQL("DROP TABLE IF EXISTS tempstat;");
+ throw ex;
+ }
+ return new SQLiteQueryStats(totalPayload, largestIndividualPayload);
+ }
+
+ /**
+ * 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 args You may include ?s in where clause in the query,
+ * which will be replaced by the values from args. The
+ * values will be bound by their type.
+ *
+ * @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.
+ *
+ * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
+ * @throws IllegalStateException if the database is not open
+ */
+ public Cursor rawQuery(String sql, Object[] args) {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ long timeStart = 0;
+ if (Config.LOGV || mSlowQueryThreshold != -1) {
+ timeStart = System.currentTimeMillis();
+ }
+ SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, null);
+ Cursor cursor = null;
+ try {
+ cursor = driver.query(mFactory, args);
+ } finally {
+ if (Config.LOGV || mSlowQueryThreshold != -1) {
+ // Force query execution
+ int count = -1;
+ if (cursor != null) {
+ count = cursor.getCount();
+ }
+
+ long duration = System.currentTimeMillis() - timeStart;
+
+ if (BuildConfig.DEBUG || duration >= mSlowQueryThreshold) {
+ Log.v(TAG,
+ "query (" + duration + " ms): " + driver.toString() +
+ ", args are , count is " + count);
+ }
+ }
+ }
+ return new CrossProcessCursorWrapper(cursor);
+ }
+
+ /**
+ * 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.
+ *
+ * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
+ * @throws IllegalStateException if the database is not open
+ */
+ 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 (BuildConfig.DEBUG || duration >= mSlowQueryThreshold) {
+ Log.v(TAG,
+ "query (" + duration + " ms): " + driver.toString() +
+ ", args are , 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) {
+ net.sqlcipher.CursorWrapper cursorWrapper = (net.sqlcipher.CursorWrapper)rawQueryWithFactory(null, sql, selectionArgs, null);
+ ((SQLiteCursor)cursorWrapper.getWrappedCursor()).setLoadStyle(initialRead, maxRead);
+ return cursorWrapper;
+ }
+
+ /**
+ * 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) {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Error inserting into " + table, 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) {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Error inserting into " + table, 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
+ *
+ * @throws SQLException If the SQL string is invalid for some reason
+ * @throws IllegalStateException if the database is not open
+ */
+ 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> entrySet = null;
+ if (initialValues != null && initialValues.size() > 0) {
+ entrySet = initialValues.valueSet();
+ Iterator> entriesIter = entrySet.iterator();
+ sql.append('(');
+
+ boolean needSeparator = false;
+ while (entriesIter.hasNext()) {
+ if (needSeparator) {
+ sql.append(", ");
+ values.append(", ");
+ }
+ needSeparator = true;
+ Map.Entry 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> entriesIter = entrySet.iterator();
+ for (int i = 0; i < size; i++) {
+ Map.Entry entry = entriesIter.next();
+ DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
+
+ }
+ }
+
+ // Run the program and then cleanup
+ statement.execute();
+
+ long insertedRowId = lastChangeCount() > 0 ? lastInsertRow() : -1;
+ if (insertedRowId == -1) {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Error inserting using into " + table);
+ }
+ } else {
+ if (BuildConfig.DEBUG && Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Inserting row " + insertedRowId +
+ " from using into " + table);
+ }
+ }
+ 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.
+ *
+ * @throws SQLException If the SQL string is invalid for some reason
+ * @throws IllegalStateException if the database is not open
+ */
+ public int delete(String table, String whereClause, String[] whereArgs) {
+ return delete(table, whereClause, (Object[])whereArgs);
+ }
+
+ /**
+ * 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.
+ *
+ * @throws SQLException If the SQL string is invalid for some reason
+ * @throws IllegalStateException if the database is not open
+ */
+ public int delete(String table, String whereClause, Object[] whereArgs) {
+ SQLiteStatement statement = null;
+ lock();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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
+ *
+ * @throws SQLException If the SQL string is invalid for some reason
+ * @throws IllegalStateException if the database is not open
+ */
+ 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
+ *
+ * @throws SQLException If the SQL string is invalid for some reason
+ * @throws IllegalStateException if the database is not open
+ */
+ 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> entrySet = values.valueSet();
+ Iterator> entriesIter = entrySet.iterator();
+
+ while (entriesIter.hasNext()) {
+ Map.Entry entry = entriesIter.next();
+ sql.append(entry.getKey());
+ sql.append("=?");
+ if (entriesIter.hasNext()) {
+ sql.append(", ");
+ }
+ }
+
+ if (!TextUtils.isEmpty(whereClause)) {
+ sql.append(" WHERE ");
+ sql.append(whereClause);
+ }
+ SQLiteStatement statement = null;
+ lock();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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 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 (BuildConfig.DEBUG && Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Updated " + numChangedRows +
+ " rows using and for " + table);
+ }
+ return numChangedRows;
+ } catch (SQLiteDatabaseCorruptException e) {
+ onCorruption();
+ throw e;
+ } catch (SQLException e) {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "Error updating using for " + table);
+ }
+ 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
+ * @throws IllegalStateException if the database is not open
+ */
+ public void execSQL(String sql) throws SQLException {
+ long timeStart = SystemClock.uptimeMillis();
+ lock();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ native_execSQL(sql);
+ } catch (SQLiteDatabaseCorruptException e) {
+ onCorruption();
+ throw e;
+ } finally {
+ unlock();
+ }
+ }
+
+ public void rawExecSQL(String sql){
+ long timeStart = SystemClock.uptimeMillis();
+ lock();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ native_rawExecSQL(sql);
+ } catch (SQLiteDatabaseCorruptException e) {
+ onCorruption();
+ throw e;
+ } finally {
+ 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,
+ *
+ * @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
+ * @throws IllegalStateException if the database is not open
+ */
+ public void execSQL(String sql, Object[] bindArgs) throws SQLException {
+ SQLiteStatement statement = null;
+ if (bindArgs == null) {
+ throw new IllegalArgumentException("Empty bindArgs");
+ }
+ long timeStart = SystemClock.uptimeMillis();
+ lock();
+ try {
+ if (!isOpen()) {
+ throw new IllegalStateException("database not open");
+ }
+ 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();
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ if (isOpen()) {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "close() was never explicitly called on database '" +
+ mPath + "' ", mStackTrace);
+ }
+ closeClosable();
+ onAllReferencesReleased();
+ }
+ }
+
+ /**
+ * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}.
+ *
+ * Sets the locale of the database to the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path The full path to the database
+ * @param password to use to open and/or create a database file (char array)
+ * @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.
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags) {
+ this(path, factory, flags, null);
+ this.openDatabaseInternal(password, null);
+ }
+
+ /**
+ * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}.
+ *
+ * Sets the locale of the database to the system's current locale.
+ * Call {@link #setLocale} if you would like something else.
+ *
+ * @param path The full path to the database
+ * @param password to use to open and/or create a database file (char array)
+ * @param factory The factory to use when creating cursors, may be NULL.
+ * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}. If the database file already
+ * exists, mFlags will be updated appropriately.
+ * @param databaseHook to run on pre/post key events
+ *
+ * @throws SQLiteException if the database cannot be opened
+ * @throws IllegalArgumentException if the database path is null
+ */
+ public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) {
+ this(path, factory, flags, null);
+ this.openDatabaseInternal(password, databaseHook);
+ }
+
+ public SQLiteDatabase(String path, byte[] password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) {
+ this(path, factory, flags, null);
+ this.openDatabaseInternal(password, databaseHook);
+ }
+
+ /**
+ * Private constructor (without database password) which DOES NOT attempt to open the database.
+ *
+ * @param path The full path to the database
+ * @param factory The factory to use when creating cursors, may be NULL.
+ * @param flags to control database access mode and other options
+ * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
+ * corruption (or null for default).
+ *
+ * @throws IllegalArgumentException if the database path is null
+ */
+ private SQLiteDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {
+ 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;
+ mPrograms = new WeakHashMap();
+
+ mErrorHandler = errorHandler;
+ }
+
+ private void openDatabaseInternal(final char[] password, SQLiteDatabaseHook hook) {
+ final byte[] keyMaterial = getBytes(password);
+ openDatabaseInternal(keyMaterial, hook);
+ }
+
+ private void openDatabaseInternal(final byte[] password, SQLiteDatabaseHook hook) {
+ boolean shouldCloseConnection = true;
+ dbopen(mPath, mFlags);
+ try {
+ keyDatabase(hook, new Runnable() {
+ public void run() {
+ if(password != null && password.length > 0) {
+ key(password);
+ }
+ }
+ });
+ shouldCloseConnection = false;
+
+ } catch(RuntimeException ex) {
+
+ final char[] keyMaterial = getChars(password);
+ if(containsNull(keyMaterial)) {
+ keyDatabase(hook, new Runnable() {
+ public void run() {
+ if(password != null) {
+ key_mutf8(keyMaterial);
+ }
+ }
+ });
+ if(password != null && password.length > 0) {
+ rekey(password);
+ }
+ shouldCloseConnection = false;
+ } else {
+ throw ex;
+ }
+ if(keyMaterial != null && keyMaterial.length > 0) {
+ Arrays.fill(keyMaterial, (char)0);
+ }
+
+ } finally {
+ if(shouldCloseConnection) {
+ dbclose();
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeClosed = getTime();
+ }
+ }
+ }
+
+ }
+
+ private boolean containsNull(char[] data) {
+ char defaultValue = '\u0000';
+ boolean status = false;
+ if(data != null && data.length > 0) {
+ for(char datum : data) {
+ if(datum == defaultValue) {
+ status = true;
+ break;
+ }
+ }
+ }
+ return status;
+ }
+
+ private void keyDatabase(SQLiteDatabaseHook databaseHook, Runnable keyOperation) {
+ if(databaseHook != null) {
+ databaseHook.preKey(this);
+ }
+ if(keyOperation != null){
+ keyOperation.run();
+ }
+ if(databaseHook != null){
+ databaseHook.postKey(this);
+ }
+ if (SQLiteDebug.DEBUG_SQL_CACHE) {
+ mTimeOpened = getTime();
+ }
+ try {
+ Cursor cursor = rawQuery("select count(*) from sqlite_master;", new String[]{});
+ if(cursor != null){
+ cursor.moveToFirst();
+ int count = cursor.getInt(0);
+ cursor.close();
+ }
+ } catch (RuntimeException e) {
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, e.getMessage(), e);
+ }
+ throw e;
+ }
+ }
+
+ private String getTime() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.US).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) {
+ /* NOTE: getVersion() will throw if database is not open. */
+ return newVersion > getVersion();
+ }
+
+ /**
+ * Getter for the path to the database file.
+ *
+ * @return the path to our database file.
+ */
+ public final String getPath() {
+ return mPath;
+ }
+
+ /**
+ * 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 && BuildConfig.DEBUG) {
+ 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 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 && BuildConfig.DEBUG) {
+ 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 && BuildConfig.DEBUG) {
+ 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 && BuildConfig.DEBUG) {
+ 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 && BuildConfig.DEBUG) {
+ 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() {
+ deallocCachedSqlStatements();
+ }
+
+ /**
+ * 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;
+ }
+
+ public static byte[] getBytes(char[] data) {
+ if(data == null || data.length == 0) return null;
+ CharBuffer charBuffer = CharBuffer.wrap(data);
+ ByteBuffer byteBuffer = Charset.forName(KEY_ENCODING).encode(charBuffer);
+ byte[] result = new byte[byteBuffer.limit()];
+ byteBuffer.get(result);
+ return result;
+ }
+
+ public static char[] getChars(byte[] data){
+ if(data == null || data.length == 0) return null;
+ ByteBuffer byteBuffer = ByteBuffer.wrap(data);
+ CharBuffer charBuffer = Charset.forName(KEY_ENCODING).decode(byteBuffer);
+ char[] result = new char[charBuffer.limit()];
+ charBuffer.get(result);
+ return result;
+ }
+
+ /* begin SQLiteSupportDatabase methods */
+
+ @Override
+ public android.database.Cursor query(String query) {
+ return rawQuery(query, null);
+ }
+
+ @Override
+ public android.database.Cursor query(String query, Object[] bindArgs) {
+ return rawQuery(query, bindArgs);
+ }
+
+ @Override
+ public android.database.Cursor query(SupportSQLiteQuery query) {
+ return query(query, null);
+ }
+
+ @Override
+ public android.database.Cursor query(final SupportSQLiteQuery supportQuery,
+ CancellationSignal cancellationSignal) {
+ String sql = supportQuery.getSql();
+ int argumentCount = supportQuery.getArgCount();
+ Object[] args = new Object[argumentCount];
+ SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, null);
+ SQLiteQuery query = new SQLiteQuery(this, sql, 0, args);
+ supportQuery.bindTo(query);
+ return new CrossProcessCursorWrapper(new SQLiteCursor(this, driver, null, query));
+ }
+
+ @Override
+ public long insert(String table, int conflictAlgorithm,
+ ContentValues values)
+ throws android.database.SQLException {
+ return insertWithOnConflict(table, null, values, conflictAlgorithm);
+ }
+
+ @Override
+ public int update(String table, int conflictAlgorithm, ContentValues values,
+ String whereClause, Object[] whereArgs) {
+ int whereArgsLength = whereArgs == null
+ ? 0
+ : whereArgs.length;
+ String[] args = new String[whereArgsLength];
+ for (int i = 0; i < whereArgsLength; i++) {
+ args[i] = whereArgs[i].toString();
+ }
+ return updateWithOnConflict(table, values, whereClause, args, conflictAlgorithm);
+ }
+
+ @Override
+ public void beginTransactionWithListener(
+ final android.database.sqlite.SQLiteTransactionListener transactionListener) {
+ beginTransactionWithListener(new SQLiteTransactionListener() {
+ @Override
+ public void onBegin() {
+ transactionListener.onBegin();
+ }
+
+ @Override
+ public void onCommit() {
+ transactionListener.onCommit();
+ }
+
+ @Override
+ public void onRollback() {
+ transactionListener.onRollback();
+ }
+ });
+ }
+
+ @Override
+ public void beginTransactionWithListenerNonExclusive(
+ final android.database.sqlite.SQLiteTransactionListener transactionListener) {
+ beginTransactionWithListenerNonExclusive(
+ new SQLiteTransactionListener() {
+ @Override
+ public void onBegin() {
+ transactionListener.onBegin();
+ }
+
+ @Override
+ public void onCommit() {
+ transactionListener.onCommit();
+ }
+
+ @Override
+ public void onRollback() {
+ transactionListener.onRollback();
+ }
+ });
+ }
+
+ /* end SQLiteSupportDatabase methods */
+
+ private void beginTransactionWithListenerInternal(SQLiteTransactionListener transactionListener,
+ SQLiteDatabaseTransactionType transactionType) {
+ 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);
+ if(BuildConfig.DEBUG){
+ Log.e(TAG, "beginTransaction() failed", e);
+ }
+ throw e;
+ }
+ ok = true;
+ return;
+ }
+ // This thread didn't already have the lock, so begin a database
+ // transaction now.
+ if(transactionType == SQLiteDatabaseTransactionType.Exclusive) {
+ execSQL("BEGIN EXCLUSIVE;");
+ } else if(transactionType == SQLiteDatabaseTransactionType.Immediate) {
+ execSQL("BEGIN IMMEDIATE;");
+ } else if(transactionType == SQLiteDatabaseTransactionType.Deferred) {
+ execSQL("BEGIN DEFERRED;");
+ } else {
+ String message = String.format("%s is an unsupported transaction type",
+ transactionType);
+ throw new IllegalArgumentException(message);
+ }
+ 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();
+ }
+ }
+ }
+
+ /**
+ * 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 getDbStats() {
+ ArrayList dbStatsList = new ArrayList();
+
+ for (SQLiteDatabase db : getActiveDatabases()) {
+ 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> attachedDbs = getAttachedDbs(db);
+ if (attachedDbs == null) {
+ continue;
+ }
+ for (int i = 0; i < attachedDbs.size(); i++) {
+ Pair 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;
+ }
+
+ private static ArrayList getActiveDatabases() {
+ ArrayList databases = new ArrayList();
+ synchronized (sActiveDatabases) {
+ databases.addAll(sActiveDatabases.keySet());
+ }
+ return databases;
+ }
+
+ /**
+ * 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> getAttachedDbs(SQLiteDatabase dbObj) {
+ if (!dbObj.isOpen()) {
+ return null;
+ }
+ ArrayList> attachedDbs = new ArrayList>();
+ Cursor c = dbObj.rawQuery("pragma database_list;", null);
+ while (c.moveToNext()) {
+ attachedDbs.add(new Pair(c.getString(1), c.getString(2)));
+ }
+ c.close();
+ return attachedDbs;
+ }
+
+ private Pair getResultFromPragma(String command) {
+ Pair result = new Pair(false, "");
+ Cursor cursor = rawQuery(command, new Object[]{});
+ if(cursor == null) return result;
+ if(cursor.moveToFirst()){
+ String value = cursor.getString(0);
+ result = new Pair(true, value);
+ }
+ cursor.close();
+ return result;
+ }
+
+
+ /**
+ * 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);
+
+ private native void key(byte[] key) throws SQLException;
+ private native void key_mutf8(char[] key) throws SQLException;
+ private native void rekey(byte[] key) throws SQLException;
+}
diff --git a/src/net/sqlcipher/database/SQLiteDatabaseHook.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java
similarity index 100%
rename from src/net/sqlcipher/database/SQLiteDatabaseHook.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDatabaseHook.java
diff --git a/src/net/sqlcipher/database/SQLiteDebug.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java
similarity index 100%
rename from src/net/sqlcipher/database/SQLiteDebug.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDebug.java
diff --git a/src/net/sqlcipher/database/SQLiteDirectCursorDriver.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java
similarity index 98%
rename from src/net/sqlcipher/database/SQLiteDirectCursorDriver.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java
index 37ef5a5e..36ae59a4 100644
--- a/src/net/sqlcipher/database/SQLiteDirectCursorDriver.java
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteDirectCursorDriver.java
@@ -21,11 +21,11 @@
/**
* A cursor driver that uses the given query directly.
- *
+ *
* @hide
*/
public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
- private String mEditTable;
+ private String mEditTable;
private SQLiteDatabase mDatabase;
private Cursor mCursor;
private String mSql;
@@ -69,7 +69,7 @@ public Cursor query(CursorFactory factory, String[] selectionArgs) {
// Create the cursor
if (factory == null) {
mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
-
+
} else {
mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
diff --git a/src/net/sqlcipher/database/SQLiteOpenHelper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java
similarity index 76%
rename from src/net/sqlcipher/database/SQLiteOpenHelper.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java
index f7af8b91..e3a24f43 100644
--- a/src/net/sqlcipher/database/SQLiteOpenHelper.java
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteOpenHelper.java
@@ -19,6 +19,7 @@
import java.io.File;
import android.content.Context;
+import android.database.sqlite.SQLiteException;
import net.sqlcipher.DatabaseErrorHandler;
import net.sqlcipher.DefaultDatabaseErrorHandler;
import net.sqlcipher.database.SQLiteDatabaseHook;
@@ -43,6 +44,8 @@ public abstract class SQLiteOpenHelper {
private final int mNewVersion;
private final SQLiteDatabaseHook mHook;
private final DatabaseErrorHandler mErrorHandler;
+ private boolean mEnableWriteAheadLogging;
+ private boolean mDeferSetWriteAheadLoggingEnabled;
private SQLiteDatabase mDatabase = null;
private boolean mIsInitializing = false;
@@ -79,7 +82,7 @@ public SQLiteOpenHelper(Context context, String name, CursorFactory factory,
int version, SQLiteDatabaseHook hook) {
this(context, name, factory, version, hook, new DefaultDatabaseErrorHandler());
}
-
+
/**
* Create a helper object to create, open, and/or manage a database.
* The database is not actually created or opened until one of
@@ -128,8 +131,12 @@ public SQLiteOpenHelper(Context context, String name, CursorFactory factory,
public synchronized SQLiteDatabase getWritableDatabase(String password) {
return getWritableDatabase(password == null ? null : password.toCharArray());
}
-
+
public synchronized SQLiteDatabase getWritableDatabase(char[] password) {
+ return getWritableDatabase(password == null ? null : SQLiteDatabase.getBytes(password));
+ }
+
+ public synchronized SQLiteDatabase getWritableDatabase(byte[] password) {
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
return mDatabase; // The database is already open for business
}
@@ -150,19 +157,19 @@ public synchronized SQLiteDatabase getWritableDatabase(char[] password) {
try {
mIsInitializing = true;
if (mName == null) {
- db = SQLiteDatabase.create(null, password);
-
+ db = SQLiteDatabase.create(null, "");
} else {
String path = mContext.getDatabasePath(mName).getPath();
-
File dbPathFile = new File (path);
- if (!dbPathFile.exists())
+ if (!dbPathFile.exists()) {
dbPathFile.getParentFile().mkdirs();
-
+ }
db = SQLiteDatabase.openOrCreateDatabase(path, password, mFactory, mHook, mErrorHandler);
}
-
-
+ if(mDeferSetWriteAheadLoggingEnabled) {
+ mEnableWriteAheadLogging = db.enableWriteAheadLogging();
+ }
+ onConfigure(db);
int version = db.getVersion();
if (version != mNewVersion) {
db.beginTransaction();
@@ -170,7 +177,11 @@ public synchronized SQLiteDatabase getWritableDatabase(char[] password) {
if (version == 0) {
onCreate(db);
} else {
+ if(version > mNewVersion) {
+ onDowngrade(db, version, mNewVersion);
+ } else {
onUpgrade(db, version, mNewVersion);
+ }
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
@@ -213,8 +224,12 @@ public synchronized SQLiteDatabase getWritableDatabase(char[] password) {
public synchronized SQLiteDatabase getReadableDatabase(String password) {
return getReadableDatabase(password == null ? null : password.toCharArray());
}
-
+
public synchronized SQLiteDatabase getReadableDatabase(char[] password) {
+ return getReadableDatabase(password == null ? null : SQLiteDatabase.getBytes(password));
+ }
+
+ public synchronized SQLiteDatabase getReadableDatabase(byte[] password) {
if (mDatabase != null && mDatabase.isOpen()) {
return mDatabase; // The database is already open for business
}
@@ -236,7 +251,7 @@ public synchronized SQLiteDatabase getReadableDatabase(char[] password) {
String path = mContext.getDatabasePath(mName).getPath();
File databasePath = new File(path);
File databasesDirectory = new File(mContext.getDatabasePath(mName).getParent());
-
+
if(!databasesDirectory.exists()){
databasesDirectory.mkdirs();
}
@@ -246,7 +261,7 @@ public synchronized SQLiteDatabase getReadableDatabase(char[] password) {
mIsInitializing = true;
db.close();
}
- db = SQLiteDatabase.openDatabase(path, password, mFactory, SQLiteDatabase.OPEN_READONLY);
+ db = SQLiteDatabase.openDatabase(path, password, mFactory, SQLiteDatabase.OPEN_READONLY, mHook, mErrorHandler);
if (db.getVersion() != mNewVersion) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + path);
@@ -274,6 +289,81 @@ public synchronized void close() {
}
}
+ /**
+ * Return the name of the SQLite database being opened, as given to
+ * the constructor.
+ */
+ public String getDatabaseName() {
+ return mName;
+ }
+
+ /**
+ * Enables or disables the use of write-ahead logging for the database.
+ *
+ * Write-ahead logging cannot be used with read-only databases so the value of
+ * this flag is ignored if the database is opened read-only.
+ *
+ * @param enabled True if write-ahead logging should be enabled, false if it
+ * should be disabled.
+ *
+ * @see SQLiteDatabase#enableWriteAheadLogging()
+ */
+ public void setWriteAheadLoggingEnabled(boolean enabled) {
+ synchronized (this) {
+ if (mEnableWriteAheadLogging != enabled) {
+ if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
+ if (enabled) {
+ mDatabase.enableWriteAheadLogging();
+ } else {
+ mDatabase.disableWriteAheadLogging();
+ }
+ mEnableWriteAheadLogging = enabled;
+ } else {
+ mDeferSetWriteAheadLoggingEnabled = enabled;
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when the database needs to be downgraded. This is strictly similar to
+ * {@link #onUpgrade} method, but is called whenever current version is newer than requested one.
+ * However, this method is not abstract, so it is not mandatory for a customer to
+ * implement it. If not overridden, default implementation will reject downgrade and
+ * throws SQLiteException
+ *
+ *
+ * This method executes within a transaction. If an exception is thrown, all changes
+ * will automatically be rolled back.
+ *
+ *
+ * @param db The database.
+ * @param oldVersion The old database version.
+ * @param newVersion The new database version.
+ */
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ throw new SQLiteException("Can't downgrade database from version " +
+ oldVersion + " to " + newVersion);
+ }
+
+ /**
+ * Called when the database connection is being configured, to enable features
+ * such as write-ahead logging or foreign key support.
+ *
+ * This method is called before {@link #onCreate}, {@link #onUpgrade},
+ * {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify
+ * the database except to configure the database connection as required.
+ *
+ * This method should only call methods that configure the parameters of the
+ * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging}
+ * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled},
+ * {@link SQLiteDatabase#setLocale}, or executing PRAGMA statements.
+ *
+ *
+ * @param db The database.
+ */
+ public void onConfigure(SQLiteDatabase db) {}
+
/**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
diff --git a/src/net/sqlcipher/database/SQLiteProgram.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java
similarity index 98%
rename from src/net/sqlcipher/database/SQLiteProgram.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java
index b978bdf0..e43826d9 100644
--- a/src/net/sqlcipher/database/SQLiteProgram.java
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteProgram.java
@@ -17,6 +17,7 @@
package net.sqlcipher.database;
import android.util.Log;
+import androidx.sqlite.db.SupportSQLiteProgram;
/**
* A base class for compiled SQLite programs.
@@ -24,7 +25,8 @@
* SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple
* threads should perform its own synchronization when using the SQLiteProgram.
*/
-public abstract class SQLiteProgram extends SQLiteClosable {
+public abstract class SQLiteProgram extends SQLiteClosable implements
+ SupportSQLiteProgram {
private static final String TAG = "SQLiteProgram";
@@ -181,6 +183,7 @@ protected void compile(String sql, boolean forceCompilation) {
*
* @param index The 1-based index to the parameter to bind null to
*/
+ @Override
public void bindNull(int index) {
if (mClosed) {
throw new IllegalStateException("program already closed");
@@ -203,6 +206,7 @@ public void bindNull(int index) {
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
+ @Override
public void bindLong(int index, long value) {
if (mClosed) {
throw new IllegalStateException("program already closed");
@@ -225,6 +229,7 @@ public void bindLong(int index, long value) {
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
+ @Override
public void bindDouble(int index, double value) {
if (mClosed) {
throw new IllegalStateException("program already closed");
@@ -247,6 +252,7 @@ public void bindDouble(int index, double value) {
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
+ @Override
public void bindString(int index, String value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
@@ -272,6 +278,7 @@ public void bindString(int index, String value) {
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
+ @Override
public void bindBlob(int index, byte[] value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
@@ -293,6 +300,7 @@ public void bindBlob(int index, byte[] value) {
/**
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
+ @Override
public void clearBindings() {
if (mClosed) {
throw new IllegalStateException("program already closed");
diff --git a/src/net/sqlcipher/database/SQLiteQuery.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java
similarity index 90%
rename from src/net/sqlcipher/database/SQLiteQuery.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java
index d9c41a61..c87bd590 100644
--- a/src/net/sqlcipher/database/SQLiteQuery.java
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQuery.java
@@ -17,6 +17,8 @@
package net.sqlcipher.database;
import net.sqlcipher.*;
+import android.database.sqlite.SQLiteDatabaseCorruptException;
+import android.database.sqlite.SQLiteMisuseException;
import android.os.SystemClock;
import android.util.Log;
@@ -65,8 +67,9 @@ public class SQLiteQuery extends SQLiteProgram {
* @param window The window to fill into
* @return number of total rows in the query
*/
- /* package */ int fillWindow(CursorWindow window,
- int maxRead, int lastPos) {
+ /* package */
+ int fillWindow(CursorWindow window,
+ int maxRead, int lastPos) {
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
try {
@@ -76,8 +79,11 @@ public class SQLiteQuery extends SQLiteProgram {
// if the start pos is not equal to 0, then most likely window is
// too small for the data set, loading by another thread
// is not safe in this situation. the native code will ignore maxRead
- int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
- maxRead, lastPos);
+ int numRows = native_fill_window(window,
+ window.getStartPosition(),
+ window.getRequiredPosition(),
+ mOffsetIndex,
+ maxRead, lastPos);
// Logging
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
@@ -215,8 +221,10 @@ public void bindArguments(Object[] args){
}
}
- private final native int native_fill_window(CursorWindow window,
- int startPos, int offsetParam, int maxRead, int lastPos);
+ private final native int native_fill_window(CursorWindow window,
+ int startPos, int requiredPos,
+ int offsetParam, int maxRead,
+ int lastPos);
private final native int native_column_count();
diff --git a/src/net/sqlcipher/database/SQLiteQueryBuilder.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java
similarity index 100%
rename from src/net/sqlcipher/database/SQLiteQueryBuilder.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryBuilder.java
diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java
new file mode 100644
index 00000000..4b36c05f
--- /dev/null
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteQueryStats.java
@@ -0,0 +1,20 @@
+package net.sqlcipher.database;
+
+public class SQLiteQueryStats {
+ long totalQueryResultSize = 0L;
+ long largestIndividualRowSize = 0L;
+
+ public SQLiteQueryStats(long totalQueryResultSize,
+ long largestIndividualRowSize) {
+ this.totalQueryResultSize = totalQueryResultSize;
+ this.largestIndividualRowSize = largestIndividualRowSize;
+ }
+
+ public long getTotalQueryResultSize(){
+ return totalQueryResultSize;
+ }
+
+ public long getLargestIndividualRowSize(){
+ return largestIndividualRowSize;
+ }
+}
diff --git a/src/net/sqlcipher/database/SQLiteStatement.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java
similarity index 96%
rename from src/net/sqlcipher/database/SQLiteStatement.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java
index 0050b8b7..84b7b4c2 100644
--- a/src/net/sqlcipher/database/SQLiteStatement.java
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteStatement.java
@@ -17,6 +17,7 @@
package net.sqlcipher.database;
import android.os.SystemClock;
+import androidx.sqlite.db.SupportSQLiteStatement;
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
@@ -27,7 +28,8 @@
* SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple
* threads should perform its own synchronization when using the SQLiteStatement.
*/
-public class SQLiteStatement extends SQLiteProgram
+public class SQLiteStatement extends SQLiteProgram implements
+ SupportSQLiteStatement
{
/**
* Don't use SQLiteStatement constructor directly, please use
@@ -46,6 +48,7 @@ public class SQLiteStatement extends SQLiteProgram
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
+ @Override
public void execute() {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
@@ -71,6 +74,7 @@ public void execute() {
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
+ @Override
public long executeInsert() {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
@@ -88,6 +92,7 @@ public long executeInsert() {
}
}
+ @Override
public int executeUpdateDelete() {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
@@ -113,6 +118,7 @@ public int executeUpdateDelete() {
*
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
+ @Override
public long simpleQueryForLong() {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
@@ -138,6 +144,7 @@ public long simpleQueryForLong() {
*
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
+ @Override
public String simpleQueryForString() {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
diff --git a/src/net/sqlcipher/database/SQLiteTransactionListener.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java
similarity index 100%
rename from src/net/sqlcipher/database/SQLiteTransactionListener.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SQLiteTransactionListener.java
diff --git a/src/net/sqlcipher/database/SqliteWrapper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java
similarity index 98%
rename from src/net/sqlcipher/database/SqliteWrapper.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java
index b1bbbfb4..1d15f99e 100644
--- a/src/net/sqlcipher/database/SqliteWrapper.java
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SqliteWrapper.java
@@ -23,6 +23,7 @@
import net.sqlcipher.*;
+import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java
new file mode 100644
index 00000000..2be2c2b2
--- /dev/null
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 Mark L. Murphy
+ *
+ * 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 androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+public class SupportFactory implements SupportSQLiteOpenHelper.Factory {
+ private final byte[] passphrase;
+ private final SQLiteDatabaseHook hook;
+ private final boolean clearPassphrase;
+
+ public SupportFactory(byte[] passphrase) {
+ this(passphrase, (SQLiteDatabaseHook)null);
+ }
+
+ public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook) {
+ this(passphrase, hook, true);
+ }
+
+ public SupportFactory(byte[] passphrase, SQLiteDatabaseHook hook,
+ boolean clearPassphrase) {
+ this.passphrase = passphrase;
+ this.hook = hook;
+ this.clearPassphrase = clearPassphrase;
+ }
+
+ @Override
+ public SupportSQLiteOpenHelper create(SupportSQLiteOpenHelper.Configuration configuration) {
+ return new SupportHelper(configuration, passphrase, hook, clearPassphrase);
+ }
+}
diff --git a/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java
new file mode 100644
index 00000000..26960617
--- /dev/null
+++ b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/SupportHelper.java
@@ -0,0 +1,118 @@
+ /*
+ * Copyright (C) 2019 Mark L. Murphy
+ *
+ * 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 android.database.sqlite.SQLiteException;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.SupportSQLiteOpenHelper;
+
+public class SupportHelper implements SupportSQLiteOpenHelper {
+ private SQLiteOpenHelper standardHelper;
+ private byte[] passphrase;
+ private final boolean clearPassphrase;
+
+ SupportHelper(final SupportSQLiteOpenHelper.Configuration configuration,
+ byte[] passphrase, final SQLiteDatabaseHook hook,
+ boolean clearPassphrase) {
+ SQLiteDatabase.loadLibs(configuration.context);
+ this.passphrase = passphrase;
+ this.clearPassphrase = clearPassphrase;
+
+ standardHelper =
+ new SQLiteOpenHelper(configuration.context, configuration.name,
+ null, configuration.callback.version, hook) {
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ configuration.callback.onCreate(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion,
+ int newVersion) {
+ configuration.callback.onUpgrade(db, oldVersion,
+ newVersion);
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion,
+ int newVersion) {
+ configuration.callback.onDowngrade(db, oldVersion,
+ newVersion);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ configuration.callback.onOpen(db);
+ }
+
+ @Override
+ public void onConfigure(SQLiteDatabase db) {
+ configuration.callback.onConfigure(db);
+ }
+ };
+ }
+
+ @Override
+ public String getDatabaseName() {
+ return standardHelper.getDatabaseName();
+ }
+
+ @Override
+ public void setWriteAheadLoggingEnabled(boolean enabled) {
+ standardHelper.setWriteAheadLoggingEnabled(enabled);
+ }
+
+ @Override
+ public SupportSQLiteDatabase getWritableDatabase() {
+ SQLiteDatabase result;
+ try {
+ result = standardHelper.getWritableDatabase(passphrase);
+ } catch (SQLiteException ex){
+ if(passphrase != null){
+ boolean isCleared = true;
+ for(byte b : passphrase){
+ isCleared = isCleared && (b == (byte)0);
+ }
+ if (isCleared) {
+ throw new IllegalStateException("The passphrase appears to be cleared. This happens by " +
+ "default the first time you use the factory to open a database, so we can remove the " +
+ "cleartext passphrase from memory. If you close the database yourself, please use a " +
+ "fresh SupportFactory to reopen it. If something else (e.g., Room) closed the " +
+ "database, and you cannot control that, use SupportFactory boolean constructor option " +
+ "to opt out of the automatic password clearing step. See the project README for more information.", ex);
+ }
+ }
+ throw ex;
+ }
+ if(clearPassphrase && passphrase != null) {
+ for (int i = 0; i < passphrase.length; i++) {
+ passphrase[i] = (byte)0;
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public SupportSQLiteDatabase getReadableDatabase() {
+ return getWritableDatabase();
+ }
+
+ @Override
+ public void close() {
+ standardHelper.close();
+ }
+}
diff --git a/src/net/sqlcipher/database/package-info.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/database/package-info.java
similarity index 100%
rename from src/net/sqlcipher/database/package-info.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/database/package-info.java
diff --git a/src/net/sqlcipher/package-info.java b/android-database-sqlcipher/src/main/java/net/sqlcipher/package-info.java
similarity index 100%
rename from src/net/sqlcipher/package-info.java
rename to android-database-sqlcipher/src/main/java/net/sqlcipher/package-info.java
diff --git a/android-database-sqlcipher/src/main/res/values/android_database_sqlcipher_strings.xml b/android-database-sqlcipher/src/main/res/values/android_database_sqlcipher_strings.xml
new file mode 100644
index 00000000..ddedb587
--- /dev/null
+++ b/android-database-sqlcipher/src/main/res/values/android_database_sqlcipher_strings.xml
@@ -0,0 +1,12 @@
+
+
+ Zetetic, LLC
+ https://www.zetetic.net/sqlcipher/
+ SQLCipher for Android
+ Android SQLite API based on SQLCipher
+ https://www.zetetic.net/sqlcipher/
+ ${clientVersionNumber}
+ true
+ https://github.com/sqlcipher/android-database-sqlcipher
+ https://www.zetetic.net/sqlcipher/license/
+
diff --git a/build-openssl-libraries.sh b/build-openssl-libraries.sh
deleted file mode 100755
index 917fe72b..00000000
--- a/build-openssl-libraries.sh
+++ /dev/null
@@ -1,116 +0,0 @@
-#! /usr/bin/env bash
-(cd external/openssl;
-
- if [ ! ${ANDROID_NDK_ROOT} ]; then
- echo "ANDROID_NDK_ROOT environment variable not set, set and rerun"
- exit 1
- fi
-
- ANDROID_LIB_ROOT=../android-libs
- ANDROID_TOOLCHAIN_DIR=/tmp/sqlcipher-android-toolchain
- OPENSSL_CONFIGURE_OPTIONS="no-krb5 no-idea no-camellia \
- no-seed no-bf no-cast no-rc2 no-rc4 no-rc5 no-md2 \
- no-md4 no-ripemd no-rsa no-ecdh no-sock no-ssl2 no-ssl3 \
- no-dsa no-dh no-ec no-ecdsa no-tls1 no-pbe no-pkcs \
- no-tlsext no-pem no-rfc3779 no-whirlpool no-ui no-srp \
- no-ssltrace no-tlsext no-mdc2 no-ecdh no-engine \
- no-tls2 no-srtp -fPIC"
-
- HOST_INFO=`uname -a`
- case ${HOST_INFO} in
- Darwin*)
- TOOLCHAIN_SYSTEM=darwin-x86
- ;;
- Linux*)
- if [[ "${HOST_INFO}" == *i686* ]]
- then
- TOOLCHAIN_SYSTEM=linux-x86
- else
- TOOLCHAIN_SYSTEM=linux-x86_64
- fi
- ;;
- *)
- echo "Toolchain unknown for host system"
- exit 1
- ;;
- esac
-
- rm -rf ${ANDROID_LIB_ROOT}
- git clean -dfx && git checkout -f
- ./Configure dist
-
- for SQLCIPHER_TARGET_PLATFORM in armeabi armeabi-v7a x86 x86_64 arm64-v8a
- do
- echo "Building for libcrypto.a for ${SQLCIPHER_TARGET_PLATFORM}"
- case "${SQLCIPHER_TARGET_PLATFORM}" in
- armeabi)
- TOOLCHAIN_ARCH=arm
- TOOLCHAIN_PREFIX=arm-linux-androideabi
- CONFIGURE_ARCH=android
- PLATFORM_OUTPUT_DIR=armeabi
- ANDROID_PLATFORM_VERSION=android-9
- ;;
- armeabi-v7a)
- TOOLCHAIN_ARCH=arm
- TOOLCHAIN_PREFIX=arm-linux-androideabi
- CONFIGURE_ARCH=android -march=armv7-a
- PLATFORM_OUTPUT_DIR=armeabi-v7a
- ANDROID_PLATFORM_VERSION=android-9
- ;;
- x86)
- TOOLCHAIN_ARCH=x86
- TOOLCHAIN_PREFIX=i686-linux-android
- CONFIGURE_ARCH=android-x86
- PLATFORM_OUTPUT_DIR=x86
- ANDROID_PLATFORM_VERSION=android-9
- ;;
- x86_64)
- TOOLCHAIN_ARCH=x86_64
- TOOLCHAIN_PREFIX=x86_64-linux-android
- CONFIGURE_ARCH=android64
- PLATFORM_OUTPUT_DIR=x86_64
- ANDROID_PLATFORM_VERSION=android-21
- ;;
- arm64-v8a)
- TOOLCHAIN_ARCH=arm64
- TOOLCHAIN_PREFIX=aarch64-linux-android
- CONFIGURE_ARCH=android64-aarch64
- PLATFORM_OUTPUT_DIR=arm64-v8a
- ANDROID_PLATFORM_VERSION=android-21
- ;;
- *)
- echo "Unsupported build platform:${SQLCIPHER_TARGET_PLATFORM}"
- exit 1
- esac
-
- rm -rf ${ANDROID_TOOLCHAIN_DIR}
- mkdir -p "${ANDROID_LIB_ROOT}/${SQLCIPHER_TARGET_PLATFORM}"
- ${ANDROID_NDK_ROOT}/build/tools/make-standalone-toolchain.sh \
- --platform=${ANDROID_PLATFORM_VERSION} \
- --install-dir=${ANDROID_TOOLCHAIN_DIR} \
- --arch=${TOOLCHAIN_ARCH}
-
- export PATH=${ANDROID_TOOLCHAIN_DIR}/bin:$PATH
- export CROSS_SYSROOT=${ANDROID_TOOLCHAIN_DIR}/sysroot
-
- RANLIB=${TOOLCHAIN_PREFIX}-ranlib \
- AR=${TOOLCHAIN_PREFIX}-ar \
- CC=${TOOLCHAIN_PREFIX}-gcc \
- ./Configure "${CONFIGURE_ARCH}" "${OPENSSL_CONFIGURE_OPTIONS}"
-
- if [ $? -ne 0 ]; then
- echo "Error executing:./Configure ${CONFIGURE_ARCH} ${OPENSSL_CONFIGURE_OPTIONS}"
- exit 1
- fi
-
- make clean
- make
-
- if [ $? -ne 0 ]; then
- echo "Error executing make for platform:${SQLCIPHER_TARGET_PLATFORM}"
- exit 1
- fi
-
- mv libcrypto.a ${ANDROID_LIB_ROOT}/${PLATFORM_OUTPUT_DIR}
- done
-)
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..0751426a
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,99 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ maven {
+ url "https://plugins.gradle.org/m2/"
+ }
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.3.1'
+ classpath "gradle.plugin.org.ec4j.gradle:editorconfig-gradle-plugin:0.0.3"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+ext {
+ if(project.hasProperty('sqlcipherAndroidClientVersion')) {
+ clientVersionNumber = "${sqlcipherAndroidClientVersion}"
+ } else {
+ clientVersionNumber = "UndefinedBuildNumber"
+ }
+ mavenPackaging = "aar"
+ mavenGroup = "net.zetetic"
+ mavenArtifactId = "android-database-sqlcipher"
+ mavenLocalRepositoryPrefix = "file://"
+ if(project.hasProperty('publishLocal') && publishLocal.toBoolean()){
+ mavenSnapshotRepositoryUrl = "outputs/snapshot"
+ mavenReleaseRepositoryUrl = "outputs/release"
+ } else {
+ mavenLocalRepositoryPrefix = ""
+ mavenSnapshotRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots"
+ mavenReleaseRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2"
+ }
+ if(project.hasProperty('publishSnapshot') && publishSnapshot.toBoolean()){
+ mavenVersionName = "${clientVersionNumber}-SNAPSHOT"
+ } else {
+ mavenVersionName = "${clientVersionNumber}"
+ }
+ if(project.hasProperty('nexusUsername')){
+ nexusUsername = "${nexusUsername}"
+ }
+ if(project.hasProperty('nexusPassword')){
+ nexusPassword = "${nexusPassword}"
+ }
+ mavenPomDescription = "SQLCipher for Android is a plugin to SQLite that provides full database encryption."
+ mavenPomUrl = "https://www.zetetic.net/sqlcipher"
+ mavenScmUrl = "https://github.com/sqlcipher/android-database-sqlcipher.git"
+ mavenScmConnection = "scm:git:https://github.com/sqlcipher/android-database-sqlcipher.git"
+ mavenScmDeveloperConnection = "scm:git:https://github.com/sqlcipher/android-database-sqlcipher.git"
+ mavenLicenseUrl = "https://www.zetetic.net/sqlcipher/license/"
+ mavenDeveloperName = "Zetetic Support"
+ mavenDeveloperEmail = "support@zetetic.net"
+ mavenDeveloperOrganization = "Zetetic LLC"
+ mavenDeveloperUrl = "https://www.zetetic.net"
+ minimumAndroidSdkVersion = 21
+ minimumAndroid64BitSdkVersion = 21
+ targetAndroidSdkVersion = 26
+ compileAndroidSdkVersion = 26
+ mainProjectName = "android-database-sqlcipher"
+ nativeRootOutputDir = "${projectDir}/${mainProjectName}/src/main"
+ if(project.hasProperty('sqlcipherRoot')) {
+ sqlcipherDir = "${sqlcipherRoot}"
+ }
+ if(project.hasProperty('opensslAndroidNativeRoot') && "${opensslAndroidNativeRoot}") {
+ androidNativeRootDir = "${opensslAndroidNativeRoot}"
+ } else {
+ androidNativeRootDir = "${nativeRootOutputDir}/external/android-libs"
+ }
+ if(project.hasProperty('opensslRoot')) {
+ opensslDir = "${opensslRoot}"
+ }
+ if(project.hasProperty('debugBuild') && debugBuild.toBoolean()) {
+ otherSqlcipherCFlags = "-fstack-protector-all"
+ ndkBuildType="NDK_DEBUG=1"
+ } else {
+ otherSqlcipherCFlags = "-DLOG_NDEBUG -fstack-protector-all"
+ ndkBuildType="NDK_DEBUG=0"
+ }
+ if(project.hasProperty('sqlcipherCFlags')
+ && project.sqlcipherCFlags?.trim()
+ && project.sqlcipherCFlags?.contains('SQLITE_HAS_CODEC')
+ && project.sqlcipherCFlags?.contains('SQLITE_TEMP_STORE')) {
+ sqlcipherCFlags = "${sqlcipherCFlags}"
+ } else {
+ if(!project.gradle.startParameter.taskNames.toString().contains('clean')){
+ throw new InvalidUserDataException("SQLCIPHER_CFLAGS environment variable must be specified and include at least '-DSQLITE_HAS_CODEC -DSQLITE_TEMP_STORE=2'")
+ }
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/custom_rules.xml b/custom_rules.xml
deleted file mode 100644
index 5402a311..00000000
--- a/custom_rules.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/external/openssl b/external/openssl
deleted file mode 160000
index 91eaf079..00000000
--- a/external/openssl
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 91eaf079b7430cb4ebb7f3ccabe74aa383b27c4e
diff --git a/external/sqlcipher b/external/sqlcipher
deleted file mode 160000
index df092f0a..00000000
--- a/external/sqlcipher
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit df092f0a7af1c8e3558a743036c089e6ef8e6307
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..2d8d1e4d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+android.useAndroidX=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..41d9927a
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..070cb702
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..1b6c7873
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..ac1b06f9
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/jars/doclava-1.0.6.jar b/jars/doclava-1.0.6.jar
deleted file mode 100644
index 6f66dac6..00000000
Binary files a/jars/doclava-1.0.6.jar and /dev/null differ
diff --git a/jni/Application32.mk b/jni/Application32.mk
deleted file mode 100644
index 8cbd6fc4..00000000
--- a/jni/Application32.mk
+++ /dev/null
@@ -1,5 +0,0 @@
-APP_PROJECT_PATH := $(shell pwd)
-APP_ABI := armeabi armeabi-v7a x86
-APP_PLATFORM := android-9
-APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/Android.mk
-APP_STL := stlport_static
diff --git a/jni/Application64.mk b/jni/Application64.mk
deleted file mode 100644
index 794b44ad..00000000
--- a/jni/Application64.mk
+++ /dev/null
@@ -1,5 +0,0 @@
-APP_PROJECT_PATH := $(shell pwd)
-APP_ABI := x86_64 arm64-v8a
-APP_PLATFORM := android-21
-APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/Android.mk
-APP_STL := stlport_static
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index 075f0c2a..00000000
--- a/pom.xml
+++ /dev/null
@@ -1,134 +0,0 @@
-
- 4.0.0
- net.zetetic
- android-database-sqlcipher
- 3.5.7
- aar
- android-database-sqlcipher
-
- SQLCipher for Android is a plugin to SQLite that provides full database encryption.
-
- https://www.zetetic.net/sqlcipher/
-
-
- https://www.zetetic.net/sqlcipher/license/
-
-
-
-
- Zetetic Support
- support@zetetic.net
- Zetetic LLC
- https://www.zetetic.net/
-
-
-
- scm:git:https://github.com/sqlcipher/android-database-sqlcipher.git
- scm:git:https://github.com/sqlcipher/android-database-sqlcipher.git
- https://github.com/sqlcipher/android-database-sqlcipher.git
-
-
-
- ossrh
- https://oss.sonatype.org/content/repositories/snapshots
-
-
- ossrh
- https://oss.sonatype.org/service/local/staging/deploy/maven2/
-
-
-
- bin/
-
-
- com.simpligility.maven.plugins
- android-maven-plugin
- 4.3.0
- true
-
-
- 23
-
-
- false
-
- AndroidManifest.xml
- res
- assets
- libs
- true
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
- 2.2.1
-
-
- attach-sources
-
- jar-no-fork
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 2.9.1
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- 1.5
-
-
- sign-artifacts
- verify
-
- sign
-
-
- 97ED25C2
-
-
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- 1.6.3
- true
-
- ossrh
- https://oss.sonatype.org/
- false
-
-
-
- com.thoughtworks.xstream
- xstream
- 1.4.7
-
-
-
-
-
-
-
- com.google.android
- android
- 4.0.1.2
- provided
-
-
-
diff --git a/project.properties b/project.properties
deleted file mode 100644
index 19c32e63..00000000
--- a/project.properties
+++ /dev/null
@@ -1,11 +0,0 @@
-# This file is automatically generated by Android Tools.
-# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
-#
-# This file must be checked in Version Control Systems.
-#
-# To customize properties used by the Ant build system use,
-# "ant.properties", and override values to adapt the script to your
-# project structure.
-
-# Project target.
-target=android-23
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..79439766
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':android-database-sqlcipher'
diff --git a/src/net/sqlcipher/SQLException.java b/src/net/sqlcipher/SQLException.java
deleted file mode 100644
index 8c8c0373..00000000
--- a/src/net/sqlcipher/SQLException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher;
-
-/**
- * An exception that indicates there was an error with SQL parsing or execution.
- */
-public class SQLException extends RuntimeException
-{
- public SQLException() {}
-
- public SQLException(String error)
- {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteAbortException.java b/src/net/sqlcipher/database/SQLiteAbortException.java
deleted file mode 100644
index 89b066ce..00000000
--- a/src/net/sqlcipher/database/SQLiteAbortException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-/**
- * An exception that indicates that the SQLite program was aborted.
- * This can happen either through a call to ABORT in a trigger,
- * or as the result of using the ABORT conflict clause.
- */
-public class SQLiteAbortException extends SQLiteException {
- public SQLiteAbortException() {}
-
- public SQLiteAbortException(String error) {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteConstraintException.java b/src/net/sqlcipher/database/SQLiteConstraintException.java
deleted file mode 100644
index d9d548f6..00000000
--- a/src/net/sqlcipher/database/SQLiteConstraintException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-/**
- * An exception that indicates that an integrity constraint was violated.
- */
-public class SQLiteConstraintException extends SQLiteException {
- public SQLiteConstraintException() {}
-
- public SQLiteConstraintException(String error) {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteDatabase.java b/src/net/sqlcipher/database/SQLiteDatabase.java
deleted file mode 100644
index d9408f96..00000000
--- a/src/net/sqlcipher/database/SQLiteDatabase.java
+++ /dev/null
@@ -1,2918 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-import net.sqlcipher.Cursor;
-import net.sqlcipher.CrossProcessCursorWrapper;
-import net.sqlcipher.DatabaseUtils;
-import net.sqlcipher.DatabaseErrorHandler;
-import net.sqlcipher.DefaultDatabaseErrorHandler;
-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.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-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.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.os.Debug;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
-import android.util.Pair;
-
-import java.io.UnsupportedEncodingException;
-
-/**
- * Exposes methods to manage a SQLCipher database.
- * SQLiteDatabase has methods to create, delete, execute SQL commands, and
- * perform other common database management tasks.
- *
A call to loadLibs(…)
should occur before attempting to
- * create or open a database connection.
- *
Database names must be unique within an application, not across all
- * applications.
- *
- */
-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;
- private static final String KEY_ENCODING = "UTF-8";
-
- /**
- * The version number of the SQLCipher for Android Java client library.
- */
- public static final String SQLCIPHER_ANDROID_VERSION = "3.5.7";
-
- // Stores reference to all databases opened in the current process.
- // (The referent Object is not used at this time.)
- // INVARIANT: Guarded by sActiveDatabases.
- private static WeakHashMap sActiveDatabases =
- new WeakHashMap();
-
- public int status(int operation, boolean reset){
- return native_status(operation, reset);
- }
-
- /**
- * Change the password of the open database using sqlite3_rekey().
- *
- * @param password new database password
- *
- * @throws SQLiteException if there is an issue changing the password internally
- * OR if the database is not open
- *
- * FUTURE @todo throw IllegalStateException if the database is not open and
- * update the test suite
- */
- public void changePassword(String password) throws SQLiteException {
- /* safeguard: */
- if (!isOpen()) {
- throw new SQLiteException("database not open");
- }
- if (password != null) {
- byte[] keyMaterial = getBytes(password.toCharArray());
- rekey(keyMaterial);
- for(byte data : keyMaterial) {
- data = 0;
- }
- }
- }
-
- /**
- * Change the password of the open database using sqlite3_rekey().
- *
- * @param password new database password (char array)
- *
- * @throws SQLiteException if there is an issue changing the password internally
- * OR if the database is not open
- *
- * FUTURE @todo throw IllegalStateException if the database is not open and
- * update the test suite
- */
- public void changePassword(char[] password) throws SQLiteException {
- /* safeguard: */
- if (!isOpen()) {
- throw new SQLiteException("database not open");
- }
- if (password != null) {
- byte[] keyMaterial = getBytes(password);
- rekey(keyMaterial);
- for(byte data : keyMaterial) {
- data = 0;
- }
- }
- }
-
- private static void loadICUData(Context context, File workingDir) {
- OutputStream out = null;
- ZipInputStream in = null;
- File icuDir = new File(workingDir, "icu");
- File icuDataFile = new File(icuDir, "icudt46l.dat");
- try {
- if(!icuDir.exists()) icuDir.mkdirs();
- if(!icuDataFile.exists()) {
- in = new ZipInputStream(context.getAssets().open("icudt46l.zip"));
- in.getNextEntry();
- out = new FileOutputStream(icuDataFile);
- byte[] buf = new byte[1024];
- int len;
- while ((len = in.read(buf)) > 0) {
- out.write(buf, 0, len);
- }
- }
- }
- catch (Exception ex) {
- Log.e(TAG, "Error copying icu dat file", ex);
- if(icuDataFile.exists()){
- icuDataFile.delete();
- }
- throw new RuntimeException(ex);
- }
- finally {
- try {
- if(in != null){
- in.close();
- }
- if(out != null){
- out.flush();
- out.close();
- }
- } catch (IOException ioe){
- Log.e(TAG, "Error in closing streams IO streams after expanding ICU dat file", ioe);
- throw new RuntimeException(ioe);
- }
- }
- }
-
- /**
- * Implement this interface to provide custom strategy for loading jni libraries.
- */
- public interface LibraryLoader {
- /**
- * Load jni libraries by given names.
- * Straightforward implementation will be calling {@link System#loadLibrary(String name)}
- * for every provided library name.
- *
- * @param libNames library names that sqlcipher need to load
- */
- void loadLibraries(String... libNames);
- }
-
- /**
- * Loads the native SQLCipher library into the application process.
- */
- public static synchronized void loadLibs (Context context) {
- loadLibs(context, context.getFilesDir());
- }
-
- /**
- * Loads the native SQLCipher library into the application process.
- */
- public static synchronized void loadLibs (Context context, File workingDir) {
- loadLibs(context, workingDir, new LibraryLoader() {
- @Override
- public void loadLibraries(String... libNames) {
- for (String libName : libNames) {
- System.loadLibrary(libName);
- }
- }
- });
- }
-
- /**
- * Loads the native SQLCipher library into the application process.
- */
- public static synchronized void loadLibs(Context context, LibraryLoader libraryLoader) {
- loadLibs(context, context.getFilesDir(), libraryLoader);
- }
-
- /**
- * Loads the native SQLCipher library into the application process.
- */
- public static synchronized void loadLibs (Context context, File workingDir, LibraryLoader libraryLoader) {
- libraryLoader.loadLibraries("sqlcipher");
-
- // 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 LOCALIZED
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
-
- /**
- * SQLite memory database name
- */
- public static final String MEMORY = ":memory:";
-
- /**
- * 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 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 */ long 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 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 mCompiledQueries = new HashMap();
- /**
- * @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;
-
- /** {@link DatabaseErrorHandler} to be used when SQLite returns any of the following errors
- * Corruption
- * */
- private final DatabaseErrorHandler mErrorHandler;
-
- /** 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();
-
- synchronized (sActiveDatabases) {
- sActiveDatabases.remove(this);
- }
- }
- }
-
- /**
- * 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, "Calling error handler for corrupt database (detected) " + mPath);
-
- // NOTE: DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database
- mErrorHandler.onCorruption(this);
- }
-
- /**
- * Locks the database for exclusive access. The database lock must be held when
- * touch the native sqlite3* object since it is single threaded and uses
- * a polling lock contention algorithm. The lock is recursive, and may be acquired
- * multiple times by the same thread. This is a no-op if mLockingEnabled is false.
- *
- * @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.
- *
- * Here is the standard idiom for transactions:
- *
- *
- * db.beginTransaction();
- * try {
- * ...
- * db.setTransactionSuccessful();
- * } finally {
- * db.endTransaction();
- * }
- *
- *
- * @throws IllegalStateException if the database is not open
- */
- 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.
- *
- * Here is the standard idiom for transactions:
- *
- *
- * db.beginTransactionWithListener(listener);
- * try {
- * ...
- * db.setTransactionSuccessful();
- * } finally {
- * db.endTransaction();
- * }
- *
- * @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}.
- *
- * @throws IllegalStateException if the database is not open
- */
- 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.
- *
- * @throws IllegalStateException if the database is not open or is not locked by the current thread
- */
- 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 database is not open, 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 transaction was yielded, false if queue was empty or database was not open
- */
- 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() {
- /* safeguard: */
- if (!isOpen()) return false;
-
- 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, false if queue was empty or database was not open
- */
- public boolean yieldIfContendedSafely() {
- /* safeguard: */
- if (!isOpen()) return false;
-
- 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, false if queue was empty or database was not open
- *
- * @throws IllegalStateException if the database is locked more than once by the current thread
- * @throws InterruptedException if the thread was interrupted while sleeping
- */
- public boolean yieldIfContendedSafely(long sleepAfterYieldDelay) {
- /* safeguard: */
- if (!isOpen()) return false;
-
- 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 mSyncUpdateInfo =
- new HashMap();
-
- public Map getSyncedTables() {
- synchronized(mSyncUpdateInfo) {
- HashMap tables = new HashMap();
- 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}.
- *
- * Sets the locale of the database to the the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path to database file to open and/or create
- * @param password to use to open and/or create database file
- * @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 and other options
- *
- * @return the newly opened database
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags) {
- return openDatabase(path, password, factory, flags, null);
- }
-
- /**
- * Open the database according to the flags {@link #OPEN_READWRITE}
- * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
- *
- * Sets the locale of the database to the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path to database file to open and/or create
- * @param password to use to open and/or create database file (char array)
- * @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 and other options
- *
- * @return the newly opened database
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags) {
- return openDatabase(path, password, factory, flags, null, null);
- }
-
- /**
- * Open the database according to the flags {@link #OPEN_READWRITE}
- * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
- * with optional hook to run on pre/post key events.
- *
- * Sets the locale of the database to the the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path to database file to open and/or create
- * @param password to use to open and/or create database file
- * @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 and other options
- * @param hook to run on pre/post key events
- *
- * @return the newly opened database
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) {
- return openDatabase(path, password, factory, flags, hook, null);
- }
-
- /**
- * Open the database according to the flags {@link #OPEN_READWRITE}
- * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
- * with optional hook to run on pre/post key events.
- *
- * Sets the locale of the database to the the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path to database file to open and/or create
- * @param password to use to open and/or create database file (char array)
- * @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 and other options
- * @param hook to run on pre/post key events (may be null)
- *
- * @return the newly opened database
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook hook) {
- return openDatabase(path, password, factory, flags, hook, null);
- }
-
- /**
- * Open the database according to the flags {@link #OPEN_READWRITE}
- * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
- * with optional hook to run on pre/post key events.
- *
- * Sets the locale of the database to the the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path to database file to open and/or create
- * @param password to use to open and/or create database file
- * @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 and other options
- * @param hook to run on pre/post key events
- * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
- * corruption (or null for default).
- *
- * @return the newly opened database
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public static SQLiteDatabase openDatabase(String path, String password, CursorFactory factory, int flags,
- SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) {
- return openDatabase(path, password == null ? null : password.toCharArray(), factory, flags, hook, errorHandler);
- }
-
- /**
- * Open the database according to the flags {@link #OPEN_READWRITE}
- * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}
- * with optional hook to run on pre/post key events.
- *
- * Sets the locale of the database to the the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path to database file to open and/or create
- * @param password to use to open and/or create database file (char array)
- * @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 and other options
- * @param hook to run on pre/post key events (may be null)
- * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
- * corruption (or null for default).
- *
- * @return the newly opened database
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public static SQLiteDatabase openDatabase(String path, char[] password, CursorFactory factory, int flags,
- SQLiteDatabaseHook hook, DatabaseErrorHandler errorHandler) {
- SQLiteDatabase sqliteDatabase = null;
- DatabaseErrorHandler myErrorHandler = (errorHandler != null) ? errorHandler : new DefaultDatabaseErrorHandler();
-
- try {
- // Open the database.
- sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler);
- sqliteDatabase.openDatabaseInternal(password, hook);
- } catch (SQLiteDatabaseCorruptException e) {
- // Try to recover from this, if possible.
- // FUTURE TBD: should we consider this for other open failures?
- Log.e(TAG, "Calling error handler for corrupt database " + path, e);
-
- // NOTE: if this errorHandler.onCorruption() throws the exception _should_
- // bubble back to the original caller.
- // DefaultDatabaseErrorHandler deletes the corrupt file, EXCEPT for memory database
- myErrorHandler.onCorruption(sqliteDatabase);
-
- // try *once* again:
- sqliteDatabase = new SQLiteDatabase(path, factory, flags, myErrorHandler);
- sqliteDatabase.openDatabaseInternal(password, hook);
- }
-
- if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
- sqliteDatabase.enableSqlTracing(path);
- }
- if (SQLiteDebug.DEBUG_SQL_TIME) {
- sqliteDatabase.enableSqlProfiling(path);
- }
-
- synchronized (sActiveDatabases) {
- sActiveDatabases.put(sqliteDatabase, null);
- }
-
- return sqliteDatabase;
- }
-
- /**
- * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY, databaseHook).
- */
- public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
- return openOrCreateDatabase(file, password, factory, databaseHook, null);
- }
-
- /**
- * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY, 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(path, password, factory, CREATE_IF_NECESSARY, databaseHook).
- */
- public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
- DatabaseErrorHandler errorHandler) {
- return openOrCreateDatabase(file == null ? null : file.getPath(), password, factory, databaseHook, errorHandler);
- }
-
- public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
- DatabaseErrorHandler errorHandler) {
- return openDatabase(path, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
- }
-
- public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook) {
- return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook);
- }
-
- public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory, SQLiteDatabaseHook databaseHook,
- DatabaseErrorHandler errorHandler) {
- return openDatabase(path, password, factory, CREATE_IF_NECESSARY, databaseHook, errorHandler);
- }
-
- /**
- * Equivalent to openDatabase(file.getPath(), password, factory, CREATE_IF_NECESSARY).
- */
- public static SQLiteDatabase openOrCreateDatabase(File file, String password, CursorFactory factory) {
- return openOrCreateDatabase(file, password, factory, null);
- }
-
- /**
- * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY).
- */
- public static SQLiteDatabase openOrCreateDatabase(String path, String password, CursorFactory factory) {
- return openDatabase(path, password, factory, CREATE_IF_NECESSARY, null);
- }
-
- /**
- * Equivalent to openDatabase(path, password, factory, CREATE_IF_NECESSARY).
- */
- public static SQLiteDatabase openOrCreateDatabase(String path, char[] password, CursorFactory factory) {
- 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.
- *
- * Sets the locale of the database to the the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param factory an optional factory class that is called to instantiate a
- * cursor when query is called
- * @param password to use to open and/or create database file
- *
- * @return a SQLiteDatabase object, or null if the database can't be created
- *
- * @throws SQLiteException if the database cannot be opened
- */
- public static SQLiteDatabase create(CursorFactory factory, String password) {
- // This is a magic string with special meaning for SQLite.
- return openDatabase(MEMORY, password == null ? null : password.toCharArray(), factory, CREATE_IF_NECESSARY);
- }
-
- /**
- * Create a memory backed SQLite database. Its contents will be destroyed
- * when the database is closed.
- *
- * Sets the locale of the database to the the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param factory an optional factory class that is called to instantiate a
- * cursor when query is called
- * @param password to use to open and/or create database file (char array)
- *
- * @return a SQLiteDatabase object, or null if the database can't be created
- *
- * @throws SQLiteException if the database cannot be opened
- */
- public static SQLiteDatabase create(CursorFactory factory, char[] password) {
- 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> iter = mPrograms.entrySet().iterator();
- while (iter.hasNext()) {
- Map.Entry 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
- *
- * @throws IllegalStateException if the database is not open
- */
- 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
- *
- * @throws SQLiteException if there is an issue executing the sql internally
- * @throws IllegalStateException if the database is not open
- */
- 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
- *
- * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable
- * OR if the database is not open
- *
- * FUTURE @todo throw IllegalStateException if the database is not open and
- * update the test suite
- *
- * NOTE: This method was deprecated by the AOSP in Android API 11.
- */
- public void markTableSyncable(String table, String deletedTable) {
- /* safeguard: */
- if (!isOpen()) {
- throw new SQLiteException("database not open");
- }
-
- 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
- *
- * @throws SQLiteException if there is an issue executing the sql to mark the table as syncable
- *
- * FUTURE @todo throw IllegalStateException if the database is not open and
- * update the test suite
- *
- * NOTE: This method was deprecated by the AOSP in Android API 11.
- */
- public void markTableSyncable(String table, String foreignKey,
- String updateTable) {
- /* safeguard: */
- if (!isOpen()) {
- throw new SQLiteException("database not open");
- }
-
- 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
- *
- * @throws SQLiteException if there is an issue executing the sql
- */
- 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.
- *
- * @throws SQLException If the SQL string is invalid for some reason
- * @throws IllegalStateException if the database is not open
- */
- 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.
- *
- * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
- * @throws IllegalStateException if the database is not open
- *
- * @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.
- *
- * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
- * @throws IllegalStateException if the database is not open
- *
- * @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.
- *
- * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
- * @throws IllegalStateException if the database is not open
- *
- * @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.
- *
- * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
- * @throws IllegalStateException if the database is not open
- */
- public Cursor rawQuery(String sql, String[] selectionArgs) {
- return rawQueryWithFactory(null, sql, selectionArgs, null);
- }
-
-
- /**
- * 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 args You may include ?s in where clause in the query,
- * which will be replaced by the values from args. The
- * values will be bound by their type.
- *
- * @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.
- *
- * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
- * @throws IllegalStateException if the database is not open
- */
- public Cursor rawQuery(String sql, Object[] args) {
- if (!isOpen()) {
- throw new IllegalStateException("database not open");
- }
- long timeStart = 0;
- if (Config.LOGV || mSlowQueryThreshold != -1) {
- timeStart = System.currentTimeMillis();
- }
- SQLiteDirectCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, null);
- Cursor cursor = null;
- try {
- cursor = driver.query(mFactory, args);
- } 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 , count is " + count);
- }
- }
- }
- return new CrossProcessCursorWrapper(cursor);
- }
-
- /**
- * 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.
- *
- * @throws SQLiteException if there is an issue executing the sql or the SQL string is invalid
- * @throws IllegalStateException if the database is not open
- */
- 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 , 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) {
- net.sqlcipher.CursorWrapper cursorWrapper = (net.sqlcipher.CursorWrapper)rawQueryWithFactory(null, sql, selectionArgs, null);
- ((SQLiteCursor)cursorWrapper.getWrappedCursor()).setLoadStyle(initialRead, maxRead);
- return cursorWrapper;
- }
-
- /**
- * 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 into " + table, 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 into " + table, 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
- *
- * @throws SQLException If the SQL string is invalid for some reason
- * @throws IllegalStateException if the database is not open
- */
- 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> entrySet = null;
- if (initialValues != null && initialValues.size() > 0) {
- entrySet = initialValues.valueSet();
- Iterator> entriesIter = entrySet.iterator();
- sql.append('(');
-
- boolean needSeparator = false;
- while (entriesIter.hasNext()) {
- if (needSeparator) {
- sql.append(", ");
- values.append(", ");
- }
- needSeparator = true;
- Map.Entry 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> entriesIter = entrySet.iterator();
- for (int i = 0; i < size; i++) {
- Map.Entry entry = entriesIter.next();
- DatabaseUtils.bindObjectToProgram(statement, i + 1, entry.getValue());
-
- }
- }
-
- // Run the program and then cleanup
- statement.execute();
-
- long insertedRowId = lastChangeCount() > 0 ? lastInsertRow() : -1;
- if (insertedRowId == -1) {
- Log.e(TAG, "Error inserting using into " + table);
- } else {
- if (Config.LOGD && Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Inserting row " + insertedRowId +
- " from using into " + table);
- }
- }
- 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.
- *
- * @throws SQLException If the SQL string is invalid for some reason
- * @throws IllegalStateException if the database is not open
- */
- 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
- *
- * @throws SQLException If the SQL string is invalid for some reason
- * @throws IllegalStateException if the database is not open
- */
- 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
- *
- * @throws SQLException If the SQL string is invalid for some reason
- * @throws IllegalStateException if the database is not open
- */
- 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> entrySet = values.valueSet();
- Iterator> entriesIter = entrySet.iterator();
-
- while (entriesIter.hasNext()) {
- Map.Entry 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 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 +
- " rows using and for " + table);
- }
- return numChangedRows;
- } catch (SQLiteDatabaseCorruptException e) {
- onCorruption();
- throw e;
- } catch (SQLException e) {
- Log.e(TAG, "Error updating using for " + table);
- 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
- * @throws IllegalStateException if the database is not open
- */
- public void execSQL(String sql) throws SQLException {
- long timeStart = SystemClock.uptimeMillis();
- lock();
- if (!isOpen()) {
- throw new IllegalStateException("database not open");
- }
- try {
- native_execSQL(sql);
- } catch (SQLiteDatabaseCorruptException e) {
- onCorruption();
- throw e;
- } finally {
- unlock();
- }
- }
-
- public void rawExecSQL(String sql){
- long timeStart = SystemClock.uptimeMillis();
- lock();
- if (!isOpen()) {
- throw new IllegalStateException("database not open");
- }
- try {
- native_rawExecSQL(sql);
- } catch (SQLiteDatabaseCorruptException e) {
- onCorruption();
- throw e;
- } finally {
- 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,
- *
- * @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
- * @throws IllegalStateException if the database is not open
- */
- 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();
- }
- }
-
- @Override
- protected void finalize() {
- if (isOpen()) {
- Log.e(TAG, "close() was never explicitly called on database '" +
- mPath + "' ", mStackTrace);
- closeClosable();
- onAllReferencesReleased();
- }
- }
-
- /**
- * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}.
- *
- * Sets the locale of the database to the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path The full path to the database
- * @param password to use to open and/or create a database file (char array)
- * @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.
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags) {
- this(path, factory, flags, null);
- this.openDatabaseInternal(password, null);
- }
-
- /**
- * Public constructor which attempts to open the database. See {@link #create} and {@link #openDatabase}.
- *
- * Sets the locale of the database to the system's current locale.
- * Call {@link #setLocale} if you would like something else.
- *
- * @param path The full path to the database
- * @param password to use to open and/or create a database file (char array)
- * @param factory The factory to use when creating cursors, may be NULL.
- * @param flags 0 or {@link #NO_LOCALIZED_COLLATORS}. If the database file already
- * exists, mFlags will be updated appropriately.
- * @param databaseHook to run on pre/post key events
- *
- * @throws SQLiteException if the database cannot be opened
- * @throws IllegalArgumentException if the database path is null
- */
- public SQLiteDatabase(String path, char[] password, CursorFactory factory, int flags, SQLiteDatabaseHook databaseHook) {
- this(path, factory, flags, null);
- this.openDatabaseInternal(password, databaseHook);
- }
-
- /**
- * Private constructor (without database password) which DOES NOT attempt to open the database.
- *
- * @param path The full path to the database
- * @param factory The factory to use when creating cursors, may be NULL.
- * @param flags to control database access mode and other options
- * @param errorHandler The {@link DatabaseErrorHandler} to be used when sqlite reports database
- * corruption (or null for default).
- *
- * @throws IllegalArgumentException if the database path is null
- */
- private SQLiteDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {
- 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;
- mPrograms = new WeakHashMap();
-
- mErrorHandler = errorHandler;
- }
-
- private void openDatabaseInternal(final char[] password, SQLiteDatabaseHook hook) {
- boolean shouldCloseConnection = true;
- final byte[] keyMaterial = getBytes(password);
- dbopen(mPath, mFlags);
- try {
-
- keyDatabase(hook, new Runnable() {
- public void run() {
- if(keyMaterial != null && keyMaterial.length > 0) {
- key(keyMaterial);
- }
- }
- });
- shouldCloseConnection = false;
-
- } catch(RuntimeException ex) {
-
- if(containsNull(password)) {
- keyDatabase(hook, new Runnable() {
- public void run() {
- if(password != null) {
- key_mutf8(password);
- }
- }
- });
- if(keyMaterial != null && keyMaterial.length > 0) {
- rekey(keyMaterial);
- }
- shouldCloseConnection = false;
- } else {
- throw ex;
- }
-
- } finally {
- if(shouldCloseConnection) {
- dbclose();
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- mTimeClosed = getTime();
- }
- }
- if(keyMaterial != null && keyMaterial.length > 0) {
- for(byte data : keyMaterial) {
- data = 0;
- }
- }
- }
-
- }
-
- private boolean containsNull(char[] data) {
- char defaultValue = '\u0000';
- boolean status = false;
- if(data != null && data.length > 0) {
- for(char datum : data) {
- if(datum == defaultValue) {
- status = true;
- break;
- }
- }
- }
- return status;
- }
-
- private void keyDatabase(SQLiteDatabaseHook databaseHook, Runnable keyOperation) {
- if(databaseHook != null) {
- databaseHook.preKey(this);
- }
- if(keyOperation != null){
- keyOperation.run();
- }
- if(databaseHook != null){
- databaseHook.postKey(this);
- }
- if (SQLiteDebug.DEBUG_SQL_CACHE) {
- mTimeOpened = getTime();
- }
- try {
- Cursor cursor = rawQuery("select count(*) from sqlite_master;", new String[]{});
- if(cursor != null){
- cursor.moveToFirst();
- int count = cursor.getInt(0);
- cursor.close();
- }
- } catch (RuntimeException e) {
- Log.e(TAG, e.getMessage(), e);
- throw e;
- }
- }
-
- private String getTime() {
- return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS ", Locale.US).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) {
- /* NOTE: getVersion() will throw if database is not open. */
- return newVersion > getVersion();
- }
-
- /**
- * Getter for the path to the database file.
- *
- * @return the path to our database file.
- */
- public final String getPath() {
- return mPath;
- }
-
- /**
- * 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 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;
- }
-
- /**
- * 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 getDbStats() {
- ArrayList dbStatsList = new ArrayList();
-
- for (SQLiteDatabase db : getActiveDatabases()) {
- 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> attachedDbs = getAttachedDbs(db);
- if (attachedDbs == null) {
- continue;
- }
- for (int i = 0; i < attachedDbs.size(); i++) {
- Pair 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;
- }
-
- private static ArrayList getActiveDatabases() {
- ArrayList databases = new ArrayList();
- synchronized (sActiveDatabases) {
- databases.addAll(sActiveDatabases.keySet());
- }
- return databases;
- }
-
- /**
- * 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> getAttachedDbs(SQLiteDatabase dbObj) {
- if (!dbObj.isOpen()) {
- return null;
- }
- ArrayList> attachedDbs = new ArrayList>();
- Cursor c = dbObj.rawQuery("pragma database_list;", null);
- while (c.moveToNext()) {
- attachedDbs.add(new Pair(c.getString(1), c.getString(2)));
- }
- c.close();
- return attachedDbs;
- }
-
- private byte[] getBytes(char[] data) {
- if(data == null || data.length == 0) return null;
- CharBuffer charBuffer = CharBuffer.wrap(data);
- ByteBuffer byteBuffer = Charset.forName(KEY_ENCODING).encode(charBuffer);
- byte[] result = new byte[byteBuffer.limit()];
- byteBuffer.get(result);
- return result;
- }
-
- /**
- * 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);
-
- private native void native_key(char[] key) throws SQLException;
-
- private native void native_rekey(String key) throws SQLException;
-
- private native void key(byte[] key) throws SQLException;
- private native void key_mutf8(char[] key) throws SQLException;
- private native void rekey(byte[] key) throws SQLException;
-}
diff --git a/src/net/sqlcipher/database/SQLiteDatabaseCorruptException.java b/src/net/sqlcipher/database/SQLiteDatabaseCorruptException.java
deleted file mode 100644
index 2e7373ca..00000000
--- a/src/net/sqlcipher/database/SQLiteDatabaseCorruptException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-/**
- * An exception that indicates that the SQLite database file is corrupt.
- */
-public class SQLiteDatabaseCorruptException extends SQLiteException {
- public SQLiteDatabaseCorruptException() {}
-
- public SQLiteDatabaseCorruptException(String error) {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteDiskIOException.java b/src/net/sqlcipher/database/SQLiteDiskIOException.java
deleted file mode 100644
index 7302abe4..00000000
--- a/src/net/sqlcipher/database/SQLiteDiskIOException.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-/**
- * An exception that indicates that an IO error occured while accessing the
- * SQLite database file.
- */
-public class SQLiteDiskIOException extends SQLiteException {
- public SQLiteDiskIOException() {}
-
- public SQLiteDiskIOException(String error) {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteDoneException.java b/src/net/sqlcipher/database/SQLiteDoneException.java
deleted file mode 100644
index f0f6f0dc..00000000
--- a/src/net/sqlcipher/database/SQLiteDoneException.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-/**
- * An exception that indicates that the SQLite program is done.
- * Thrown when an operation that expects a row (such as {@link
- * SQLiteStatement#simpleQueryForString} or {@link
- * SQLiteStatement#simpleQueryForLong}) does not get one.
- */
-public class SQLiteDoneException extends SQLiteException {
- public SQLiteDoneException() {}
-
- public SQLiteDoneException(String error) {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteException.java b/src/net/sqlcipher/database/SQLiteException.java
deleted file mode 100644
index 2c7f11a3..00000000
--- a/src/net/sqlcipher/database/SQLiteException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-import net.sqlcipher.*;
-
-/**
- * A SQLite exception that indicates there was an error with SQL parsing or execution.
- */
-public class SQLiteException extends SQLException {
- public SQLiteException() {}
-
- public SQLiteException(String error) {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteFullException.java b/src/net/sqlcipher/database/SQLiteFullException.java
deleted file mode 100644
index 66af19fe..00000000
--- a/src/net/sqlcipher/database/SQLiteFullException.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-/**
- * An exception that indicates that the SQLite database is full.
- */
-public class SQLiteFullException extends SQLiteException {
- public SQLiteFullException() {}
-
- public SQLiteFullException(String error) {
- super(error);
- }
-}
diff --git a/src/net/sqlcipher/database/SQLiteMisuseException.java b/src/net/sqlcipher/database/SQLiteMisuseException.java
deleted file mode 100644
index ef261fc7..00000000
--- a/src/net/sqlcipher/database/SQLiteMisuseException.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sqlcipher.database;
-
-public class SQLiteMisuseException extends SQLiteException {
- public SQLiteMisuseException() {}
-
- public SQLiteMisuseException(String error) {
- super(error);
- }
-}