diff --git a/.gitignore b/.gitignore
index 6e37075..d0d2980 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ sqlciphertest/build
*.iml
local.properties
sqlcipher/.cxx
-sqlcipher/src/main/jni/sqlcipher/*.c
-sqlcipher/src/main/jni/sqlcipher/*.h
+sqlcipher/src/main/jni/sqlcipher/sqlite3.c
+sqlcipher/src/main/jni/sqlcipher/sqlite3.h
sqlcipher/src/main/jni/sqlcipher/android-libs
build
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 0eaa191..9ca7336 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,16 @@
.PHONY: clean build-debug build-release \
publish-snapshot-to-local-maven \
- publish-snapshot-to-local-nexus
+ publish-snapshot-to-local-nexus test
GRADLE = ./gradlew
clean:
-rm -rf build
$(GRADLE) clean
+test:
+ ANDROID_SERIAL=$(shell adb devices | tail -n +2 | awk '!/emulator/{print $$1}') \
+ $(GRADLE) :sqlcipher:connectedDebugAndroidTest
+
build-debug:
$(GRADLE) assembleDebug
diff --git a/README.md b/README.md
index 77fda99..184f9e9 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ SQLCipher for Android provides a library replacement for `android.database.sqlit
### Compatibility
-SQLCipher for Android supports Android API 21 and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64_v8a` architectures.
+SQLCipher for Android supports Android API 21 and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64-v8a` architectures.
### Contributions
@@ -16,14 +16,14 @@ We welcome contributions, to contribute to SQLCipher for Android, a [contributor
Add a local reference to the local library and dependency:
```groovy
-implementation files('libs/sqlcipher-android-4.6.0-release.aar')
+implementation files('libs/sqlcipher-android-undefined-release.aar')
implementation 'androidx.sqlite:sqlite:2.2.0'
```
or source a Community edition build from Maven Central:
```groovy
-implementation 'net.zetetic:sqlcipher-android:4.6.0@aar'
+implementation 'net.zetetic:sqlcipher-android:undefined@aar'
implementation 'androidx.sqlite:sqlite:2.2.0'
```
@@ -76,6 +76,34 @@ db = Room.databaseBuilder(context, AppDatabase.class, databaseFile.getAbsolutePa
.openHelperFactory(factory).build();
```
+### Logging
+
+Logging may occur in 3 distinct areas within this library:
+
+1. Within the Java client library
+2. Within the JNI interop layer
+3. Within SQLCipher core
+
+##### Java Client Logging
+
+By default, logging within the Java client library is routed to Logcat. If you wish to disable this logging entirely, you may utilize
+the [`NoopTarget`](sqlcipher/src/main/java/net/zetetic/database/NoopTarget.java) instead:
+
+```java
+Logger.setTarget(new NoopTarget());
+```
+
+You can instead provide a custom logging target by registering a different target that implements the [`LogTarget`](sqlcipher/src/main/java/net/zetetic/database/LogTarget.java) interface.
+
+##### JNI Interop Layer
+
+There are two different compile-specific options available to alter the logging output from the JNI layer. To remove `INFO`, `DEBUG`, and `VERBOSE` log messages from the JNI layer, include `-DNDEBUG` with CFLAGS; this will allow `WARN` and `ERROR` logs to output to logcat. Alternatively, to exclude all log output from JNI, build the library using `-DSQLCIPHER_OMIT_LOG`.
+
+##### SQLCipher core
+
+To manage the logging produced from SQLCipher core, please review the runtime configurations: [`PRAGMA cipher_log`](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log),
+[`PRAGMA cipher_log_level`](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log_level), and [`PRAGMA cipher_log_source`](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log_source).
+
### Building
## Android NDK
@@ -90,7 +118,7 @@ This repository is not batteries-included. Specifically, you will need to build
/sqlcipher/src/main/jni/sqlcipher/android-libs/armeabi-v7a/libcrypto.a
/sqlcipher/src/main/jni/sqlcipher/android-libs/x86/libcrypto.a
/sqlcipher/src/main/jni/sqlcipher/android-libs/x86_64/libcrypto.a
-/sqlcipher/src/main/jni/sqlcipher/android-libs/arm64_v8a/libcrypto.a
+/sqlcipher/src/main/jni/sqlcipher/android-libs/arm64-v8a/libcrypto.a
/sqlcipher/src/main/jni/sqlcipher/android-libs/include/
/sqlcipher/src/main/jni/sqlcipher/sqlite3.c
/sqlcipher/src/main/jni/sqlcipher/sqlite3.h
diff --git a/README.md.template b/README.md.template
index 1139295..1a215f0 100644
--- a/README.md.template
+++ b/README.md.template
@@ -4,7 +4,7 @@ SQLCipher for Android provides a library replacement for `android.database.sqlit
### Compatibility
-SQLCipher for Android supports Android API <%=minSdkVersion%> and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64_v8a` architectures.
+SQLCipher for Android supports Android API <%=minSdkVersion%> and up on `armeabi-v7a`, `x86`, `x86_64`, and `arm64-v8a` architectures.
### Contributions
@@ -76,6 +76,34 @@ db = Room.databaseBuilder(context, AppDatabase.class, databaseFile.getAbsolutePa
.openHelperFactory(factory).build();
```
+### Logging
+
+Logging may occur in 3 distinct areas within this library:
+
+1. Within the Java client library
+2. Within the JNI interop layer
+3. Within SQLCipher core
+
+##### Java Client Logging
+
+By default, logging within the Java client library is routed to Logcat. If you wish to disable this logging entirely, you may utilize
+the [`NoopTarget`](sqlcipher/src/main/java/net/zetetic/database/NoopTarget.java) instead:
+
+```java
+Logger.setTarget(new NoopTarget());
+```
+
+You can instead provide a custom logging target by registering a different target that implements the [`LogTarget`](sqlcipher/src/main/java/net/zetetic/database/LogTarget.java) interface.
+
+##### JNI Interop Layer
+
+There are two different compile-specific options available to alter the logging output from the JNI layer. To remove `INFO`, `DEBUG`, and `VERBOSE` log messages from the JNI layer, include `-DNDEBUG` with CFLAGS; this will allow `WARN` and `ERROR` logs to output to logcat. Alternatively, to exclude all log output from JNI, build the library using `-DSQLCIPHER_OMIT_LOG`.
+
+##### SQLCipher core
+
+To manage the logging produced from SQLCipher core, please review the runtime configurations: [`PRAGMA cipher_log`](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log),
+[`PRAGMA cipher_log_level`](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log_level), and [`PRAGMA cipher_log_source`](https://www.zetetic.net/sqlcipher/sqlcipher-api/#cipher_log_source).
+
### Building
## Android NDK
@@ -90,7 +118,7 @@ This repository is not batteries-included. Specifically, you will need to build
/sqlcipher/src/main/jni/sqlcipher/android-libs/armeabi-v7a/libcrypto.a
/sqlcipher/src/main/jni/sqlcipher/android-libs/x86/libcrypto.a
/sqlcipher/src/main/jni/sqlcipher/android-libs/x86_64/libcrypto.a
-/sqlcipher/src/main/jni/sqlcipher/android-libs/arm64_v8a/libcrypto.a
+/sqlcipher/src/main/jni/sqlcipher/android-libs/arm64-v8a/libcrypto.a
/sqlcipher/src/main/jni/sqlcipher/android-libs/include/
/sqlcipher/src/main/jni/sqlcipher/sqlite3.c
/sqlcipher/src/main/jni/sqlcipher/sqlite3.h
diff --git a/build.gradle b/build.gradle
index 3300e32..882915d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
- def gradleToolsVersion = "7.4.1"
+ def gradleToolsVersion = "8.7.2"
classpath "com.android.tools.build:gradle:${gradleToolsVersion}"
}
}
@@ -22,13 +22,18 @@ allprojects {
mavenCentral()
google()
}
+ tasks.withType(JavaCompile).tap {
+ configureEach {
+ options.compilerArgs << "-Xlint:deprecation"
+ }
+ }
}
project.ext {
if(project.hasProperty('sqlcipherAndroidVersion') && "${sqlcipherAndroidVersion}") {
libraryVersion = "${sqlcipherAndroidVersion}"
} else {
- libraryVersion = "4.6.0"
+ libraryVersion = "undefined"
}
minSdkVersion = 21
androidXSQLiteVersion = "2.2.0"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 93107ff..d76bd07 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Thu Jun 16 15:29:23 CDT 2022
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/sqlcipher/build.gradle b/sqlcipher/build.gradle
index d79a5e4..9732cc5 100644
--- a/sqlcipher/build.gradle
+++ b/sqlcipher/build.gradle
@@ -3,15 +3,19 @@ apply plugin: "maven-publish"
apply plugin: "signing"
android {
- compileSdkVersion 33
-
+ compileSdkVersion 34
+ namespace "net.zetetic.database"
defaultConfig {
minSdkVersion "${rootProject.ext.minSdkVersion}"
- targetSdkVersion 33
+ targetSdkVersion 34
versionCode 1
versionName "${rootProject.ext.libraryVersion}"
project.archivesBaseName = "sqlcipher-android-${versionName}"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+ // The following argument makes the Android Test Orchestrator run its
+ // "pm clear" command after each test invocation. This command ensures
+ // that the app's state is completely cleared between tests.
+ testInstrumentationRunnerArguments clearPackageData: 'true'
consumerProguardFile 'consumer-rules.pro'
}
@@ -50,9 +54,17 @@ android {
useLibrary 'android.test.base' // for android.test.AndroidTestCase
useLibrary 'android.test.runner' // for android.test.MoreAsserts
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+ }
+
+ publishing {
+ singleVariant('release') {
+ withSourcesJar()
+ withJavadocJar()
+ }
}
}
@@ -80,7 +92,6 @@ allprojects {
repositories {
// The order in which you list these repositories matter.
google()
- jcenter()
}
}
@@ -126,7 +137,6 @@ afterEvaluate {
artifactId = "sqlcipher-android"
version = "${rootProject.ext.mavenVersionName}"
from components.release
- artifact androidSourcesJar
pom {
name = "sqlcipher-android"
packaging = "aar"
@@ -173,13 +183,4 @@ afterEvaluate {
required { isReleaseBuild() && gradle.taskGraph.hasTask("publish") }
sign publishing.publications.mavenJava
}
-
- artifacts {
- archives androidSourcesJar
- }
-}
-
-task androidSourcesJar(type: Jar) {
- archiveClassifier.set('sources')
- from android.sourceSets.main.java.srcDirs
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/SQLCipherWALTestScenario.java b/sqlcipher/src/androidTest/java/net/zetetic/database/SQLCipherWALTestScenario.java
index 53a28e9..1540587 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/SQLCipherWALTestScenario.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/SQLCipherWALTestScenario.java
@@ -1,5 +1,7 @@
package net.zetetic.database;
+import static org.junit.Assert.assertEquals;
+
import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
@@ -83,6 +85,7 @@ public void setup() throws Exception {
}
@Test
+ @SuppressWarnings("deprecation")
public void testEncryptedWalMode() throws Exception {
// create database
final MyHelper helper = new MyHelper(mContext);
@@ -91,7 +94,7 @@ public void testEncryptedWalMode() throws Exception {
// verify that WAL journal mode is set
final Cursor pragmaCursor = helper.getWritableDatabase().rawQuery("PRAGMA journal_mode", null);
pragmaCursor.moveToFirst();
- Assert.assertEquals("wal", pragmaCursor.getString(pragmaCursor.getColumnIndex("journal_mode")));
+ assertEquals("wal", pragmaCursor.getString(pragmaCursor.getColumnIndex("journal_mode")));
pragmaCursor.close();
// start long running transaction
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java
index 4a849ee..230af5a 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/AbstractCursorTest.java
@@ -16,18 +16,34 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Context;
-import android.database.AbstractCursor;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.CursorIndexOutOfBoundsException;
-import android.database.CursorWindow;
import android.database.DataSetObserver;
+import net.zetetic.database.AbstractCursor;
+import net.zetetic.database.CursorWindow;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
+
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
-import android.test.InstrumentationTestCase;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.Suppress;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.util.ArrayList;
@@ -36,7 +52,8 @@
/**
* Test {@link AbstractCursor}.
*/
-public class AbstractCursorTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AbstractCursorTest {
private static final int POSITION0 = 0;
private static final int POSITION1 = 1;
private static final int ROW_MAX = 10;
@@ -53,31 +70,31 @@ public class AbstractCursorTest extends InstrumentationTestCase {
private File mDatabaseFile;
private AbstractCursor mDatabaseCursor;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
setupDatabase();
ArrayList list = createTestList(ROW_MAX, COLUMN_NAMES.length);
mTestAbstractCursor = new TestAbstractCursor(COLUMN_NAMES, list);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabaseCursor.close();
mTestAbstractCursor.close();
mDatabase.close();
if (mDatabaseFile.exists()) {
mDatabaseFile.delete();
}
- super.tearDown();
}
+ @Test
public void testConstructor() {
TestAbstractCursor abstractCursor = new TestAbstractCursor();
assertEquals(-1, abstractCursor.getPosition());
}
+ @Test
public void testGetBlob() {
try {
mTestAbstractCursor.getBlob(0);
@@ -87,6 +104,7 @@ public void testGetBlob() {
}
}
+ @Test
public void testRegisterDataSetObserver() {
MockDataSetObserver datasetObserver = new MockDataSetObserver();
@@ -110,6 +128,7 @@ public void testRegisterDataSetObserver() {
mDatabaseCursor.registerDataSetObserver(datasetObserver);
}
+ @Test
public void testRegisterContentObserver() {
MockContentObserver contentObserver = new MockContentObserver();
@@ -133,12 +152,14 @@ public void testRegisterContentObserver() {
mDatabaseCursor.registerContentObserver(contentObserver);
}
+ @Test
public void testSetNotificationUri() {
final Uri testUri = Settings.System.getUriFor(Settings.System.TIME_12_24);
- mDatabaseCursor.setNotificationUri(getInstrumentation().getContext().getContentResolver(),
+ mDatabaseCursor.setNotificationUri(ApplicationProvider.getApplicationContext().getContentResolver(),
testUri);
}
+ @Test
public void testRespond() {
Bundle b = new Bundle();
Bundle bundle = mDatabaseCursor.respond(b);
@@ -148,6 +169,7 @@ public void testRespond() {
assertSame(Bundle.EMPTY, bundle);
}
+ @Test
public void testRequery() {
MockDataSetObserver mock = new MockDataSetObserver();
mDatabaseCursor.registerDataSetObserver(mock);
@@ -156,6 +178,7 @@ public void testRequery() {
assertTrue(mock.hadCalledOnChanged());
}
+ @Test
public void testOnChange() throws InterruptedException {
MockContentObserver mock = new MockContentObserver();
mTestAbstractCursor.registerContentObserver(mock);
@@ -169,6 +192,8 @@ public void testOnChange() throws InterruptedException {
assertTrue(mock.hadCalledOnChange());
}
+ @Suppress
+ @Test
public void testOnMove() {
assertFalse(mTestAbstractCursor.getOnMoveRet());
mTestAbstractCursor.moveToFirst();
@@ -182,6 +207,8 @@ public void testOnMove() {
assertEquals(5, mTestAbstractCursor.getNewPos());
}
+ @Suppress
+ @Test
public void testOnMove_samePosition() {
mTestAbstractCursor.moveToFirst();
mTestAbstractCursor.moveToPosition(5);
@@ -192,6 +219,7 @@ public void testOnMove_samePosition() {
assertEquals(6, mTestAbstractCursor.getRowsMovedSum());
}
+ @Test
public void testMoveToPrevious() {
// Test moveToFirst, isFirst, moveToNext, getPosition
assertTrue(mDatabaseCursor.moveToFirst());
@@ -262,50 +290,29 @@ public void testMoveToPrevious() {
assertFalse(mDatabaseCursor.isAfterLast());
}
+ @Test
public void testIsClosed() {
assertFalse(mDatabaseCursor.isClosed());
mDatabaseCursor.close();
assertTrue(mDatabaseCursor.isClosed());
}
- public void testGetWindow() {
- CursorWindow window = new CursorWindow(false);
- assertEquals(0, window.getNumRows());
- // fill window from position 0
- mDatabaseCursor.fillWindow(0, window);
-
- assertNotNull(mDatabaseCursor.getWindow());
- assertEquals(mDatabaseCursor.getCount(), window.getNumRows());
-
- while (mDatabaseCursor.moveToNext()) {
- assertEquals(mDatabaseCursor.getInt(POSITION1),
- window.getInt(mDatabaseCursor.getPosition(), POSITION1));
- }
- window.clear();
- }
-
+ @Test
public void testGetWantsAllOnMoveCalls() {
assertFalse(mDatabaseCursor.getWantsAllOnMoveCalls());
}
- public void testIsFieldUpdated() {
- mTestAbstractCursor.moveToFirst();
- assertFalse(mTestAbstractCursor.isFieldUpdated(0));
- }
-
- public void testGetUpdatedField() {
- mTestAbstractCursor.moveToFirst();
- assertNull(mTestAbstractCursor.getUpdatedField(0));
- }
-
+ @Test
public void testGetExtras() {
assertSame(Bundle.EMPTY, mDatabaseCursor.getExtras());
}
+ @Test
public void testGetCount() {
assertEquals(DATA_COUNT, mDatabaseCursor.getCount());
}
+ @Test
public void testGetColumnNames() {
String[] names = mDatabaseCursor.getColumnNames();
assertEquals(COLUMN_NAMES1.length, names.length);
@@ -315,11 +322,13 @@ public void testGetColumnNames() {
}
}
+ @Test
public void testGetColumnName() {
assertEquals(COLUMN_NAMES1[0], mDatabaseCursor.getColumnName(0));
assertEquals(COLUMN_NAMES1[1], mDatabaseCursor.getColumnName(1));
}
+ @Test
public void testGetColumnIndexOrThrow() {
final String COLUMN_FAKE = "fake_name";
assertEquals(POSITION0, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION0]));
@@ -335,15 +344,18 @@ public void testGetColumnIndexOrThrow() {
}
}
+ @Test
public void testGetColumnIndex() {
assertEquals(POSITION0, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION0]));
assertEquals(POSITION1, mDatabaseCursor.getColumnIndex(COLUMN_NAMES1[POSITION1]));
}
+ @Test
public void testGetColumnCount() {
assertEquals(COLUMN_NAMES1.length, mDatabaseCursor.getColumnCount());
}
+ @Test
public void testDeactivate() {
MockDataSetObserver mock = new MockDataSetObserver();
mDatabaseCursor.registerDataSetObserver(mock);
@@ -352,11 +364,13 @@ public void testDeactivate() {
assertTrue(mock.hadCalledOnInvalid());
}
+ @Suppress
+ @Test
public void testCopyStringToBuffer() {
CharArrayBuffer ca = new CharArrayBuffer(1000);
mTestAbstractCursor.moveToFirst();
mTestAbstractCursor.copyStringToBuffer(0, ca);
- CursorWindow window = new CursorWindow(false);
+ CursorWindow window = new CursorWindow("");
mTestAbstractCursor.fillWindow(0, window);
StringBuffer sb = new StringBuffer();
@@ -366,6 +380,8 @@ public void testCopyStringToBuffer() {
assertEquals(sb.toString(), new String(ca.data, 0, ca.sizeCopied));
}
+ @Suppress
+ @Test
public void testCheckPosition() {
// Test with position = -1.
try {
@@ -389,6 +405,7 @@ public void testCheckPosition() {
}
}
+ @Test
public void testSetExtras() {
Bundle b = new Bundle();
mTestAbstractCursor.setExtras(b);
@@ -415,7 +432,7 @@ private static ArrayList createTestList(int rows, int cols) {
}
private void setupDatabase() {
- File dbDir = getInstrumentation().getTargetContext().getDir("tests",
+ File dbDir = ApplicationProvider.getApplicationContext().getDir("tests",
Context.MODE_PRIVATE);
mDatabaseFile = new File(dbDir, "database_test.db");
if (mDatabaseFile.exists()) {
@@ -502,7 +519,6 @@ public int getRowsMovedSum() {
@Override
public boolean onMove(int oldPosition, int newPosition) {
- mOnMoveReturnValue = super.onMove(oldPosition, newPosition);
mOldPosition = oldPosition;
mNewPosition = newPosition;
mRowsMovedSum += Math.abs(newPosition - oldPosition);
@@ -560,26 +576,20 @@ public boolean isNull(int column) {
return false;
}
+ @Override
+ public int getType(int column) {
+ return 0;
+ }
+
public boolean hadCalledOnChange() {
return mHadCalledOnChange;
}
- // the following are protected methods
@Override
protected void checkPosition() {
super.checkPosition();
}
- @Override
- protected Object getUpdatedField(int columnIndex) {
- return super.getUpdatedField(columnIndex);
- }
-
- @Override
- protected boolean isFieldUpdated(int columnIndex) {
- return super.isFieldUpdated(columnIndex);
- }
-
@Override
protected void onChange(boolean selfChange) {
super.onChange(selfChange);
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorJoinerTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorJoinerTest.java
index 7911ac1..494a386 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorJoinerTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorJoinerTest.java
@@ -17,16 +17,29 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Context;
import android.database.Cursor;
import android.database.CursorJoiner;
import android.database.CursorJoiner.Result;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
-import android.test.AndroidTestCase;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
-public class CursorJoinerTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class CursorJoinerTest {
private static final int TEST_ITEM_COUNT = 10;
private static final int DEFAULT_TABLE1_VALUE_BEGINS = 1;
@@ -44,20 +57,20 @@ public class CursorJoinerTest extends AndroidTestCase {
private SQLiteDatabase mDatabase;
private File mDatabaseFile;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
setupDatabase();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
mDatabaseFile.delete();
- super.tearDown();
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testCursorJoinerAndIterator() {
Cursor cursor1 = getCursor(TABLE_NAME_1, null, null);
Cursor cursor2 = getCursor(TABLE_NAME_2, null, null);
@@ -132,6 +145,7 @@ public void testCursorJoinerAndIterator() {
closeCursor(cursor1);
}
+ @Test
public void testNext() {
String[] columnNames = new String[] { "number" };
Cursor cursor1 = getCursor(TABLE_NAME_1, null, columnNames);
@@ -205,7 +219,7 @@ private void initializeTables() {
}
private void setupDatabase() {
- File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE);
+ File dbDir = ApplicationProvider.getApplicationContext().getDir("tests", Context.MODE_PRIVATE);
mDatabaseFile = new File(dbDir, "database_test.db");
if (mDatabaseFile.exists()) {
mDatabaseFile.delete();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java
index 7b9cb87..02df1b4 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWindowTest.java
@@ -16,21 +16,40 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.database.CharArrayBuffer;
-import android.database.CursorWindow;
-import android.database.MatrixCursor;
import android.database.sqlite.SQLiteException;
-import android.os.Parcel;
-import android.test.AndroidTestCase;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import net.zetetic.database.CursorWindow;
+import net.zetetic.database.MatrixCursor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
-public class CursorWindowTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class CursorWindowTest {
private static final String TEST_STRING = "Test String";
+ @Before
+ public void setUp() {
+ System.loadLibrary("sqlcipher");
+ }
+
+ @Test
public void testWriteCursorToWindow() throws Exception {
// create cursor
String[] colNames = new String[]{"_id", "name", "number", "profit"};
@@ -42,7 +61,7 @@ public void testWriteCursorToWindow() throws Exception {
}
// fill window
- CursorWindow window = new CursorWindow(false);
+ CursorWindow window = new CursorWindow("");
cursor.fillWindow(0, window);
// read from cursor window
@@ -75,6 +94,7 @@ public void testWriteCursorToWindow() throws Exception {
assertEquals(0, window.getNumRows());
}
+ @Test
public void testNull() {
CursorWindow window = getOneByOneWindow();
@@ -82,10 +102,11 @@ public void testNull() {
assertTrue(window.putNull(0, 0));
assertNull(window.getString(0, 0));
assertEquals(0, window.getLong(0, 0));
- assertEquals(0.0, window.getDouble(0, 0));
+ assertEquals(0.0, window.getDouble(0, 0), 0.001d);
assertNull(window.getBlob(0, 0));
}
+ @Test
public void testEmptyString() {
CursorWindow window = getOneByOneWindow();
@@ -93,38 +114,19 @@ public void testEmptyString() {
assertTrue(window.putString("", 0, 0));
assertEquals("", window.getString(0, 0));
assertEquals(0, window.getLong(0, 0));
- assertEquals(0.0, window.getDouble(0, 0));
+ assertEquals(0.0, window.getDouble(0, 0), 0.001d);
}
+ @Test
public void testConstructors() {
- int TEST_NUMBER = 5;
- CursorWindow cursorWindow;
-
// Test constructor with 'true' input, and getStartPosition should return 0
- cursorWindow = new CursorWindow(true);
+ CursorWindow cursorWindow = new CursorWindow("");
assertEquals(0, cursorWindow.getStartPosition());
-
- // Test constructor with 'false' input
- cursorWindow = new CursorWindow(false);
- assertEquals(0, cursorWindow.getStartPosition());
-
- // Test newFromParcel
- Parcel parcel = Parcel.obtain();
- cursorWindow = new CursorWindow(true);
- cursorWindow.setStartPosition(TEST_NUMBER);
- cursorWindow.setNumColumns(1);
- cursorWindow.allocRow();
- cursorWindow.putString(TEST_STRING, TEST_NUMBER, 0);
- cursorWindow.writeToParcel(parcel, 0);
-
- parcel.setDataPosition(0);
- cursorWindow = CursorWindow.CREATOR.createFromParcel(parcel);
- assertEquals(TEST_NUMBER, cursorWindow.getStartPosition());
- assertEquals(TEST_STRING, cursorWindow.getString(TEST_NUMBER, 0));
}
+ @Test
public void testDataStructureOperations() {
- CursorWindow cursorWindow = new CursorWindow(true);
+ CursorWindow cursorWindow = new CursorWindow("");
// Test with normal values
assertTrue(cursorWindow.setNumColumns(0));
@@ -145,7 +147,7 @@ public void testDataStructureOperations() {
cursorWindow.freeLastRow();
assertEquals(0, cursorWindow.getNumRows());
- cursorWindow = new CursorWindow(true);
+ cursorWindow = new CursorWindow("");
assertTrue(cursorWindow.setNumColumns(6));
assertTrue(cursorWindow.allocRow());
// Column number set to negative number, so now can put values.
@@ -175,6 +177,7 @@ public void testDataStructureOperations() {
}
}
+ @Test
public void testAccessDataValues() {
final long NUMBER_LONG_INTEGER = (long) 0xaabbccddffL;
final long NUMBER_INTEGER = (int) NUMBER_LONG_INTEGER;
@@ -190,7 +193,7 @@ public void testAccessDataValues() {
originalBlob[i] = (byte) i;
}
- CursorWindow cursorWindow = new CursorWindow(true);
+ CursorWindow cursorWindow = new CursorWindow("");
cursorWindow.setNumColumns(5);
cursorWindow.allocRow();
@@ -214,7 +217,7 @@ public void testAccessDataValues() {
assertEquals(0, cursorWindow.getLong(0, 0));
assertEquals(0, cursorWindow.getInt(0, 0));
assertEquals(0, cursorWindow.getShort(0, 0));
- assertEquals(0.0, cursorWindow.getDouble(0, 0));
+ assertEquals(0.0, cursorWindow.getDouble(0, 0), 0.001d);
assertEquals(0.0f, cursorWindow.getFloat(0, 0), 0.00000001f);
assertFalse(cursorWindow.isNull(0, 0));
assertFalse(cursorWindow.isBlob(0, 0));
@@ -226,7 +229,7 @@ public void testAccessDataValues() {
assertEquals(0, cursorWindow.getLong(0, 1));
assertEquals(0, cursorWindow.getInt(0, 1));
assertEquals(0, cursorWindow.getShort(0, 1));
- assertEquals(0.0, cursorWindow.getDouble(0, 1));
+ assertEquals(0.0, cursorWindow.getDouble(0, 1), 0.001d);
assertEquals(0.0f, cursorWindow.getFloat(0, 1), 0.00000001f);
assertNull(cursorWindow.getBlob(0, 1));
assertTrue(cursorWindow.isNull(0, 1));
@@ -279,8 +282,9 @@ public void testAccessDataValues() {
assertTrue(cursorWindow.isBlob(0, 4));
}
+ @Test
public void testCopyStringToBuffer() {
- int DEFAULT_ARRAY_LENGTH = 64;
+ int DEFAULT_ARRAY_LENGTH = 60;
String baseString = "0123456789";
String expectedString = "";
// Create a 60 characters string.
@@ -288,7 +292,7 @@ public void testCopyStringToBuffer() {
expectedString += baseString;
}
CharArrayBuffer charArrayBuffer = new CharArrayBuffer(null);
- CursorWindow cursorWindow = new CursorWindow(true);
+ CursorWindow cursorWindow = new CursorWindow("");
cursorWindow.setNumColumns(2);
cursorWindow.allocRow();
@@ -314,11 +318,12 @@ public void testCopyStringToBuffer() {
assertEquals(expectedString.length(), charArrayBuffer.data.length);
}
+ @Test
public void testAccessStartPosition() {
final int TEST_POSITION_1 = 0;
final int TEST_POSITION_2 = 3;
- CursorWindow cursorWindow = new CursorWindow(true);
+ CursorWindow cursorWindow = new CursorWindow("");
fillCursorTestContents(cursorWindow, 5);
// Test setStartPosition
@@ -343,6 +348,7 @@ public void testAccessStartPosition() {
}
}
+ @Test
public void testClearAndOnAllReferencesReleased() {
MockCursorWindow cursorWindow = new MockCursorWindow(true);
@@ -369,16 +375,11 @@ public void testClearAndOnAllReferencesReleased() {
assertTrue(cursorWindow.hasReleasedAllReferences());
}
- public void testDescribeContents() {
- CursorWindow cursorWindow = new CursorWindow(true);
- assertEquals(0, cursorWindow.describeContents());
- }
-
private class MockCursorWindow extends CursorWindow {
private boolean mHasReleasedAllReferences = false;
public MockCursorWindow(boolean localWindow) {
- super(localWindow);
+ super("");
}
@Override
@@ -426,7 +427,7 @@ private static ArrayList> createTestList(int rows, int cols)
* The method comes from unit_test CursorWindowTest.
*/
private CursorWindow getOneByOneWindow() {
- CursorWindow window = new CursorWindow(false);
+ CursorWindow window = new CursorWindow("");
assertTrue(window.setNumColumns(1));
assertTrue(window.allocRow());
return window;
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWrapperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWrapperTest.java
index 042905f..4372c2f 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWrapperTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/CursorWrapperTest.java
@@ -17,6 +17,13 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Context;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
@@ -26,14 +33,22 @@
import android.database.StaleDataException;
import android.os.Bundle;
import android.os.Handler;
-import android.test.AndroidTestCase;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.util.Arrays;
-public class CursorWrapperTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class CursorWrapperTest {
private static final String FIRST_NUMBER = "123";
private static final String SECOND_NUMBER = "5555";
@@ -52,20 +67,20 @@ public class CursorWrapperTest extends AndroidTestCase {
private static final int CURRENT_DATABASE_VERSION = 42;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
setupDatabase();
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
closeDatabase();
- super.tearDown();
}
- public void testConstrucotorAndClose() {
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testConstructorAndClose() {
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
assertTrue(cursorWrapper.requery());
@@ -85,6 +100,8 @@ private Cursor getCursor() {
return cursor;
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testGetCount() {
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
int defaultCount = cursorWrapper.getCount();
@@ -119,6 +136,8 @@ public void testGetCount() {
rebuildDatabase();
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testDeactivate() throws IllegalStateException {
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
MockObserver observer = new MockObserver();
@@ -175,6 +194,7 @@ public void testDeactivate() throws IllegalStateException {
}
}
+ @Test
public void testGettingColumnInfos() {
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
@@ -205,6 +225,7 @@ public void testGettingColumnInfos() {
cursorWrapper.close();
}
+ @Test
public void testPositioning() {
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
@@ -282,6 +303,7 @@ public void testPositioning() {
cursorWrapper.close();
}
+ @Test
public void testGettingValues() {
final byte NUMBER_BLOB_UNIT = 99;
final String STRING_TEXT = "Test String";
@@ -358,12 +380,14 @@ public void testGettingValues() {
mDatabase.execSQL("DROP TABLE test2");
}
+ @Test
public void testGetExtras() {
CursorWrapper cursor = new CursorWrapper(getCursor());
Bundle bundle = cursor.getExtras();
assertSame(Bundle.EMPTY, bundle);
}
+ @Test
public void testCopyStringToBuffer() {
CharArrayBuffer charArrayBuffer = new CharArrayBuffer(1000);
Cursor cursor = getCursor();
@@ -390,6 +414,7 @@ public void testCopyStringToBuffer() {
cursorWrapper.close();
}
+ @Test
public void testRespond() {
Bundle b = new Bundle();
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
@@ -398,12 +423,15 @@ public void testRespond() {
cursorWrapper.close();
}
+ @Test
public void testGetWantsAllOnMoveCalls() {
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
assertFalse(cursorWrapper.getWantsAllOnMoveCalls());
cursorWrapper.close();
}
+
+ @Test
public void testContentObserverOperations() throws IllegalStateException {
CursorWrapper cursorWrapper = new CursorWrapper(getCursor());
MockContentObserver observer = new MockContentObserver(null);
@@ -439,6 +467,7 @@ public void testContentObserverOperations() throws IllegalStateException {
cursorWrapper.close();
}
+ @Test
public void testSetExtras() {
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
Cursor cursor = getCursor();
@@ -473,7 +502,7 @@ private void deleteAllRecords(SQLiteDatabase database) {
}
private void setupDatabase() {
- File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE);
+ File dbDir = ApplicationProvider.getApplicationContext().getDir("tests", Context.MODE_PRIVATE);
/* don't use the same database name as the one in super class
* this class's setUp() method deletes a database file just opened by super.setUp().
* that can cause corruption in database in the following situation:
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseCursorTest.java
index 0985c94..4d161d1 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseCursorTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseCursorTest.java
@@ -16,6 +16,13 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -28,17 +35,22 @@
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteQuery;
import android.os.Looper;
-import android.test.AndroidTestCase;
-import android.test.PerformanceTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.util.Arrays;
import java.util.Random;
-public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTestCase {
+@RunWith(AndroidJUnit4.class)
+public class DatabaseCursorTest {
private static final String sString1 = "this is a test";
private static final String sString2 = "and yet another test";
private static final String sString3 = "this string is a little longer, but still a test";
@@ -49,12 +61,13 @@ public class DatabaseCursorTest extends AndroidTestCase implements PerformanceTe
protected static final int TYPE_CURSOR = 0;
protected static final int TYPE_CURSORWRAPPER = 1;
private int mTestType = TYPE_CURSOR;
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
System.loadLibrary("sqlcipher");
- File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE);
+ File dbDir = mContext.getDir("tests", Context.MODE_PRIVATE);
mDatabaseFile = new File(dbDir, "database_test.db");
if (mDatabaseFile.exists()) {
mDatabaseFile.delete();
@@ -64,11 +77,10 @@ protected void setUp() throws Exception {
mDatabase.setVersion(CURRENT_DATABASE_VERSION);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
mDatabaseFile.delete();
- super.tearDown();
}
public void setupTestType(int testType) {
@@ -89,11 +101,6 @@ public boolean isPerformanceOnly() {
return false;
}
- // These test can only be run once.
- public int startPerformance(Intermediates intermediates) {
- return 1;
- }
-
private void populateDefaultTable() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
@@ -102,7 +109,7 @@ private void populateDefaultTable() {
mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
}
- @MediumTest
+ @Test
public void testBlob() throws Exception {
// create table
mDatabase.execSQL(
@@ -137,13 +144,13 @@ public void testBlob() throws Exception {
int dCol = testCursor.getColumnIndexOrThrow("d");
int lCol = testCursor.getColumnIndexOrThrow("l");
byte[] cBlob = testCursor.getBlob(bCol);
- assertTrue(Arrays.equals(blob, cBlob));
+ assertArrayEquals(blob, cBlob);
assertEquals(s, testCursor.getString(sCol));
- assertEquals((double) d, testCursor.getDouble(dCol));
+ assertEquals(d, testCursor.getDouble(dCol), 0.001d);
assertEquals((long) l, testCursor.getLong(lCol));
}
- @MediumTest
+ @Test
public void testRealColumns() throws Exception {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data REAL);");
ContentValues values = new ContentValues();
@@ -153,11 +160,11 @@ public void testRealColumns() throws Exception {
Cursor testCursor = getTestCursor(mDatabase.rawQuery("SELECT data FROM test", null));
assertNotNull(testCursor);
assertTrue(testCursor.moveToFirst());
- assertEquals(42.11, testCursor.getDouble(0));
+ assertEquals(42.11, testCursor.getDouble(0), 0.001d);
testCursor.close();
}
- @MediumTest
+ @Test
public void testCursor1() throws Exception {
populateDefaultTable();
@@ -206,9 +213,8 @@ public void testCursor1() throws Exception {
assertEquals(sString3, s);
int i;
-
- for (testCursor.moveToFirst(), i = 0; !testCursor.isAfterLast();
- testCursor.moveToNext(), i++) {
+ testCursor.moveToFirst();
+ for (i = 0; !testCursor.isAfterLast(); testCursor.moveToNext(), i++) {
testCursor.getInt(0);
}
@@ -223,7 +229,7 @@ public void testCursor1() throws Exception {
testCursor.close();
}
- @MediumTest
+ @Test
public void testCursor2() throws Exception {
populateDefaultTable();
@@ -240,8 +246,8 @@ public void testCursor2() throws Exception {
}
int i;
- for (testCursor.moveToFirst(), i = 0; !testCursor.isAfterLast();
- testCursor.moveToNext(), i++) {
+ testCursor.moveToFirst();
+ for (i = 0; !testCursor.isAfterLast(); testCursor.moveToNext(), i++) {
testCursor.getInt(0);
}
assertEquals(0, i);
@@ -254,7 +260,7 @@ public void testCursor2() throws Exception {
testCursor.close();
}
- @MediumTest
+ @Test
public void testLargeField() throws Exception {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
@@ -312,7 +318,7 @@ public void onInvalidated() {
}
}
- @LargeTest
+ @Test
public void testManyRowsLong() throws Exception {
mDatabase.beginTransaction();
final int count = 9000;
@@ -343,7 +349,7 @@ public void testManyRowsLong() throws Exception {
testCursor.close();
}
- @LargeTest
+ @Test
public void testManyRowsTxt() throws Exception {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
StringBuilder sql = new StringBuilder(2100);
@@ -376,7 +382,7 @@ public void testManyRowsTxt() throws Exception {
testCursor.close();
}
- @LargeTest
+ @Test
public void testManyRowsTxtLong() throws Exception {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, txt TEXT, data INT);");
@@ -413,8 +419,9 @@ public void testManyRowsTxtLong() throws Exception {
testCursor.close();
}
- @MediumTest
- public void testRequery() throws Exception {
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testReQuery() throws Exception {
populateDefaultTable();
Cursor testCursor = getTestCursor(mDatabase.rawQuery("SELECT * FROM test", null));
@@ -426,8 +433,9 @@ public void testRequery() throws Exception {
testCursor.close();
}
- @MediumTest
- public void testRequeryWithSelection() throws Exception {
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testReQueryWithSelection() throws Exception {
populateDefaultTable();
Cursor testCursor = getTestCursor(
@@ -445,8 +453,9 @@ public void testRequeryWithSelection() throws Exception {
testCursor.close();
}
- @MediumTest
- public void testRequeryWithSelectionArgs() throws Exception {
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testReQueryWithSelectionArgs() throws Exception {
populateDefaultTable();
Cursor testCursor = getTestCursor(mDatabase.rawQuery("SELECT data FROM test WHERE data = ?",
@@ -463,8 +472,9 @@ public void testRequeryWithSelectionArgs() throws Exception {
testCursor.close();
}
- @MediumTest
- public void testRequeryWithAlteredSelectionArgs() throws Exception {
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testReQueryWithAlteredSelectionArgs() throws Exception {
/**
* Test the ability of a subclass of SQLiteCursor to change its query arguments.
*/
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtils_InsertHelperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsInsertHelperTest.java
similarity index 87%
rename from sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtils_InsertHelperTest.java
rename to sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsInsertHelperTest.java
index ba990f7..1d97d19 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtils_InsertHelperTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsInsertHelperTest.java
@@ -17,49 +17,69 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import net.zetetic.database.DatabaseUtils.InsertHelper;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
+import java.util.Arrays;
-public class DatabaseUtils_InsertHelperTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("deprecation")
+public class DatabaseUtilsInsertHelperTest {
private static final String TEST_TABLE_NAME = "test";
private static final String DATABASE_NAME = "database_test.db";
private SQLiteDatabase mDatabase;
private InsertHelper mInsertHelper;
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
System.loadLibrary("sqlcipher");
- getContext().deleteDatabase(DATABASE_NAME);
+ mContext.deleteDatabase(DATABASE_NAME);
File f = mContext.getDatabasePath(DATABASE_NAME);
mDatabase = SQLiteDatabase.openOrCreateDatabase(f, null);
assertNotNull(mDatabase);
mInsertHelper = new InsertHelper(mDatabase, TEST_TABLE_NAME);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mInsertHelper.close();
mDatabase.close();
- getContext().deleteDatabase(DATABASE_NAME);
- super.tearDown();
+ mContext.deleteDatabase(DATABASE_NAME);
}
+ @Test
public void testConstructor() {
new InsertHelper(mDatabase, TEST_TABLE_NAME);
}
+ @Test
public void testClose() {
mInsertHelper.close();
}
+ @Test
public void testGetColumnIndex() {
mDatabase.execSQL("CREATE TABLE " + TEST_TABLE_NAME + " (_id INTEGER PRIMARY KEY, " +
"name TEXT, age INTEGER, address TEXT);");
@@ -75,6 +95,7 @@ public void testGetColumnIndex() {
}
}
+ @Test
public void testInsert() {
mDatabase.execSQL("CREATE TABLE " + TEST_TABLE_NAME + "(_id INTEGER PRIMARY KEY," +
" boolean_value INTEGER, int_value INTEGER, long_value INTEGER," +
@@ -132,11 +153,11 @@ public void testInsert() {
assertEquals(1, cursor.getInt(booleanValueIndex));
assertEquals(10, cursor.getInt(intValueIndex));
assertEquals(1000L, cursor.getLong(longValueIndex));
- assertEquals(123.456, cursor.getDouble(doubleValueIndex));
- assertEquals(1.0f, cursor.getFloat(floatValueIndex));
+ assertEquals(123.456d, cursor.getDouble(doubleValueIndex), 0.001d);
+ assertEquals(1.0f, cursor.getFloat(floatValueIndex), 0.001d);
assertEquals("test insert", cursor.getString(stringValueIndex));
byte[] value = cursor.getBlob(blobValueIndex);
- MoreAsserts.assertEquals(blob, value);
+ assertTrue(Arrays.equals(blob, value));
assertNull(cursor.getString(nullValueIndex));
cursor.close();
@@ -164,11 +185,11 @@ public void testInsert() {
assertEquals(0, cursor.getInt(booleanValueIndex));
assertEquals(123, cursor.getInt(intValueIndex));
assertEquals(987654L, cursor.getLong(longValueIndex));
- assertEquals(654.321, cursor.getDouble(doubleValueIndex));
- assertEquals(21.1f, cursor.getFloat(floatValueIndex));
+ assertEquals(654.321d, cursor.getDouble(doubleValueIndex), 0.001d);
+ assertEquals(21.1f, cursor.getFloat(floatValueIndex), 0.001d);
assertEquals("insert another row", cursor.getString(stringValueIndex));
value = cursor.getBlob(blobValueIndex);
- MoreAsserts.assertEquals(blob, value);
+ assertTrue(Arrays.equals(blob, value));
assertNull(cursor.getString(nullValueIndex));
cursor.close();
@@ -177,6 +198,7 @@ public void testInsert() {
assertEquals(-1, mInsertHelper.insert(values));
}
+ @Test
public void testReplace() {
mDatabase.execSQL("CREATE TABLE " + TEST_TABLE_NAME + "(_id INTEGER PRIMARY KEY," +
" boolean_value INTEGER, int_value INTEGER, long_value INTEGER," +
@@ -228,11 +250,11 @@ public void testReplace() {
assertEquals(1, cursor.getInt(booleanValueIndex));
assertEquals(10, cursor.getInt(intValueIndex));
assertEquals(1000L, cursor.getLong(longValueIndex));
- assertEquals(123.456, cursor.getDouble(doubleValueIndex));
- assertEquals(1.0f, cursor.getFloat(floatValueIndex));
+ assertEquals(123.456, cursor.getDouble(doubleValueIndex), .001d);
+ assertEquals(1.0f, cursor.getFloat(floatValueIndex), 0.001d);
assertEquals("test insert", cursor.getString(stringValueIndex));
byte[] value = cursor.getBlob(blobValueIndex);
- MoreAsserts.assertEquals(blob, value);
+ assertTrue(Arrays.equals(blob, value));
assertNull(cursor.getString(nullValueIndex));
cursor.close();
@@ -274,11 +296,11 @@ public void testReplace() {
assertEquals(0, cursor.getInt(booleanValueIndex));
assertEquals(123, cursor.getInt(intValueIndex));
assertEquals(987654L, cursor.getLong(longValueIndex));
- assertEquals(654.321, cursor.getDouble(doubleValueIndex));
- assertEquals(21.1f, cursor.getFloat(floatValueIndex));
+ assertEquals(654.321d, cursor.getDouble(doubleValueIndex), 0.001d);
+ assertEquals(21.1f, cursor.getFloat(floatValueIndex), 0.001d);
assertEquals("replace the row", cursor.getString(stringValueIndex));
value = cursor.getBlob(blobValueIndex);
- MoreAsserts.assertEquals(blob, value);
+ assertArrayEquals(blob, value);
assertNull(cursor.getString(nullValueIndex));
cursor.close();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsTest.java
index 5fc903a..a130cf1 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/DatabaseUtilsTest.java
@@ -17,27 +17,43 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteException;
import android.os.ParcelFileDescriptor;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import net.zetetic.database.DatabaseUtils;
import net.zetetic.database.DatabaseUtils.InsertHelper;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteStatement;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
-public class DatabaseUtilsTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class DatabaseUtilsTest {
private SQLiteDatabase mDatabase;
private File mDatabaseFile;
private static final String[] TEST_PROJECTION = new String[] {
@@ -47,12 +63,13 @@ public class DatabaseUtilsTest extends AndroidTestCase {
"address" // 3
};
private static final String TABLE_NAME = "test";
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
System.loadLibrary("sqlcipher");
- File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE);
+ File dbDir = mContext.getDir("tests", Context.MODE_PRIVATE);
mDatabaseFile = new File(dbDir, "database_test.db");
if (mDatabaseFile.exists()) {
mDatabaseFile.delete();
@@ -67,13 +84,13 @@ protected void setUp() throws Exception {
"CREATE TABLE boolean_test (_id INTEGER PRIMARY KEY, value BOOLEAN)");
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
mDatabaseFile.delete();
- super.tearDown();
}
+ @Test
public void testAppendEscapedSQLString() {
String expected = "name='Mike'";
StringBuilder sb = new StringBuilder("name=");
@@ -86,11 +103,13 @@ public void testAppendEscapedSQLString() {
assertEquals(expected, sb.toString());
}
+ @Test
public void testSqlEscapeString() {
String expected = "'Jack'";
assertEquals(expected, DatabaseUtils.sqlEscapeString("Jack"));
}
+ @Test
public void testAppendValueToSql() {
String expected = "address='LA'";
StringBuilder sb = new StringBuilder("address=");
@@ -108,6 +127,7 @@ public void testAppendValueToSql() {
assertEquals(expected, sb.toString());
}
+ @Test
public void testBindObjectToProgram() {
String name = "Mike";
int age = 21;
@@ -136,6 +156,7 @@ public void testBindObjectToProgram() {
assertEquals(address, cursor.getString(3));
}
+ @Test
public void testCreateDbFromSqlStatements() {
String dbName = "ExampleName";
String sqls = "CREATE TABLE " + TABLE_NAME + " (_id INTEGER PRIMARY KEY, name TEXT);\n"
@@ -143,7 +164,7 @@ public void testCreateDbFromSqlStatements() {
File f = mContext.getDatabasePath(dbName);
f.mkdirs();
f.delete();
- DatabaseUtils.createDbFromSqlStatements(getContext(), f.toString(), 1, sqls);
+ DatabaseUtils.createDbFromSqlStatements(mContext, f.toString(), 1, sqls);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f,null);
final String[] PROJECTION = new String[] {
@@ -155,9 +176,10 @@ public void testCreateDbFromSqlStatements() {
assertEquals(1, cursor.getCount());
cursor.moveToFirst();
assertEquals("Mike", cursor.getString(1));
- getContext().deleteDatabase(dbName);
+ mContext.deleteDatabase(dbName);
}
+ @Test
public void testCursorDoubleToContentValues() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -169,15 +191,16 @@ public void testCursorDoubleToContentValues() {
String key = "key";
cursor.moveToFirst();
DatabaseUtils.cursorDoubleToContentValues(cursor, "age", contentValues, key);
- assertEquals(20.0, contentValues.getAsDouble(key));
+ assertEquals(20.0, contentValues.getAsDouble(key), 0.001d);
DatabaseUtils.cursorDoubleToContentValues(cursor, "Error Field Name", contentValues, key);
assertNull(contentValues.getAsDouble(key));
DatabaseUtils.cursorDoubleToContentValues(cursor, "name", contentValues, key);
- assertEquals(0.0, contentValues.getAsDouble(key));
+ assertEquals(0.0, contentValues.getAsDouble(key), 0.001d);
}
+ @Test
public void testCursorDoubleToCursorValues() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -188,15 +211,16 @@ public void testCursorDoubleToCursorValues() {
ContentValues contentValues = new ContentValues();
cursor.moveToFirst();
DatabaseUtils.cursorDoubleToCursorValues(cursor, "age", contentValues);
- assertEquals(20.0, contentValues.getAsDouble("age"));
+ assertEquals(20.0, contentValues.getAsDouble("age"), 0.001d);
DatabaseUtils.cursorDoubleToCursorValues(cursor, "Error Field Name", contentValues);
assertNull(contentValues.getAsDouble("Error Field Name"));
DatabaseUtils.cursorDoubleToCursorValues(cursor, "name", contentValues);
- assertEquals(0.0, contentValues.getAsDouble("name"));
+ assertEquals(0.0, contentValues.getAsDouble("name"), 0.001d);
}
+ @Test
public void testCursorIntToContentValues() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -226,7 +250,8 @@ public void testCursorIntToContentValues() {
assertEquals(Integer.valueOf(0), contentValues.getAsInteger("name"));
}
- public void testcursorLongToContentValues() {
+ @Test
+ public void testCursorLongToContentValues() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
Cursor cursor = mDatabase.query(TABLE_NAME, TEST_PROJECTION, null, null, null, null, null);
@@ -255,6 +280,7 @@ public void testcursorLongToContentValues() {
assertEquals(Long.valueOf(0), contentValues.getAsLong("name"));
}
+ @Test
public void testCursorRowToContentValues() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -281,16 +307,17 @@ public void testCursorRowToContentValues() {
cursor.moveToNext();
DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
assertFalse(contentValues.getAsBoolean("value"));
- assertTrue(contentValues.getAsInteger("value")==0);
+ assertEquals(0, (int) contentValues.getAsInteger("value"));
cursor.moveToNext();
DatabaseUtils.cursorRowToContentValues(cursor, contentValues);
- assertTrue(contentValues.getAsInteger("value")==1);
+ assertEquals(1, (int) contentValues.getAsInteger("value"));
// The following does not work:
// https://stackoverflow.com/questions/13095277/android-database-with-contentvalue-with-boolean-bug
// assertTrue(contentValues.getAsBoolean("value"));
}
+ @Test
public void testCursorStringToContentValues() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -330,6 +357,8 @@ public void testCursorStringToContentValues() {
assertEquals("Mike", contentValues.get("name"));
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testCursorStringToInsertHelper() {
// create a new table.
mDatabase.execSQL("CREATE TABLE test_copy (_id INTEGER PRIMARY KEY, " +
@@ -361,6 +390,7 @@ public void testCursorStringToInsertHelper() {
assertEquals("LA", cursor.getString(3));
}
+ @Test
public void testDumpCurrentRow() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -386,6 +416,7 @@ public void testDumpCurrentRow() {
assertEquals(expected, DatabaseUtils.dumpCurrentRowToString(cursor));
}
+ @Test
public void testDumpCursor() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -429,6 +460,7 @@ public void testDumpCursor() {
assertEquals(pos, cursor.getPosition()); // dumpCursor should not change status of cursor
}
+ @Test
public void testCollationKey() {
String key1 = DatabaseUtils.getCollationKey("abc");
String key2 = DatabaseUtils.getCollationKey("ABC");
@@ -441,10 +473,11 @@ public void testCollationKey() {
key2 = DatabaseUtils.getHexCollationKey("ABC");
key3 = DatabaseUtils.getHexCollationKey("bcd");
- assertTrue(key1.equals(key2));
- assertFalse(key1.equals(key3));
+ assertEquals(key1, key2);
+ assertNotEquals(key1, key3);
}
+ @Test
public void testLongForQuery() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -484,6 +517,7 @@ public void testLongForQuery() {
statement.close();
}
+ @Test
public void testQueryNumEntries() {
assertEquals(0, DatabaseUtils.queryNumEntries(mDatabase, TABLE_NAME));
@@ -557,6 +591,7 @@ public void testQueryNumEntries() {
// }
// }
+ @Test
public void testStringForQuery() {
mDatabase.execSQL("INSERT INTO " + TABLE_NAME + " (name, age, address)" +
" VALUES ('Mike', '20', 'LA');");
@@ -659,7 +694,7 @@ private static void assertInputStreamContent(byte[] expected, InputStream is)
int count = is.read(observed);
assertEquals(expected.length, count);
assertEquals(-1, is.read());
- MoreAsserts.assertEquals(expected, observed);
+ assertArrayEquals(expected, observed);
} finally {
is.close();
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/MergeCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/MergeCursorTest.java
index 7c43778..4671c7d 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/MergeCursorTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/database_cts/MergeCursorTest.java
@@ -17,6 +17,12 @@
package net.zetetic.database.database_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
@@ -24,12 +30,20 @@
import android.database.MergeCursor;
import android.database.StaleDataException;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
-import android.test.AndroidTestCase;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.File;
import java.util.Arrays;
-public class MergeCursorTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class MergeCursorTest {
private final int NUMBER_1_COLUMN_INDEX = 1;
private static final String TABLE1_NAME = "test1";
private static final String TABLE2_NAME = "test2";
@@ -54,16 +68,15 @@ public class MergeCursorTest extends AndroidTestCase {
private static final int MAX_VALUE = 10;
private static final int HALF_VALUE = MAX_VALUE / 2;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
setupDatabase();
mCursors = new Cursor[2];
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
for (int i = 0; i < mCursors.length; i++) {
if (null != mCursors[i]) {
mCursors[i].close();
@@ -71,9 +84,9 @@ protected void tearDown() throws Exception {
}
mDatabase.close();
mDatabaseFile.delete();
- super.tearDown();
}
+ @Test
public void testConstructor() {
// If each item of mCursors are null, count will be zero.
MergeCursor mergeCursor = new MergeCursor(mCursors);
@@ -86,6 +99,7 @@ public void testConstructor() {
assertEquals(mCursors[0].getCount() + mCursors[1].getCount(), mergeCursor.getCount());
}
+ @Test
public void testOnMove() {
createCursors();
MergeCursor mergeCursor = new MergeCursor(mCursors);
@@ -97,6 +111,7 @@ public void testOnMove() {
}
}
+ @Test
public void testCursorSwiching() {
mDatabase.execSQL("CREATE TABLE " + TABLE5_NAME + " (_id INTEGER PRIMARY KEY,"
+ TABLE3_COLUMNS + ");");
@@ -124,6 +139,7 @@ public void testCursorSwiching() {
assertTrue(Arrays.equals(tableColumns, mergeCursor.getColumnNames()));
}
+ @Test
public void testGetValues() {
byte NUMBER_BLOB_UNIT = 99;
String[] TEST_STRING = new String[] {"Test String1", "Test String2"};
@@ -183,6 +199,7 @@ public void testGetValues() {
}
}
+ @Test
public void testContentObsererOperations() throws IllegalStateException {
createCursors();
MergeCursor mergeCursor = new MergeCursor(mCursors);
@@ -220,6 +237,7 @@ public void testContentObsererOperations() throws IllegalStateException {
}
}
+ @Test
public void testDeactivate() throws IllegalStateException {
createCursors();
MergeCursor mergeCursor = new MergeCursor(mCursors);
@@ -281,6 +299,7 @@ public void testDeactivate() throws IllegalStateException {
}
}
+ @Test
public void testRequery() {
final String TEST_VALUE1 = Integer.toString(MAX_VALUE + 1);
final String TEST_VALUE2 = Integer.toString(MAX_VALUE + 2);
@@ -325,7 +344,7 @@ private void buildDatabaseWithTestValues(String text, double doubleNumber, long
}
private void setupDatabase() {
- File dbDir = getContext().getDir("tests", Context.MODE_PRIVATE);
+ File dbDir = ApplicationProvider.getApplicationContext().getDir("tests", Context.MODE_PRIVATE);
mDatabaseFile = new File(dbDir, "database_test.db");
if (mDatabaseFile.exists()) {
mDatabaseFile.delete();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java
index b043b35..12cd90e 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_android/DatabaseGeneralTest.java
@@ -17,6 +17,16 @@
package net.zetetic.database.sqlcipher_android;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.ContentValues;
import android.content.Context;
import android.database.CharArrayBuffer;
@@ -24,27 +34,34 @@
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteException;
import android.os.Parcel;
-import android.test.AndroidTestCase;
-import android.test.PerformanceTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.suitebuilder.annotation.Suppress;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.Suppress;
+
import android.util.Log;
import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteStatement;
import net.zetetic.database.DefaultDatabaseErrorHandler;
import junit.framework.Assert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
-@SuppressWarnings({"deprecated", "ResultOfMethodCallIgnored"})
-public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceTestCase {
+@RunWith(AndroidJUnit4.class)
+public class DatabaseGeneralTest {
private static final String TAG = "DatabaseGeneralTest";
private static final String sString1 = "this is a test";
@@ -56,36 +73,29 @@ public class DatabaseGeneralTest extends AndroidTestCase implements PerformanceT
private SQLiteDatabase mDatabase;
private File mDatabaseFile;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
- File dbDir = getContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
+ File dbDir = ApplicationProvider.getApplicationContext().getDir(this.getClass().getName(), Context.MODE_PRIVATE);
mDatabaseFile = new File(dbDir, "database_test.db");
if (mDatabaseFile.exists()) {
mDatabaseFile.delete();
}
mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
- assertNotNull(mDatabase);
+ assertThat(mDatabase, is(notNullValue()));
mDatabase.setVersion(CURRENT_DATABASE_VERSION);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
mDatabaseFile.delete();
- super.tearDown();
}
public boolean isPerformanceOnly() {
return false;
}
- // These test can only be run once.
- public int startPerformance(Intermediates intermediates) {
- return 1;
- }
-
private void populateDefaultTable() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
@@ -94,7 +104,7 @@ private void populateDefaultTable() {
mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
}
- @MediumTest
+ @Test
public void testCustomFunctionNoReturn() {
mDatabase.addCustomFunction("emptyFunction", 1, new SQLiteDatabase.CustomFunction() {
@Override
@@ -108,14 +118,14 @@ public void callback(String[] args) {
assertSame(null, cursor.getString(0));
}
- @MediumTest
+ @Test
public void testVersion() throws Exception {
assertEquals(CURRENT_DATABASE_VERSION, mDatabase.getVersion());
mDatabase.setVersion(11);
assertEquals(11, mDatabase.getVersion());
}
- @MediumTest
+ @Test
public void testUpdate() throws Exception {
populateDefaultTable();
@@ -131,7 +141,7 @@ public void testUpdate() throws Exception {
}
@Suppress // PHONE_NUMBERS_EQUAL not supported
- @MediumTest
+ @Test
public void testPhoneNumbersEqual() throws Exception {
mDatabase.execSQL("CREATE TABLE phones (num TEXT);");
mDatabase.execSQL("INSERT INTO phones (num) VALUES ('911');");
@@ -281,7 +291,7 @@ private void assertPhoneNumberNotEqual(String phone1, String phone2, boolean use
* @throws Exception
*/
@Suppress // PHONE_NUMBERS_EQUAL not supported
- @SmallTest
+ @Test
public void testPhoneNumbersEqualInternationl() throws Exception {
assertPhoneNumberEqual("1", "1");
assertPhoneNumberEqual("123123", "123123");
@@ -337,7 +347,7 @@ public void testPhoneNumbersEqualInternationl() throws Exception {
assertPhoneNumberNotEqual("080-1234-5678", "+819012345678", true);
}
- @MediumTest
+ @Test
public void testCopyString() throws Exception {
mDatabase.execSQL("CREATE TABLE guess (numi INTEGER, numf FLOAT, str TEXT);");
mDatabase.execSQL(
@@ -378,8 +388,8 @@ public void testCopyString() throws Exception {
c.moveToNext();
c.copyStringToBuffer(numfIdx, buf);
- assertEquals(-1.0, Double.valueOf(
- new String(buf.data, 0, buf.sizeCopied)));
+ var value = java.lang.Double.valueOf(new String(buf.data, 0, buf.sizeCopied)).doubleValue();
+ assertEquals(-1.0d, value, 0.001);
c.copyStringToBuffer(strIdx, buf);
compareTo = c.getString(strIdx);
@@ -389,7 +399,7 @@ public void testCopyString() throws Exception {
c.close();
}
- @MediumTest
+ @Test
public void testSchemaChange1() throws Exception {
SQLiteDatabase db1 = mDatabase;
Cursor cursor;
@@ -405,7 +415,7 @@ public void testSchemaChange1() throws Exception {
cursor.close();
}
- @MediumTest
+ @Test
public void testSchemaChange2() throws Exception {
mDatabase.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
Cursor cursor = mDatabase.query("db1", null, null, null, null, null, null);
@@ -414,7 +424,7 @@ public void testSchemaChange2() throws Exception {
cursor.close();
}
- @MediumTest
+ @Test
public void testSchemaChange3() throws Exception {
mDatabase.execSQL("CREATE TABLE db1 (_id INTEGER PRIMARY KEY, data TEXT);");
mDatabase.execSQL("INSERT INTO db1 (data) VALUES ('test');");
@@ -431,7 +441,7 @@ public void testSchemaChange3() throws Exception {
}
}
- @MediumTest
+ @Test
public void testSelectionArgs() throws Exception {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
ContentValues values = new ContentValues(1);
@@ -449,7 +459,7 @@ public void testSelectionArgs() throws Exception {
}
@Suppress // unicode collator not supported yet
- @MediumTest
+ @Test
public void testTokenize() throws Exception {
Cursor c;
mDatabase.execSQL("CREATE TABLE tokens (" +
@@ -621,7 +631,7 @@ public void testTokenize() throws Exception {
"SELECT source from tokens_no_index where token GLOB '" + key + "*'", null));
}
- @MediumTest
+ @Test
public void testTransactions() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
mDatabase.execSQL("INSERT INTO test (num) VALUES (0)");
@@ -724,7 +734,7 @@ private void assertThrowsIllegalState(Runnable r) {
Assert.assertTrue(ok);
}
- @MediumTest
+ @Test
public void testContentValues() throws Exception {
ContentValues values = new ContentValues();
values.put("string", "value");
@@ -748,7 +758,7 @@ public void testContentValues() throws Exception {
public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
- @MediumTest
+ @Test
public void testTableInfoPragma() throws Exception {
mDatabase.execSQL("CREATE TABLE pragma_test (" +
"i INTEGER DEFAULT 1234, " +
@@ -797,7 +807,7 @@ public void testTableInfoPragma() throws Exception {
}
}
- @MediumTest
+ @Test
public void testSemicolonsInStatements() throws Exception {
mDatabase.execSQL("CREATE TABLE pragma_test (" +
"i INTEGER DEFAULT 1234, " +
@@ -817,7 +827,7 @@ public void testSemicolonsInStatements() throws Exception {
}
}
- @MediumTest
+ @Test
public void testUnionsWithBindArgs() {
/* make sure unions with bindargs work http://b/issue?id=1061291 */
mDatabase.execSQL("CREATE TABLE A (i int);");
@@ -850,7 +860,7 @@ public void testUnionsWithBindArgs() {
* It finishes without failure when it is not available.
*/
@Suppress
- @MediumTest
+ @Test
public void testCollateLocalizedForJapanese() throws Exception {
final String testName = "DatabaseGeneralTest#testCollateLocalizedForJapanese()";
final Locale[] localeArray = Locale.getAvailableLocales();
@@ -933,7 +943,7 @@ public void testCollateLocalizedForJapanese() throws Exception {
}
}
- @SmallTest
+ @Test
public void testSetMaxCacheSize() {
mDatabase.execSQL("CREATE TABLE test (i int, j int);");
mDatabase.execSQL("insert into test values(1,1);");
@@ -950,6 +960,7 @@ public void testSetMaxCacheSize() {
}
@LargeTest
+ @Test
public void testDefaultDatabaseErrorHandler() {
DefaultDatabaseErrorHandler errorHandler = new DefaultDatabaseErrorHandler();
@@ -960,7 +971,7 @@ public void testDefaultDatabaseErrorHandler() {
assertFalse(mDatabase.isOpen());
assertTrue(dbfile.exists());
try {
- errorHandler.onCorruption(mDatabase);
+ errorHandler.onCorruption(mDatabase, new SQLiteException());
if(SQLiteDatabase.hasCodec()){
assertTrue(dbfile.exists());
} else {
@@ -976,7 +987,7 @@ public void testDefaultDatabaseErrorHandler() {
memoryDb.close();
assertFalse(memoryDb.isOpen());
try {
- errorHandler.onCorruption(memoryDb);
+ errorHandler.onCorruption(memoryDb, new SQLiteException());
} catch (Exception e) {
fail("unexpected");
}
@@ -987,7 +998,7 @@ public void testDefaultDatabaseErrorHandler() {
assertNotNull(dbObj);
assertTrue(dbObj.isOpen());
try {
- errorHandler.onCorruption(dbObj);
+ errorHandler.onCorruption(dbObj, new SQLiteException());
if(SQLiteDatabase.hasCodec()){
assertTrue(dbfile.exists());
} else{
@@ -1012,7 +1023,7 @@ public void testDefaultDatabaseErrorHandler() {
assertTrue(dbObj.isOpen());
List> attachedDbs = dbObj.getAttachedDbs();
try {
- errorHandler.onCorruption(dbObj);
+ errorHandler.onCorruption(dbObj, new SQLiteException());
if(SQLiteDatabase.hasCodec()){
assertTrue(dbfile.exists());
} else {
@@ -1047,7 +1058,7 @@ public void testDefaultDatabaseErrorHandler() {
assertTrue(dbObj.isOpen());
attachedDbs = dbObj.getAttachedDbs();
try {
- errorHandler.onCorruption(dbObj);
+ errorHandler.onCorruption(dbObj, new SQLiteException());
if(SQLiteDatabase.hasCodec()){
assertTrue(dbfile.exists());
} else {
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/AndroidSQLCipherTestCase.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/AndroidSQLCipherTestCase.java
index 776d92a..07bc2ef 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/AndroidSQLCipherTestCase.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/AndroidSQLCipherTestCase.java
@@ -1,6 +1,7 @@
package net.zetetic.database.sqlcipher_cts;
import android.content.Context;
+import android.icu.text.NumberFormat;
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -19,6 +20,9 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.Locale;
@RunWith(AndroidJUnit4.class)
@@ -105,4 +109,205 @@ protected void log(String message, Object... args) {
protected void loge(Exception ex, String message, Object... args) {
Log.e(TAG, String.format(Locale.getDefault(), message, args), ex);
}
+
+ public interface RowColumnValueBuilder {
+ Object buildRowColumnValue(String[] columns, int row, int column);
+ }
+
+ protected void buildDatabase(
+ SQLiteDatabase database,
+ int rows,
+ int columns,
+ RowColumnValueBuilder builder) {
+ var columnNames = new ArrayList();
+ Log.i(TAG, String.format("Building database with %s rows, %d columns",
+ NumberFormat.getInstance().format(rows), columns));
+ var createTemplate = "CREATE TABLE t1(%s);";
+ var insertTemplate = "INSERT INTO t1 VALUES(%s);";
+ var createBuilder = new StringBuilder();
+ var insertBuilder = new StringBuilder();
+ for (int column = 0; column < columns; column++) {
+ var columnName = generateColumnName(columnNames, column);
+ createBuilder.append(String.format("%s BLOB%s",
+ columnName,
+ column != columns - 1 ? "," : ""));
+ insertBuilder.append(String.format("?%s", column != columns - 1 ? "," : ""));
+ }
+ var create = String.format(createTemplate, createBuilder.toString());
+ var insert = String.format(insertTemplate, insertBuilder.toString());
+ database.execSQL("DROP TABLE IF EXISTS t1;");
+ database.execSQL(create);
+ database.execSQL("BEGIN;");
+ var names = columnNames.toArray(new String[0]);
+ for (int row = 0; row < rows; row++) {
+ var insertArgs = new Object[columns];
+ for (var column = 0; column < columns; column++) {
+ insertArgs[column] = builder.buildRowColumnValue(names, row, column);
+ }
+ database.execSQL(insert, insertArgs);
+ }
+ database.execSQL("COMMIT;");
+ Log.i(TAG, String.format("Database built with %d columns, %d rows", columns, rows));
+ }
+
+ protected Integer[] generateRandomNumbers(
+ int max,
+ int times){
+ var random = new SecureRandom();
+ var numbers = new ArrayList<>();
+ for(var index = 0; index < times; index++){
+ boolean alreadyExists;
+ do {
+ var value = random.nextInt(max);
+ alreadyExists = numbers.contains(value);
+ if(!alreadyExists){
+ numbers.add(value);
+ }
+ } while(alreadyExists);
+ }
+ return numbers.toArray(new Integer[0]);
+ }
+
+ protected List ReservedWords = Arrays.asList(
+ "ABORT",
+ "ACTION",
+ "ADD",
+ "AFTER",
+ "ALL",
+ "ALTER",
+ "ANALYZE",
+ "AND",
+ "AS",
+ "ASC",
+ "ATTACH",
+ "AUTOINCREMENT",
+ "BEFORE",
+ "BEGIN",
+ "BETWEEN",
+ "BY",
+ "CASCADE",
+ "CASE",
+ "CAST",
+ "CHECK",
+ "COLLATE",
+ "COLUMN",
+ "COMMIT",
+ "CONFLICT",
+ "CONSTRAINT",
+ "CREATE",
+ "CROSS",
+ "CURRENT_DATE",
+ "CURRENT_TIME",
+ "CURRENT_TIMESTAMP",
+ "DATABASE",
+ "DEFAULT",
+ "DEFERRABLE",
+ "DEFERRED",
+ "DELETE",
+ "DESC",
+ "DETACH",
+ "DISTINCT",
+ "DROP",
+ "EACH",
+ "ELSE",
+ "END",
+ "ESCAPE",
+ "EXCEPT",
+ "EXCLUSIVE",
+ "EXISTS",
+ "EXPLAIN",
+ "FAIL",
+ "FOR",
+ "FOREIGN",
+ "FROM",
+ "FULL",
+ "GLOB",
+ "GROUP",
+ "HAVING",
+ "IF",
+ "IGNORE",
+ "IMMEDIATE",
+ "IN",
+ "INDEX",
+ "INDEXED",
+ "INITIALLY",
+ "INNER",
+ "INSERT",
+ "INSTEAD",
+ "INTERSECT",
+ "INTO",
+ "IS",
+ "ISNULL",
+ "JOIN",
+ "KEY",
+ "LEFT",
+ "LIKE",
+ "LIMIT",
+ "MATCH",
+ "NATURAL",
+ "NO",
+ "NOT",
+ "NOTNULL",
+ "NULL",
+ "OF",
+ "OFFSET",
+ "ON",
+ "OR",
+ "ORDER",
+ "OUTER",
+ "PLAN",
+ "PRAGMA",
+ "PRIMARY",
+ "QUERY",
+ "RAISE",
+ "RECURSIVE",
+ "REFERENCES",
+ "REGEXP",
+ "REINDEX",
+ "RELEASE",
+ "RENAME",
+ "REPLACE",
+ "RESTRICT",
+ "RIGHT",
+ "ROLLBACK",
+ "ROW",
+ "SAVEPOINT",
+ "SELECT",
+ "SET",
+ "TABLE",
+ "TEMP",
+ "TEMPORARY",
+ "THEN",
+ "TO",
+ "TRANSACTION",
+ "TRIGGER",
+ "UNION",
+ "UNIQUE",
+ "UPDATE",
+ "USING",
+ "VACUUM",
+ "VALUES",
+ "VIEW",
+ "VIRTUAL",
+ "WHEN",
+ "WHERE",
+ "WITH",
+ "WITHOUT");
+
+ private String generateColumnName(
+ List columnNames,
+ int columnIndex){
+ var random = new SecureRandom();
+ var labels = "abcdefghijklmnopqrstuvwxyz";
+ var element = columnIndex < labels.length()
+ ? String.valueOf(labels.charAt(columnIndex))
+ : String.valueOf(labels.charAt(random.nextInt(labels.length() - 1)));
+ while(columnNames.contains(element) || ReservedWords.contains(element.toUpperCase())){
+ element += labels.charAt(random.nextInt(labels.length() - 1));
+ }
+ columnNames.add(element);
+ Log.i(TAG, String.format("Generated column name:%s for index:%d", element, columnIndex));
+ return element;
+ }
+
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/DatabaseStatementTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/DatabaseStatementTest.java
index 2358965..29e465b 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/DatabaseStatementTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/DatabaseStatementTest.java
@@ -16,16 +16,26 @@
package net.zetetic.database.sqlcipher_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDoneException;
-import android.test.AndroidTestCase;
-import android.test.PerformanceTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteStatement;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
/*
@@ -34,7 +44,8 @@
* Modifications:
* - use Context to create and delete the DB to avoid hard-coded paths
*/
-public class DatabaseStatementTest extends AndroidTestCase implements PerformanceTestCase {
+@RunWith(AndroidJUnit4.class)
+public class DatabaseStatementTest {
private static final String sString1 = "this is a test";
private static final String sString2 = "and yet another test";
@@ -45,11 +56,10 @@ public class DatabaseStatementTest extends AndroidTestCase implements Performanc
private static final int CURRENT_DATABASE_VERSION = 42;
private SQLiteDatabase mDatabase;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
- File f = mContext.getDatabasePath(DATABASE_NAME);
+ File f = ApplicationProvider.getApplicationContext().getDatabasePath(DATABASE_NAME);
f.mkdirs();
if (f.exists()) { f.delete(); }
mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
@@ -57,22 +67,16 @@ protected void setUp() throws Exception {
mDatabase.setVersion(CURRENT_DATABASE_VERSION);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
- getContext().deleteDatabase(DATABASE_NAME);
- super.tearDown();
+ ApplicationProvider.getApplicationContext().deleteDatabase(DATABASE_NAME);
}
public boolean isPerformanceOnly() {
return false;
}
- // These test can only be run once.
- public int startPerformance(Intermediates intermediates) {
- return 1;
- }
-
private void populateDefaultTable() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
@@ -81,7 +85,8 @@ private void populateDefaultTable() {
mDatabase.execSQL("INSERT INTO test (data) VALUES ('" + sString3 + "');");
}
- @MediumTest
+ @Test
+ @SuppressWarnings("deprecation")
public void testExecuteStatement() throws Exception {
populateDefaultTable();
SQLiteStatement statement = mDatabase.compileStatement("DELETE FROM test");
@@ -93,7 +98,7 @@ public void testExecuteStatement() throws Exception {
statement.close();
}
- @MediumTest
+ @Test
public void testSimpleQuery() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
@@ -130,7 +135,7 @@ public void testSimpleQuery() throws Exception {
statement2.close();
}
- @MediumTest
+ @Test
public void testStatementLongBinding() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
@@ -152,7 +157,7 @@ public void testStatementLongBinding() throws Exception {
c.close();
}
- @MediumTest
+ @Test
public void testStatementStringBinding() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num TEXT);");
SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
@@ -174,7 +179,7 @@ public void testStatementStringBinding() throws Exception {
c.close();
}
- @MediumTest
+ @Test
public void testStatementClearBindings() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
@@ -196,7 +201,7 @@ public void testStatementClearBindings() throws Exception {
c.close();
}
- @MediumTest
+ @Test
public void testSimpleStringBinding() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num TEXT, value TEXT);");
String statement = "INSERT INTO test (num, value) VALUES (?,?)";
@@ -220,7 +225,7 @@ public void testSimpleStringBinding() throws Exception {
c.close();
}
- @MediumTest
+ @Test
public void testStatementMultipleBindings() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);");
SQLiteStatement statement =
@@ -283,7 +288,7 @@ public void run() {
}
}
- @MediumTest
+ @Test
public void testStatementMultiThreaded() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER, str TEXT);");
SQLiteStatement statement =
@@ -298,7 +303,7 @@ public void testStatementMultiThreaded() throws Exception {
}
}
- @MediumTest
+ @Test
public void testStatementConstraint() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL);");
SQLiteStatement statement = mDatabase.compileStatement("INSERT INTO test (num) VALUES (?)");
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/RoomUpsertTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/RoomUpsertTest.java
index e495521..a2d6fca 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/RoomUpsertTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/RoomUpsertTest.java
@@ -72,7 +72,7 @@ public static abstract class UserDao {
abstract User[] findById(long id);
}
- @Database(entities = {User.class}, version = 1)
+ @Database(entities = {User.class}, version = 1, exportSchema = false)
public static abstract class UserDatabase extends RoomDatabase {
abstract UserDao userDao();
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java
index 4f00506..0e59dfb 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherDatabaseTest.java
@@ -2,14 +2,13 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.fail;
import android.content.ContentValues;
import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteException;
+import android.util.Log;
import net.zetetic.database.sqlcipher.SQLiteCursor;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
@@ -19,8 +18,15 @@
import org.junit.Test;
import java.io.File;
+import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Random;
+import java.util.UUID;
public class SQLCipherDatabaseTest extends AndroidSQLCipherTestCase {
@@ -477,31 +483,211 @@ public void shouldRetrieveLargeSingleRowResultFromCursor(){
}
@Test
- public void shouldAllowCursorWindowToResize(){
+ public void shouldAllowCursorWindowToResizeWithNonDefaultAllocationSize(){
try {
Cursor cursor;
int id = 1, extra = 1024, size = 256;
- byte[] tooLargeQueriedData = null;
SQLiteCursor.setCursorWindowSize(size);
byte[] tooLargeData = generateRandomBytes(size + extra);
database.execSQL("create table t1(a,b);");
database.execSQL("insert into t1(a,b) values(?,?);", new Object[]{id, tooLargeData});
- try {
- cursor = database.rawQuery("select b from t1 where a = ?;", new Object[]{id});
- if(cursor != null && cursor.moveToFirst()) {
- fail("CursorWindow should be too small to fill query results");
- }
- } catch (Exception ex){
- SQLiteCursor.setCursorWindowSize(size + extra);
- cursor = database.rawQuery("select b from t1 where a = ?;", new Object[]{id});
- if(cursor != null && cursor.moveToFirst()) {
- tooLargeQueriedData = cursor.getBlob(0);
- cursor.close();
+ cursor = database.rawQuery("select b from t1 where a = ?;", id);
+ if (cursor != null && cursor.moveToFirst()) {
+ byte[] value = cursor.getBlob(0);
+ assertThat(Arrays.equals(value, tooLargeData), is(true));
+ } else {
+ fail("CursorWindow should resize automatically to fill query results");
+ }
+ } finally {
+ SQLiteCursor.resetCursorWindowSize();
+ }
+ }
+
+ @Test
+ public void shouldTestSmallResizeOfAllocationForCursor(){
+ try {
+ Cursor cursor;
+ int id = 1, size = 1024 * 3000;
+ SQLiteCursor.resetCursorWindowSize();
+ byte[] tooLargeData = generateRandomBytes(size);
+ database.execSQL("create table t1(a,b);");
+ database.execSQL("insert into t1(a,b) values(?,?);", new Object[]{id, tooLargeData});
+ long start = System.nanoTime();
+ cursor = database.rawQuery("select b from t1 where a = ?;", id);
+ if (cursor != null && cursor.moveToFirst()) {
+ byte[] value = cursor.getBlob(0);
+ assertThat(Arrays.equals(value, tooLargeData), is(true));
+ } else {
+ fail("CursorWindow should resize automatically to fill query results");
+ }
+ long stop = System.nanoTime();
+ Log.i(TAG, String.format("Query completed in %d ms", (stop - start)/1000000));
+ } finally {
+ SQLiteCursor.resetCursorWindowSize();
+ }
+ }
+
+ @Test
+ public void shouldAllowCursorWindowToResizeWithDefaultAllocationSize(){
+ try {
+ Cursor cursor;
+ int dataRows = 10, size = 2048 * 1024;
+ SQLiteCursor.resetCursorWindowSize();
+ HashMap data = new HashMap<>();
+ for (int row = 0; row < dataRows; row++) {
+ data.put(UUID.randomUUID().toString(), generateRandomBytes(size));
+ }
+ database.execSQL("create table t1(a,b);");
+ for (String key : data.keySet()) {
+ database.execSQL("insert into t1(a,b) values(?,?);", new Object[]{key, data.get(key)});
+ }
+ long start = System.nanoTime();
+ cursor = database.rawQuery("select * from t1;");
+ if(cursor != null){
+ while(cursor.moveToNext()){
+ String key = cursor.getString(0);
+ byte[] value = cursor.getBlob(1);
+ assertThat(Arrays.equals(data.get(key), value), is(true));
}
+ long stop = System.nanoTime();
+ Log.i(TAG, String.format("Query completed in %d ms", (stop - start)/1000000));
+ cursor.close();
+ } else {
+ fail("Unable to retrieve data from database");
}
- assertThat(Arrays.equals(tooLargeQueriedData, tooLargeData), is(true));
} finally {
SQLiteCursor.resetCursorWindowSize();
}
}
+
+ @Test
+ public void shouldSupportDeleteWithNullWhereArgs(){
+ long rowsFound = -1L;
+ database.execSQL("create table t1(a,b);");
+ Object[] whereArgs = null;
+ database.execSQL("insert into t1(a,b) values(?,?)", new Object[]{1, 2});
+ database.execSQL("insert into t1(a,b) values(?,?)", new Object[]{3, 4});
+ long rowsDeleted = database.delete("t1", "a in (1, 3)", whereArgs);
+ assertThat(rowsDeleted, is(2L));
+ Cursor cursor = database.rawQuery("select count(*) from t1;", null);
+ if(cursor != null && cursor.moveToNext()){
+ rowsFound = cursor.getLong(0);
+ cursor.close();
+ }
+ assertThat(rowsFound, is(0L));
+ }
+
+
+ // This test recreated a scenario where the CursorWindow::allocRow
+ // would alloc a RowSlot*, then the alloc call to allocate the
+ // fieldDirOffset (based on fieldDirSize) would cause mData
+ // to move (when there was just enough space in mData for a RowSlot*,
+ // but not enough for the corresponding FieldSlot*), invalidating the
+ // previous rowSlot address. This has been addressed by reassigning the
+ // rowSlot pointer after alloc, prior to binding the fieldDirOffset
+ // and should not fail now.
+ /** @noinspection StatementWithEmptyBody*/
+ @Test
+ public void shouldNotCauseRowSlotAllocationCrash(){
+ SQLiteCursor.resetCursorWindowSize();
+ database.execSQL("create table t1(a INTEGER, b INTEGER, c TEXT, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, "+
+ "aa, bb, cc, dd, ee, ff, gg, hh, ii, jj, kk, ll, mm, nn, oo, pp, qq, rr, ss, tt, uu, vv);");
+ database.beginTransaction();
+ Random random = new Random();
+ for(int i = 0; i < 20; i++) {
+ int size = 1024*3 + 450;
+ database.execSQL("insert into t1(a, b, c) values(?, ?, randomblob(?));", new Object[]{i, size, size});
+ }
+ database.setTransactionSuccessful();
+ var value = false;
+ var cursor = database.rawQuery("select * from t1;");
+ if(cursor != null){
+ while(cursor.moveToNext()){}
+ value = true;
+ }
+ assertThat(value, is(true));
+ }
+
+ @Test
+ public void shouldBuildLargeDatabaseWithCustomCursorSizeAndNavigateValuesWithDigest() throws UnsupportedEncodingException, NoSuchAlgorithmException {
+ int rowCount = 1000;
+ int windowAllocationSize = 1024 * 1024 / 20;
+ buildDatabase(database, rowCount, 30, (columns, row, column) -> {
+ try {
+ var digest = MessageDigest.getInstance("SHA-1");
+ var columnName = columns[column];
+ var value = String.format("%s%d", columnName, row);
+ return digest.digest(value.getBytes(StandardCharsets.UTF_8));
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ return null;
+ }
+ });
+
+ var randomRows = generateRandomNumbers(rowCount, rowCount);
+ SQLiteCursor.setCursorWindowSize(windowAllocationSize);
+ var cursor = database.rawQuery("SELECT * FROM t1;", null);
+ var digest = MessageDigest.getInstance("SHA-1");
+ int row = 0;
+ Log.i(TAG, "Walking cursor forward");
+ while(cursor.moveToNext()){
+ var compare = compareDigestForAllColumns(cursor, digest, row);
+ assertThat(compare, is(true));
+ row++;
+ }
+ Log.i(TAG, "Walking cursor backward");
+ while(cursor.moveToPrevious()){
+ row--;
+ var compare = compareDigestForAllColumns(cursor, digest, row);
+ assertThat(compare, is(true));
+ }
+ Log.i(TAG, "Walking cursor randomly");
+ for(int randomRow : randomRows){
+ cursor.moveToPosition(randomRow);
+ var compare = compareDigestForAllColumns(cursor, digest, randomRow);
+ assertThat(compare, is(true));
+ }
+ }
+
+ @Test
+ public void shouldCheckAllTypesFromCursor(){
+ database.execSQL("drop table if exists t1;");
+ database.execSQL("create table t1(a text, b integer, c text, d real, e blob)");
+ byte[] data = new byte[10];
+ new SecureRandom().nextBytes(data);
+ database.execSQL("insert into t1(a, b, c, d, e) values(?, ?, ?, ?, ?)", new Object[]{"test1", 100, null, 3.25, data});
+ Cursor results = database.rawQuery("select * from t1", new String[]{});
+ results.moveToFirst();
+ int type_a = results.getType(0);
+ int type_b = results.getType(1);
+ int type_c = results.getType(2);
+ int type_d = results.getType(3);
+ int type_e = results.getType(4);
+ results.close();
+ assertThat(type_a, is(Cursor.FIELD_TYPE_STRING));
+ assertThat(type_b, is(Cursor.FIELD_TYPE_INTEGER));
+ assertThat(type_c, is(Cursor.FIELD_TYPE_NULL));
+ assertThat(type_d, is(Cursor.FIELD_TYPE_FLOAT));
+ assertThat(type_e, is(Cursor.FIELD_TYPE_BLOB));
+ }
+
+ private boolean compareDigestForAllColumns(
+ Cursor cursor,
+ MessageDigest digest,
+ int row) throws UnsupportedEncodingException {
+ var columnCount = cursor.getColumnCount();
+ for(var column = 0; column < columnCount; column++){
+ Log.i(TAG, String.format("Comparing SHA-1 digest for row:%d", row));
+ var columnName = cursor.getColumnName(column);
+ var actual = cursor.getBlob(column);
+ var value = String.format("%s%d", columnName, row);
+ var expected = digest.digest(value.getBytes(StandardCharsets.UTF_8));
+ if(!Arrays.equals(actual, expected)){
+ Log.e(TAG, String.format("SHA-1 digest mismatch for row:%d column:%d", row, column));
+ return false;
+ }
+ }
+ return true;
+ }
+
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherOpenHelperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherOpenHelperTest.java
index e03377a..9f47658 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherOpenHelperTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherOpenHelperTest.java
@@ -72,7 +72,7 @@ private class SqlCipherOpenHelper extends SQLiteOpenHelper {
private final String TAG = getClass().getSimpleName();
public SqlCipherOpenHelper(Context context) {
- super(context, "test.db", "test", null, 1, 1, sqLiteDatabase -> Log.e(SQLCipherOpenHelperTest.this.TAG, "onCorruption()"), new SQLiteDatabaseHook() {
+ super(context, "test.db", "test", null, 1, 1, (sqLiteDatabase, ex) -> Log.e(SQLCipherOpenHelperTest.this.TAG, "onCorruption()"), new SQLiteDatabaseHook() {
@Override
public void preKey(SQLiteConnection sqLiteConnection) {
Log.d(SQLCipherOpenHelperTest.this.TAG, "preKey()");
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java
index 2afa9f5..1ed9111 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLCipherVersionTest.java
@@ -17,6 +17,6 @@ public void shouldExtractLibraryCipherVersion() {
cipherVersion = cursor.getString(0);
cursor.close();
}
- assertThat(cipherVersion, containsString("4.6.0"));
+ assertThat(cipherVersion, containsString("4.6.1"));
}
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteAbortExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteAbortExceptionTest.java
index b92798e..51c1873 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteAbortExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteAbortExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteAbortException;
-import android.test.AndroidTestCase;
-public class SQLiteAbortExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteAbortExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteAbortException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteClosableTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteClosableTest.java
index b63795b..e1d5e14 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteClosableTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteClosableTest.java
@@ -16,10 +16,20 @@
package net.zetetic.database.sqlcipher_cts;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import net.zetetic.database.sqlcipher.SQLiteClosable;
-import android.test.AndroidTestCase;
-public class SQLiteClosableTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SuppressWarnings("deprecation")
+public class SQLiteClosableTest {
private class MockSQLiteClosable extends SQLiteClosable {
private boolean mOnAllReferencesReleasedCalled = false;
private boolean mOnAllReferencesReleasedFromContainerCalled = false;
@@ -42,6 +52,7 @@ public boolean isOnAllReferencesReleasedFromContainerCalled() {
}
}
+ @Test
public void testAcquireReference() {
MockSQLiteClosable closable = new MockSQLiteClosable();
@@ -60,6 +71,8 @@ public void testAcquireReference() {
}
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testReleaseReferenceFromContainer() {
MockSQLiteClosable closable = new MockSQLiteClosable();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteConstraintExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteConstraintExceptionTest.java
index 81e6300..261f865 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteConstraintExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteConstraintExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteConstraintException;
-import android.test.AndroidTestCase;
-public class SQLiteConstraintExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteConstraintExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteConstraintException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteCursorTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteCursorTest.java
index 919e558..1fc54cf 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteCursorTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteCursorTest.java
@@ -17,24 +17,39 @@
package net.zetetic.database.sqlcipher_cts;
-import android.database.AbstractCursor;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
import android.database.Cursor;
-import android.database.CursorWindow;
import android.database.DataSetObserver;
import android.database.StaleDataException;
-import android.test.AndroidTestCase;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import net.zetetic.database.AbstractCursor;
+import net.zetetic.database.CursorWindow;
import net.zetetic.database.sqlcipher.SQLiteCursor;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteDirectCursorDriver;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.util.Arrays;
/**
* Test {@link AbstractCursor}.
*/
-public class SQLiteCursorTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteCursorTest {
private SQLiteDatabase mDatabase;
private static final String[] COLUMNS = new String[] { "_id", "number_1", "number_2" };
private static final String TABLE_NAME = "test";
@@ -43,11 +58,12 @@ public class SQLiteCursorTest extends AndroidTestCase {
private static final int TEST_COUNT = 10;
private static final String TEST_SQL = "SELECT * FROM test ORDER BY number_1";
private static final String DATABASE_FILE = "database_test.db";
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
+ mContext = ApplicationProvider.getApplicationContext();
File f = mContext.getDatabasePath(DATABASE_FILE);
f.mkdirs();
if (f.exists()) { f.delete(); }
@@ -56,13 +72,14 @@ protected void setUp() throws Exception {
addValuesIntoTable(TABLE_NAME, DEFAULT_TABLE_VALUE_BEGINS, TEST_COUNT);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
- getContext().deleteDatabase(DATABASE_FILE);
- super.tearDown();
+ mContext.deleteDatabase(DATABASE_FILE);
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testConstructor() {
SQLiteDirectCursorDriver cursorDriver = new SQLiteDirectCursorDriver(mDatabase,
TEST_SQL, TABLE_NAME, null);
@@ -77,6 +94,7 @@ public void testConstructor() {
assertNotNull(cursor);
}
+ @Test
public void testClose() {
SQLiteCursor cursor = getCursor();
assertTrue(cursor.moveToFirst());
@@ -92,9 +110,10 @@ public void testClose() {
assertTrue(cursor.isClosed());
}
+ @Test
public void testRegisterDataSetObserver() {
SQLiteCursor cursor = getCursor();
- MockCursorWindow cursorWindow = new MockCursorWindow(false);
+ MockCursorWindow cursorWindow = new MockCursorWindow();
MockObserver observer = new MockObserver();
@@ -143,6 +162,7 @@ public void testRegisterDataSetObserver() {
assertFalse(observer.hasInvalidated());
}
+ @Test
public void testRequery() {
final String DELETE = "DELETE FROM " + TABLE_NAME + " WHERE number_1 =";
final String DELETE_1 = DELETE + "1;";
@@ -166,6 +186,8 @@ public void testRequery() {
assertTrue(observer.hasChanged());
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testRequery2() {
mDatabase.disableWriteAheadLogging();
mDatabase.execSQL("create table testRequery2 (i int);");
@@ -192,6 +214,7 @@ public void testRequery2() {
assertFalse(c.requery());
}
+ @Test
public void testGetColumnIndex() {
SQLiteCursor cursor = getCursor();
@@ -202,6 +225,7 @@ public void testGetColumnIndex() {
assertTrue(Arrays.equals(COLUMNS, cursor.getColumnNames()));
}
+ @Test
public void testSetSelectionArguments() {
final String SELECTION = "_id > ?";
int TEST_ARG1 = 2;
@@ -214,6 +238,7 @@ public void testSetSelectionArguments() {
assertEquals(TEST_COUNT - TEST_ARG2, cursor.getCount());
}
+ @Test
public void testOnMove() {
// Do not test this API. It is callback which:
// 1. The callback mechanism has been tested in super class
@@ -271,8 +296,8 @@ protected boolean hasInvalidated () {
private class MockCursorWindow extends CursorWindow {
private boolean mIsClosed = false;
- public MockCursorWindow(boolean localWindow) {
- super(localWindow);
+ public MockCursorWindow() {
+ super("");
}
@Override
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseCorruptExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseCorruptExceptionTest.java
index f79bff8..378e08f 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseCorruptExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseCorruptExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteDatabaseCorruptException;
-import android.test.AndroidTestCase;
-public class SQLiteDatabaseCorruptExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteDatabaseCorruptExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteDatabaseCorruptException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseTest.java
index fe1f2c4..cccd81f 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDatabaseTest.java
@@ -16,11 +16,19 @@
package net.zetetic.database.sqlcipher_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.ContentValues;
import android.database.Cursor;
import android.database.SQLException;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import net.zetetic.database.DatabaseUtils;
import net.zetetic.database.sqlcipher.SQLiteCursor;
@@ -31,11 +39,17 @@
import net.zetetic.database.sqlcipher.SQLiteStatement;
import net.zetetic.database.sqlcipher.SQLiteTransactionListener;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Semaphore;
-public class SQLiteDatabaseTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteDatabaseTest {
private SQLiteDatabase mDatabase;
private File mDatabaseFile;
private String mDatabaseFilePath;
@@ -58,14 +72,13 @@ public class SQLiteDatabaseTest extends AndroidTestCase {
"address" // 3
};
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
- getContext().deleteDatabase(DATABASE_FILE_NAME);
- mDatabaseFilePath = getContext().getDatabasePath(DATABASE_FILE_NAME).getPath();
- mDatabaseFile = getContext().getDatabasePath(DATABASE_FILE_NAME);
+ ApplicationProvider.getApplicationContext().deleteDatabase(DATABASE_FILE_NAME);
+ mDatabaseFilePath = ApplicationProvider.getApplicationContext().getDatabasePath(DATABASE_FILE_NAME).getPath();
+ mDatabaseFile = ApplicationProvider.getApplicationContext().getDatabasePath(DATABASE_FILE_NAME);
mDatabaseDir = mDatabaseFile.getParent();
mDatabaseFile.getParentFile().mkdirs(); // directory may not exist
mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile, null);
@@ -76,13 +89,13 @@ protected void setUp() throws Exception {
mTransactionListenerOnRollbackCalled = false;
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
mDatabaseFile.delete();
- super.tearDown();
}
+ @Test
public void testOpenDatabase() {
CursorFactory factory = new CursorFactory() {
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
@@ -115,6 +128,7 @@ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
db.close();
}
+ @Test
public void testDeleteDatabase() throws IOException {
File dbFile = new File(mDatabaseDir, "database_test12345678.db");
File journalFile = new File(dbFile.getPath() + "-journal");
@@ -149,13 +163,16 @@ public void testDeleteDatabase() throws IOException {
assertFalse(deletedAgain);
}
- private class MockSQLiteCursor extends SQLiteCursor {
+ @SuppressWarnings("deprecation")
+ private static class MockSQLiteCursor extends SQLiteCursor {
public MockSQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
String editTable, SQLiteQuery query) {
super(db, driver, editTable, query);
}
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testTransaction() {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
mDatabase.execSQL("INSERT INTO test (num) VALUES (0)");
@@ -282,19 +299,16 @@ private void assertThrowsIllegalState(Runnable r) {
}
}
+ @Test
public void testAccessMaximumSize() {
long curMaximumSize = mDatabase.getMaximumSize();
// the new maximum size is less than the current size.
mDatabase.setMaximumSize(curMaximumSize - 1);
assertEquals(curMaximumSize, mDatabase.getMaximumSize());
-
- // the new maximum size is more than the current size.
- mDatabase.setMaximumSize(curMaximumSize + 1);
- assertEquals(curMaximumSize + mDatabase.getPageSize(), mDatabase.getMaximumSize());
- assertTrue(mDatabase.getMaximumSize() > curMaximumSize);
}
+ @Test
public void testAccessPageSize() {
File databaseFile = new File(mDatabaseDir, "database.db");
if (databaseFile.exists()) {
@@ -318,6 +332,8 @@ public void testAccessPageSize() {
}
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testCompileStatement() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, "
+ "name TEXT, age INTEGER, address TEXT);");
@@ -359,6 +375,7 @@ public void testCompileStatement() {
cursor.close();
}
+ @Test
public void testDelete() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, "
+ "name TEXT, age INTEGER, address TEXT);");
@@ -413,6 +430,7 @@ public void testDelete() {
cursor.close();
}
+ @Test
public void testExecSQL() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, "
+ "name TEXT, age INTEGER, address TEXT);");
@@ -503,6 +521,7 @@ public void testExecSQL() {
cursor.close();;
}
+ @Test
public void testFindEditTable() {
String tables = "table1 table2 table3";
assertEquals("table1", SQLiteDatabase.findEditTable(tables));
@@ -520,10 +539,12 @@ public void testFindEditTable() {
}
}
+ @Test
public void testGetPath() {
assertEquals(mDatabaseFilePath, mDatabase.getPath());
}
+ @Test
public void testAccessVersion() {
mDatabase.setVersion(1);
assertEquals(1, mDatabase.getVersion());
@@ -532,6 +553,7 @@ public void testAccessVersion() {
assertEquals(3, mDatabase.getVersion());
}
+ @Test
public void testInsert() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, "
+ "name TEXT, age INTEGER, address TEXT);");
@@ -616,6 +638,7 @@ public void testInsert() {
}
}
+ @Test
public void testIsOpen() {
assertTrue(mDatabase.isOpen());
@@ -623,6 +646,7 @@ public void testIsOpen() {
assertFalse(mDatabase.isOpen());
}
+ @Test
public void testIsReadOnly() {
assertFalse(mDatabase.isReadOnly());
@@ -638,10 +662,13 @@ public void testIsReadOnly() {
}
}
+ @Test
public void testReleaseMemory() {
SQLiteDatabase.releaseMemory();
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testSetLockingEnabled() {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
mDatabase.execSQL("INSERT INTO test (num) VALUES (0)");
@@ -655,6 +682,7 @@ public void testSetLockingEnabled() {
mDatabase.endTransaction();
}
+ @Test
@SuppressWarnings("deprecation")
public void testYieldIfContendedWhenNotContended() {
assertFalse(mDatabase.yieldIfContended());
@@ -683,6 +711,7 @@ public void testYieldIfContendedWhenNotContended() {
mDatabase.endTransaction();
}
+ @Test
@SuppressWarnings("deprecation")
public void testYieldIfContendedWhenContended() throws Exception {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
@@ -735,6 +764,7 @@ public void run() {
t.join();
}
+ @Test
public void testQuery() {
mDatabase.execSQL("CREATE TABLE employee (_id INTEGER PRIMARY KEY, " +
"name TEXT, month INTEGER, salary INTEGER);");
@@ -844,6 +874,7 @@ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
cursor.close();
}
+ @Test
public void testReplace() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, "
+ "name TEXT, age INTEGER, address TEXT);");
@@ -909,6 +940,7 @@ public void testReplace() {
}
}
+ @Test
public void testUpdate() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, data TEXT);");
@@ -929,6 +961,7 @@ public void testUpdate() {
cursor.close();
}
+ @Test
public void testNeedUpgrade() {
mDatabase.setVersion(0);
assertTrue(mDatabase.needUpgrade(1));
@@ -936,6 +969,7 @@ public void testNeedUpgrade() {
assertFalse(mDatabase.needUpgrade(1));
}
+ @Test
public void testSetLocale() {
// final String[] STRINGS = {
// "c\u00f4t\u00e9",
@@ -979,12 +1013,14 @@ public void testSetLocale() {
// });
}
+ @Test
public void testOnAllReferencesReleased() {
assertTrue(mDatabase.isOpen());
mDatabase.releaseReference();
assertFalse(mDatabase.isOpen());
}
+ @Test
public void testTransactionWithSQLiteTransactionListener() {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
mDatabase.execSQL("INSERT INTO test (num) VALUES (0)");
@@ -1016,6 +1052,7 @@ public void testTransactionWithSQLiteTransactionListener() {
assertEquals(mTransactionListenerOnRollbackCalled, false);
}
+ @Test
public void testRollbackTransactionWithSQLiteTransactionListener() {
mDatabase.execSQL("CREATE TABLE test (num INTEGER);");
mDatabase.execSQL("INSERT INTO test (num) VALUES (0)");
@@ -1060,6 +1097,7 @@ public void onRollback() {
}
}
+ @Test
public void testGroupConcat() {
mDatabase.execSQL("CREATE TABLE test (i INT, j TEXT);");
@@ -1093,6 +1131,7 @@ public void testGroupConcat() {
// should get no exceptions
}
+ @Test
public void testSchemaChanges() {
mDatabase.execSQL("CREATE TABLE test (i INT, j INT);");
@@ -1160,6 +1199,7 @@ public void testSchemaChanges() {
deleteStatement.close();
}
+ @Test
public void testSchemaChangesNewTable() {
mDatabase.execSQL("CREATE TABLE test (i INT, j INT);");
@@ -1224,6 +1264,7 @@ public void testSchemaChangesNewTable() {
deleteStatement2.close();
}
+ @Test
public void testSchemaChangesDropTable() {
mDatabase.execSQL("CREATE TABLE test (i INT, j INT);");
@@ -1269,7 +1310,8 @@ public void testSchemaChangesDropTable() {
*
* @throws InterruptedException
*/
- @LargeTest
+ @Test
+ @SuppressWarnings("deprecation")
public void testReaderGetsOldVersionOfDataWhenWriterIsInXact() throws InterruptedException {
// redo setup to create WAL enabled database
mDatabase.close();
@@ -1343,6 +1385,7 @@ public ReaderQueryingData(int count) {
}
}
+ @Test
public void testExceptionsFromEnableWriteAheadLogging() {
// attach a database
// redo setup to create WAL enabled database
@@ -1364,6 +1407,7 @@ public void testExceptionsFromEnableWriteAheadLogging() {
db.close();
}
+ @Test
public void testEnableThenDisableWriteAheadLogging() {
// Enable WAL.
assertFalse(mDatabase.isWriteAheadLoggingEnabled());
@@ -1383,6 +1427,7 @@ public void testEnableThenDisableWriteAheadLogging() {
assertFalse(mDatabase.isWriteAheadLoggingEnabled());
}
+ @Test
public void testEnableThenDisableWriteAheadLoggingUsingOpenFlag() {
new File(mDatabase.getPath()).delete();
mDatabase = SQLiteDatabase.openDatabase(mDatabaseFile.getPath(), null,
@@ -1403,11 +1448,12 @@ public void testEnableThenDisableWriteAheadLoggingUsingOpenFlag() {
assertFalse(mDatabase.isWriteAheadLoggingEnabled());
}
+ @Test
public void testEnableWriteAheadLoggingFromContextUsingModeFlag() {
// Without the MODE_ENABLE_WRITE_AHEAD_LOGGING flag, database opens without WAL.
- getContext().deleteDatabase(DATABASE_FILE_NAME);
+ ApplicationProvider.getApplicationContext().deleteDatabase(DATABASE_FILE_NAME);
- File f = getContext().getDatabasePath(DATABASE_FILE_NAME);
+ File f = ApplicationProvider.getApplicationContext().getDatabasePath(DATABASE_FILE_NAME);
mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
assertFalse(mDatabase.isWriteAheadLoggingEnabled());
mDatabase.close();
@@ -1420,6 +1466,7 @@ public void testEnableWriteAheadLoggingFromContextUsingModeFlag() {
// mDatabase.close();
}
+ @Test
public void testEnableWriteAheadLoggingShouldThrowIfTransactionInProgress() {
assertFalse(mDatabase.isWriteAheadLoggingEnabled());
String oldJournalMode = DatabaseUtils.stringForQuery(
@@ -1441,6 +1488,7 @@ public void testEnableWriteAheadLoggingShouldThrowIfTransactionInProgress() {
.equalsIgnoreCase(oldJournalMode));
}
+ @Test
public void testDisableWriteAheadLoggingShouldThrowIfTransactionInProgress() {
// Enable WAL.
assertFalse(mDatabase.isWriteAheadLoggingEnabled());
@@ -1463,6 +1511,7 @@ public void testDisableWriteAheadLoggingShouldThrowIfTransactionInProgress() {
.equalsIgnoreCase("WAL"));
}
+ @Test
public void testEnableAndDisableForeignKeys() {
// Initially off.
assertEquals(0, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
@@ -1490,4 +1539,17 @@ public void testEnableAndDisableForeignKeys() {
mDatabase.setForeignKeyConstraintsEnabled(true);
assertEquals(1, DatabaseUtils.longForQuery(mDatabase, "PRAGMA foreign_keys", null));
}
+
+ @Test
+ public void testShouldSupportBindingNullValue(){
+ String b = "";
+ mDatabase.execSQL("CREATE TABLE t1(a,b);");
+ mDatabase.execSQL("INSERT INTO t1 VALUES(?, ?);", new Object[]{null, "123"});
+ Cursor cursor = mDatabase.rawQuery("SELECT * FROM t1 WHERE a is ?;", new Object[]{null});
+ if(cursor != null && cursor.moveToNext()){
+ b = cursor.getString(1);
+ cursor.close();
+ }
+ assertEquals("123", b);
+ }
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDiskIOExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDiskIOExceptionTest.java
index 9cd9d8d..febd2e3 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDiskIOExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDiskIOExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteDiskIOException;
-import android.test.AndroidTestCase;
-public class SQLiteDiskIOExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteDiskIOExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteDiskIOException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDoneExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDoneExceptionTest.java
index 231113f..384903a 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDoneExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteDoneExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteDoneException;
-import android.test.AndroidTestCase;
-public class SQLiteDoneExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteDoneExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteDoneException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteExceptionTest.java
index 1d9c14b..92d2c56 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteException;
-import android.test.AndroidTestCase;
-public class SQLiteExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java
index 3f51469..4708ddd 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFtsTest.java
@@ -16,19 +16,32 @@
package net.zetetic.database.sqlcipher_cts;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
import android.content.ContentValues;
+import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.test.AndroidTestCase;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
import net.zetetic.database.sqlcipher.SQLiteDatabase;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
/**
* Tests to verify FTS3/4 SQLite support.
*/
-public class SQLiteFtsTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteFtsTest {
private static final String TEST_TABLE = "cts_fts";
@@ -39,10 +52,11 @@ public class SQLiteFtsTest extends AndroidTestCase {
};
private SQLiteDatabase mDatabase;
+ private Context mContext;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
+ mContext = ApplicationProvider.getApplicationContext();
System.loadLibrary("sqlcipher");
File f = mContext.getDatabasePath("CTS_FTS");
f.mkdirs();
@@ -50,17 +64,14 @@ public void setUp() throws Exception {
mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
}
- @Override
+ @After
public void tearDown() throws Exception {
- try {
- final String path = mDatabase.getPath();
- mDatabase.close();
- SQLiteDatabase.deleteDatabase(new File(path));
- } finally {
- super.tearDown();
- }
+ final String path = mDatabase.getPath();
+ mDatabase.close();
+ SQLiteDatabase.deleteDatabase(new File(path));
}
+ @Test
public void testFts3Porter() throws Exception {
prepareFtsTable(TEST_TABLE, "fts3", "tokenize=porter");
@@ -77,6 +88,7 @@ public void testFts3Porter() throws Exception {
}
}
+ @Test
public void testFts3Simple() throws Exception {
prepareFtsTable(TEST_TABLE, "fts3", "tokenize=simple");
@@ -91,6 +103,7 @@ public void testFts3Simple() throws Exception {
}
}
+ @Test
public void testFts4Simple() throws Exception {
prepareFtsTable(TEST_TABLE, "fts4", "tokenize=simple");
@@ -111,7 +124,7 @@ private void prepareFtsTable(String table, String ftsType, String options)
"CREATE VIRTUAL TABLE " + table + " USING " + ftsType
+ "(content TEXT, " + options + ");");
- final Resources res = getContext().getResources();
+ final Resources res = mContext.getResources();
final ContentValues values = new ContentValues();
for (String content : TEST_CONTENT) {
values.clear();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFullExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFullExceptionTest.java
index a62f3cd..2e62200 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFullExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteFullExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteFullException;
-import android.test.AndroidTestCase;
-public class SQLiteFullExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteFullExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteFullException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteMisuseExceptionTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteMisuseExceptionTest.java
index dd3bfa3..3b91cd5 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteMisuseExceptionTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteMisuseExceptionTest.java
@@ -17,9 +17,16 @@
package net.zetetic.database.sqlcipher_cts;
import android.database.sqlite.SQLiteMisuseException;
-import android.test.AndroidTestCase;
-public class SQLiteMisuseExceptionTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteMisuseExceptionTest {
+
+ @Test
public void testConstructor() {
new SQLiteMisuseException();
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteOpenHelperTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteOpenHelperTest.java
index 822cfec..5adc5c7 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteOpenHelperTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteOpenHelperTest.java
@@ -16,9 +16,18 @@
package net.zetetic.database.sqlcipher_cts;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.content.Context;
import android.database.Cursor;
-import android.test.AndroidTestCase;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import net.zetetic.database.sqlcipher.SQLiteCursor;
import net.zetetic.database.sqlcipher.SQLiteCursorDriver;
@@ -27,15 +36,21 @@
import net.zetetic.database.sqlcipher.SQLiteOpenHelper;
import net.zetetic.database.sqlcipher.SQLiteQuery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
/**
* Test {@link SQLiteOpenHelper}.
*/
-public class SQLiteOpenHelperTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteOpenHelperTest {
private static final String TEST_DATABASE_NAME = "database_test.db";
static String DATABASE_PATH;
private static final int TEST_VERSION = 1;
private static final int TEST_ILLEGAL_VERSION = 0;
private MockOpenHelper mOpenHelper;
+ private Context mContext;
private SQLiteDatabase.CursorFactory mFactory = new SQLiteDatabase.CursorFactory() {
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
String editTable, SQLiteQuery query) {
@@ -43,14 +58,15 @@ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
}
};
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
+ mContext = ApplicationProvider.getApplicationContext();
DATABASE_PATH = mContext.getDatabasePath(TEST_DATABASE_NAME).toString();
mOpenHelper = getOpenHelper();
}
+ @Test
public void testConstructor() {
new MockOpenHelper(mContext, DATABASE_PATH, mFactory, TEST_VERSION);
@@ -65,6 +81,7 @@ public void testConstructor() {
new MockOpenHelper(mContext, DATABASE_PATH, null, TEST_VERSION);
}
+ @Test
public void testGetDatabase() {
SQLiteDatabase database = null;
assertFalse(mOpenHelper.hasCalledOnOpen());
@@ -131,6 +148,7 @@ public void resetStatus() {
}
}
+ @SuppressWarnings("deprecation")
private class MockCursor extends SQLiteCursor {
public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver, String editTable,
SQLiteQuery query) {
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteProgramTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteProgramTest.java
index 47ddacd..5317b03 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteProgramTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteProgramTest.java
@@ -17,25 +17,40 @@
package net.zetetic.database.sqlcipher_cts;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteException;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import net.zetetic.database.sqlcipher.SQLiteStatement;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
-public class SQLiteProgramTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteProgramTest {
private static final String DATABASE_NAME = "database_test.db";
private SQLiteDatabase mDatabase;
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
System.loadLibrary("sqlcipher");
File f = mContext.getDatabasePath(DATABASE_NAME);
f.mkdirs();
@@ -44,14 +59,14 @@ protected void setUp() throws Exception {
assertNotNull(mDatabase);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
- getContext().deleteDatabase(DATABASE_NAME);
+ mContext.deleteDatabase(DATABASE_NAME);
- super.tearDown();
}
+ @Test
public void testBind() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
"num1 INTEGER, num2 INTEGER, image BLOB);");
@@ -122,6 +137,7 @@ public void testBind() {
statement.close();
}
+ @Test
public void testBindNull() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
"num1 INTEGER, num2 INTEGER, image BLOB);");
@@ -152,6 +168,7 @@ public void testBindNull() {
cursor.close();
}
+ @Test
public void testBindBlob() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " +
"num1 INTEGER, num2 INTEGER, image BLOB);");
@@ -180,7 +197,7 @@ public void testBindBlob() {
assertEquals("string2", cursor.getString(COLUMN_TEXT2_INDEX));
assertEquals(100, cursor.getInt(COLUMN_NUM1_INDEX));
byte[] value = cursor.getBlob(COLUMN_IMAGE_INDEX);
- MoreAsserts.assertEquals(blob, value);
+ assertArrayEquals(blob, value);
cursor.close();
}
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java
index aef5c5e..99a7ac1 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteQueryBuilderTest.java
@@ -20,7 +20,9 @@
import android.database.Cursor;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
-import android.test.AndroidTestCase;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import net.zetetic.database.sqlcipher.SQLiteCursor;
import net.zetetic.database.sqlcipher.SQLiteCursorDriver;
@@ -28,39 +30,51 @@
import net.zetetic.database.sqlcipher.SQLiteQuery;
import net.zetetic.database.sqlcipher.SQLiteQueryBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
-public class SQLiteQueryBuilderTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteQueryBuilderTest {
private SQLiteDatabase mDatabase;
private final String TEST_TABLE_NAME = "test";
private final String EMPLOYEE_TABLE_NAME = "employee";
private static final String DATABASE_FILE = "database_test.db";
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
System.loadLibrary("sqlcipher");
- File f = mContext.getDatabasePath(DATABASE_FILE);
+ File f = ApplicationProvider.getApplicationContext().getDatabasePath(DATABASE_FILE);
f.mkdirs();
if (f.exists()) { f.delete(); }
mDatabase = SQLiteDatabase.openOrCreateDatabase(f,null);
assertNotNull(mDatabase);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
- getContext().deleteDatabase(DATABASE_FILE);
- super.tearDown();
+ ApplicationProvider.getApplicationContext().deleteDatabase(DATABASE_FILE);
}
+ @Test
public void testConstructor() {
new SQLiteQueryBuilder();
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testSetDistinct() {
String expected;
SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
@@ -95,38 +109,8 @@ public void testSetDistinct() {
assertEquals(expected, sql);
}
- public void testSetProjectionMap() {
- String expected;
- Map projectMap = new HashMap();
- projectMap.put("EmployeeName", "name");
- projectMap.put("EmployeeAge", "age");
- projectMap.put("EmployeeAddress", "address");
- SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
- sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
- sqliteQueryBuilder.setDistinct(false);
- sqliteQueryBuilder.setProjectionMap(projectMap);
- String sql = sqliteQueryBuilder.buildQuery(new String[] { "EmployeeName", "EmployeeAge" },
- null, null, null, null, null, null);
- expected = "SELECT name, age FROM " + TEST_TABLE_NAME;
- assertEquals(expected, sql);
-
- sql = sqliteQueryBuilder.buildQuery(null, // projectionIn is null
- null, null, null, null, null, null);
- assertTrue(sql.matches("SELECT (age|name|address), (age|name|address), (age|name|address) "
- + "FROM " + TEST_TABLE_NAME));
- assertTrue(sql.contains("age"));
- assertTrue(sql.contains("name"));
- assertTrue(sql.contains("address"));
-
- sqliteQueryBuilder.setProjectionMap(null);
- sql = sqliteQueryBuilder.buildQuery(new String[] { "name", "address" },
- null, null, null, null, null, null);
- assertTrue(sql.matches("SELECT (name|address), (name|address) "
- + "FROM " + TEST_TABLE_NAME));
- assertTrue(sql.contains("name"));
- assertTrue(sql.contains("address"));
- }
-
+ @Test
+ @SuppressWarnings("deprecation")
public void testSetCursorFactory() {
mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, " +
"name TEXT, age INTEGER, address TEXT);");
@@ -154,6 +138,41 @@ public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
assertTrue(cursor instanceof MockCursor);
}
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testSetProjectionMap() {
+ String expected;
+ Map projectMap = new HashMap();
+ projectMap.put("EmployeeName", "name");
+ projectMap.put("EmployeeAge", "age");
+ projectMap.put("EmployeeAddress", "address");
+ SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
+ sqliteQueryBuilder.setTables(TEST_TABLE_NAME);
+ sqliteQueryBuilder.setDistinct(false);
+ sqliteQueryBuilder.setProjectionMap(projectMap);
+ String sql = sqliteQueryBuilder.buildQuery(new String[] { "EmployeeName", "EmployeeAge" },
+ null, null, null, null, null, null);
+ expected = "SELECT name, age FROM " + TEST_TABLE_NAME;
+ assertEquals(expected, sql);
+
+ sql = sqliteQueryBuilder.buildQuery(null, // projectionIn is null
+ null, null, null, null, null, null);
+ assertTrue(sql.matches("SELECT (age|name|address), (age|name|address), (age|name|address) "
+ + "FROM " + TEST_TABLE_NAME));
+ assertTrue(sql.contains("age"));
+ assertTrue(sql.contains("name"));
+ assertTrue(sql.contains("address"));
+
+ sqliteQueryBuilder.setProjectionMap(null);
+ sql = sqliteQueryBuilder.buildQuery(new String[] { "name", "address" },
+ null, null, null, null, null, null);
+ assertTrue(sql.matches("SELECT (name|address), (name|address) "
+ + "FROM " + TEST_TABLE_NAME));
+ assertTrue(sql.contains("name"));
+ assertTrue(sql.contains("address"));
+ }
+
+ @SuppressWarnings("deprecation")
private static class MockCursor extends SQLiteCursor {
public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
String editTable, SQLiteQuery query) {
@@ -161,6 +180,7 @@ public MockCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
}
}
+ @Test
public void testBuildQueryString() {
String expected;
final String[] DEFAULT_TEST_PROJECTION = new String [] { "name", "age", "sum(salary)" };
@@ -179,6 +199,8 @@ public void testBuildQueryString() {
assertEquals(expected, sql);
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testBuildQuery() {
final String[] DEFAULT_TEST_PROJECTION = new String[] { "name", "sum(salary)" };
final String DEFAULT_TEST_WHERE = "age > 25";
@@ -195,6 +217,7 @@ public void testBuildQuery() {
assertEquals(expected, sql);
}
+ @Test
public void testAppendColumns() {
StringBuilder sb = new StringBuilder();
String[] columns = new String[] { "name", "age" };
@@ -204,6 +227,7 @@ public void testAppendColumns() {
assertEquals("name, age ", sb.toString());
}
+ @Test
public void testQuery() {
createEmployeeTable();
@@ -243,6 +267,8 @@ public void testQuery() {
assertEquals(4000, cursor.getInt(COLUMN_SALARY_INDEX));
}
+ @Test
+ @SuppressWarnings("deprecation")
public void testUnionQuery() {
String expected;
String[] innerProjection = new String[] {"name", "age", "location"};
@@ -278,6 +304,7 @@ public void testUnionQuery() {
assertEquals(expected, unionQuery);
}
+ @Test
public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
createEmployeeTable();
@@ -291,6 +318,7 @@ public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
assertEquals(3, cursor.getCount());
}
+ @Test
public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
createEmployeeTable();
@@ -309,6 +337,7 @@ public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
}
}
+ @Test
public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() {
createEmployeeTable();
@@ -329,6 +358,7 @@ public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() {
}
}
+ @Test
public void testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows() {
createEmployeeTable();
@@ -404,6 +434,7 @@ public void run() {
fail("Could not prove that the query actually blocked before cancel() was called.");
}
+ @Test
public void testCancelableQuery_WhenCanceledDuringLongRunningQuery_CancelsQueryAndThrows() {
// Populate a table with a bunch of integers.
mDatabase.execSQL("CREATE TABLE x (v INTEGER);");
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteStatementTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteStatementTest.java
index ffd4441..c9f62a9 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteStatementTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SQLiteStatementTest.java
@@ -17,7 +17,14 @@
package net.zetetic.database.sqlcipher_cts;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import net.zetetic.database.DatabaseUtils;
import net.zetetic.database.sqlcipher.SQLiteDatabase;
@@ -26,15 +33,22 @@
import android.database.SQLException;
import android.database.sqlite.SQLiteDoneException;
import android.os.ParcelFileDescriptor;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.Suppress;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
-public class SQLiteStatementTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SQLiteStatementTest {
private static final String STRING1 = "this is a test";
private static final String STRING2 = "another test";
@@ -51,12 +65,13 @@ public class SQLiteStatementTest extends AndroidTestCase {
private static final int CURRENT_DATABASE_VERSION = 42;
private SQLiteDatabase mDatabase;
+ private Context mContext;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
+ mContext = ApplicationProvider.getApplicationContext();
System.loadLibrary("sqlcipher");
- getContext().deleteDatabase(DATABASE_NAME);
+ mContext.deleteDatabase(DATABASE_NAME);
File f = mContext.getDatabasePath(DATABASE_NAME);
f.mkdirs();
if (f.exists()) { f.delete(); }
@@ -65,11 +80,10 @@ protected void setUp() throws Exception {
mDatabase.setVersion(CURRENT_DATABASE_VERSION);
}
- @Override
- protected void tearDown() throws Exception {
+ @After
+ public void tearDown() throws Exception {
mDatabase.close();
- getContext().deleteDatabase(DATABASE_NAME);
- super.tearDown();
+ mContext.deleteDatabase(DATABASE_NAME);
}
private void populateDefaultTable() {
@@ -86,6 +100,7 @@ private void populateBlobTable() {
}
}
+ @Test
public void testExecute() {
mDatabase.disableWriteAheadLogging();
populateDefaultTable();
@@ -129,6 +144,7 @@ public void testExecute() {
}
}
+ @Test
public void testExecuteInsert() {
populateDefaultTable();
@@ -168,6 +184,7 @@ public void testExecuteInsert() {
}
}
+ @Test
public void testSimpleQueryForLong() {
mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
@@ -192,6 +209,7 @@ public void testSimpleQueryForLong() {
statement.close();
}
+ @Test
public void testSimpleQueryForString() {
mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
@@ -217,16 +235,19 @@ public void testSimpleQueryForString() {
}
@Suppress
+ @Test
public void testSimpleQueryForBlobFileDescriptorSuccessNormal() throws IOException {
doTestSimpleQueryForBlobFileDescriptorSuccess(0);
}
@Suppress
+ @Test
public void testSimpleQueryForBlobFileDescriptorSuccessEmpty() throws IOException {
doTestSimpleQueryForBlobFileDescriptorSuccess(1);
}
@Suppress
+ @Test
public void testSimpleQueryForBlobFileDescriptorSuccessNull() {
populateBlobTable();
@@ -236,22 +257,25 @@ public void testSimpleQueryForBlobFileDescriptorSuccessNull() {
}
@Suppress
+ @Test
public void testSimpleQueryForBlobFileDescriptorSuccess00() throws IOException {
doTestSimpleQueryForBlobFileDescriptorSuccess(3);
}
@Suppress
+ @Test
public void testSimpleQueryForBlobFileDescriptorSuccessFF() throws IOException {
doTestSimpleQueryForBlobFileDescriptorSuccess(4);
}
@Suppress
+ @Test
public void testSimpleQueryForBlobFileDescriptorSuccessEmbeddedNul() throws IOException {
doTestSimpleQueryForBlobFileDescriptorSuccess(5);
}
@Suppress
- private void doTestSimpleQueryForBlobFileDescriptorSuccess(int i) throws IOException {
+ public void doTestSimpleQueryForBlobFileDescriptorSuccess(int i) throws IOException {
populateBlobTable();
String sql = "SELECT data FROM blob_test WHERE _id = " + i;
@@ -261,6 +285,7 @@ private void doTestSimpleQueryForBlobFileDescriptorSuccess(int i) throws IOExcep
}
@Suppress
+ @Test
public void testSimpleQueryForBlobFileDescriptorSuccessParam() throws IOException {
populateBlobTable();
@@ -271,6 +296,7 @@ public void testSimpleQueryForBlobFileDescriptorSuccessParam() throws IOExceptio
assertFileDescriptorContent(BLOBS[0], fd);
}
+ @Test
public void testGetBlobFailureNoParam() throws Exception {
populateBlobTable();
@@ -291,6 +317,7 @@ public void testGetBlobFailureNoParam() throws Exception {
assertNotNull("Should have thrown SQLiteDoneException", expectedException);
}
+ @Test
public void testGetBlobFailureParam() throws Exception {
populateBlobTable();
@@ -346,7 +373,7 @@ private static void assertInputStreamContent(byte[] expected, InputStream is)
int count = is.read(observed);
assertEquals(expected.length, count);
assertEquals(-1, is.read());
- MoreAsserts.assertEquals(expected, observed);
+ assertArrayEquals(expected, observed);
} finally {
is.close();
}
diff --git a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java
index 9acb9f0..0e7895c 100644
--- a/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java
+++ b/sqlcipher/src/androidTest/java/net/zetetic/database/sqlcipher_cts/SupportAPIRoomTest.java
@@ -1,7 +1,6 @@
package net.zetetic.database.sqlcipher_cts;
import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
@@ -29,7 +28,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,6 +48,9 @@ public void before(){
Context context = ApplicationProvider.getApplicationContext();
System.loadLibrary("sqlcipher");
databaseFile = context.getDatabasePath("users.db");
+ if(databaseFile.exists()){
+ databaseFile.delete();
+ }
SupportOpenHelperFactory factory = new SupportOpenHelperFactory("user".getBytes(StandardCharsets.UTF_8));
db = Room.databaseBuilder(context, AppDatabase.class, databaseFile.getAbsolutePath())
.openHelperFactory(factory).build();
@@ -117,7 +118,7 @@ public void after(){
}
}
- @Database(entities = {User.class}, version = 1)
+ @Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract static class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
diff --git a/sqlcipher/src/main/AndroidManifest.xml b/sqlcipher/src/main/AndroidManifest.xml
index fc4c1a9..a2f47b6 100644
--- a/sqlcipher/src/main/AndroidManifest.xml
+++ b/sqlcipher/src/main/AndroidManifest.xml
@@ -1,3 +1,2 @@
-
+
diff --git a/sqlcipher/src/main/java/net/zetetic/database/AbstractCursor.java b/sqlcipher/src/main/java/net/zetetic/database/AbstractCursor.java
new file mode 100644
index 0000000..c8356fa
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/AbstractCursor.java
@@ -0,0 +1,426 @@
+/*
+ * 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.
+ */
+// modified from original source see README at the top level of this project
+
+package net.zetetic.database;
+
+import android.content.ContentResolver;
+import android.database.CharArrayBuffer;
+import android.database.ContentObservable;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.CursorIndexOutOfBoundsException;
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This is an abstract cursor class that handles a lot of the common code
+ * that all cursors need to deal with and is provided for convenience reasons.
+ */
+public abstract class AbstractCursor implements Cursor {
+
+ private static final String TAG = "Cursor";
+
+ protected int mPos;
+
+ protected boolean mClosed;
+
+ //@Deprecated // deprecated in AOSP but still used for non-deprecated methods
+ protected ContentResolver mContentResolver;
+
+ private Uri mNotifyUri;
+
+ private final Object mSelfObserverLock = new Object();
+ private ContentObserver mSelfObserver;
+ private boolean mSelfObserverRegistered;
+ private final DataSetObservable mDataSetObservable = new DataSetObservable();
+ private final ContentObservable mContentObservable = new ContentObservable();
+
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public void fillWindow(int position, CursorWindow window) {
+ DatabaseUtils.cursorFillWindow(this, position, window);
+ }
+
+ @Override
+ abstract public int getCount();
+
+ @Override
+ abstract public String[] getColumnNames();
+
+ @Override
+ abstract public String getString(int column);
+ @Override
+ abstract public short getShort(int column);
+ @Override
+ abstract public int getInt(int column);
+ @Override
+ abstract public long getLong(int column);
+ @Override
+ abstract public float getFloat(int column);
+ @Override
+ abstract public double getDouble(int column);
+ @Override
+ abstract public boolean isNull(int column);
+
+ @Override
+ public abstract int getType(int column);
+
+ @Override
+ public byte[] getBlob(int column) {
+ throw new UnsupportedOperationException("getBlob is not supported");
+ }
+
+ @Override
+ public int getColumnCount() {
+ return getColumnNames().length;
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void deactivate() {
+ onDeactivateOrClose();
+ }
+
+ /** @hide */
+ protected void onDeactivateOrClose() {
+ if (mSelfObserver != null) {
+ mContentResolver.unregisterContentObserver(mSelfObserver);
+ mSelfObserverRegistered = false;
+ }
+ mDataSetObservable.notifyInvalidated();
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public boolean requery() {
+ if (mSelfObserver != null && !mSelfObserverRegistered) {
+ mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
+ mSelfObserverRegistered = true;
+ }
+ mDataSetObservable.notifyChanged();
+ return true;
+ }
+
+ @Override
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ @Override
+ public void close() {
+ mClosed = true;
+ mContentObservable.unregisterAll();
+ onDeactivateOrClose();
+ }
+
+ @Override
+ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+ // Default implementation, uses getString
+ String result = getString(columnIndex);
+ if (result != null) {
+ char[] data = buffer.data;
+ if (data == null || data.length < result.length()) {
+ buffer.data = result.toCharArray();
+ } else {
+ result.getChars(0, result.length(), data, 0);
+ }
+ buffer.sizeCopied = result.length();
+ } else {
+ buffer.sizeCopied = 0;
+ }
+ }
+
+ public AbstractCursor() {
+ mPos = -1;
+ }
+
+ @Override
+ public final int getPosition() {
+ return mPos;
+ }
+
+ @Override
+ public final boolean moveToPosition(int position) {
+ // Make sure position isn't past the end of the cursor
+ final int count = getCount();
+ if (position >= count) {
+ mPos = count;
+ return false;
+ }
+
+ // Make sure position isn't before the beginning of the cursor
+ if (position < 0) {
+ mPos = -1;
+ return false;
+ }
+
+ // Check for no-op moves, and skip the rest of the work for them
+ if (position == mPos) {
+ return true;
+ }
+
+ boolean result = onMove(mPos, position);
+ if (!result) {
+ mPos = -1;
+ } else {
+ mPos = position;
+ }
+
+ return result;
+ }
+
+ /**
+ * This function is called every time the cursor is successfully scrolled
+ * to a new position, giving the subclass a chance to update any state it
+ * may have. If it returns false the move function will also do so and the
+ * cursor will scroll to the beforeFirst position.
+ *
+ * This function should be called by methods such as {@link #moveToPosition(int)},
+ * so it will typically not be called from outside of the cursor class itself.
+ *
+ *
+ * @param oldPosition The position that we're moving from.
+ * @param newPosition The position that we're moving to.
+ * @return True if the move is successful, false otherwise.
+ */
+ public abstract boolean onMove(int oldPosition, int newPosition);
+
+ @Override
+ public final boolean move(int offset) {
+ return moveToPosition(mPos + offset);
+ }
+
+ @Override
+ public final boolean moveToFirst() {
+ return moveToPosition(0);
+ }
+
+ @Override
+ public final boolean moveToLast() {
+ return moveToPosition(getCount() - 1);
+ }
+
+ @Override
+ public final boolean moveToNext() {
+ return moveToPosition(mPos + 1);
+ }
+
+ @Override
+ public final boolean moveToPrevious() {
+ return moveToPosition(mPos - 1);
+ }
+
+ @Override
+ public final boolean isFirst() {
+ return mPos == 0 && getCount() != 0;
+ }
+
+ @Override
+ public final boolean isLast() {
+ int cnt = getCount();
+ return mPos == (cnt - 1) && cnt != 0;
+ }
+
+ @Override
+ public final boolean isBeforeFirst() {
+ return getCount() == 0 || mPos == -1;
+ }
+
+ @Override
+ public final boolean isAfterLast() {
+ return getCount() == 0 || mPos == getCount();
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ // Hack according to bug 903852
+ final int periodIndex = columnName.lastIndexOf('.');
+ if (periodIndex != -1) {
+ Exception e = new Exception();
+ Logger.e(TAG, "requesting column name with table name -- " + columnName, e);
+ columnName = columnName.substring(periodIndex + 1);
+ }
+
+ String columnNames[] = getColumnNames();
+ int length = columnNames.length;
+ for (int i = 0; i < length; i++) {
+ if (columnNames[i].equalsIgnoreCase(columnName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public int getColumnIndexOrThrow(String columnName) {
+ final int index = getColumnIndex(columnName);
+ if (index < 0) {
+ throw new IllegalArgumentException("column '" + columnName + "' does not exist");
+ }
+ return index;
+ }
+
+ @Override
+ public String getColumnName(int columnIndex) {
+ return getColumnNames()[columnIndex];
+ }
+
+ @Override
+ public void registerContentObserver(ContentObserver observer) {
+ mContentObservable.registerObserver(observer);
+ }
+
+ @Override
+ public void unregisterContentObserver(ContentObserver observer) {
+ // cursor will unregister all observers when it close
+ if (!mClosed) {
+ mContentObservable.unregisterObserver(observer);
+ }
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.registerObserver(observer);
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver observer) {
+ mDataSetObservable.unregisterObserver(observer);
+ }
+
+ /**
+ * Subclasses must call this method when they finish committing updates to notify all
+ * observers.
+ *
+ * @param selfChange value
+ */
+ @SuppressWarnings("deprecation")
+ protected void onChange(boolean selfChange) {
+ synchronized (mSelfObserverLock) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ mContentObservable.dispatchChange(selfChange, null);
+ } else {
+ mContentObservable.dispatchChange(selfChange);
+ }
+ if (mNotifyUri != null && selfChange) {
+ mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
+ }
+ }
+ }
+
+ /**
+ * Specifies a content URI to watch for changes.
+ *
+ * @param cr The content resolver from the caller's context.
+ * @param notifyUri The URI to watch for changes. This can be a
+ * specific row URI, or a base URI for a whole class of content.
+ */
+ @Override
+ public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
+ synchronized (mSelfObserverLock) {
+ mNotifyUri = notifyUri;
+ mContentResolver = cr;
+ if (mSelfObserver != null) {
+ mContentResolver.unregisterContentObserver(mSelfObserver);
+ }
+ mSelfObserver = new SelfContentObserver(this);
+ mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
+ mSelfObserverRegistered = true;
+ }
+ }
+
+ @Override
+ public Uri getNotificationUri() {
+ synchronized (mSelfObserverLock) {
+ return mNotifyUri;
+ }
+ }
+
+ @Override
+ public boolean getWantsAllOnMoveCalls() {
+ return false;
+ }
+
+ @Override
+ public void setExtras(Bundle extras) {
+ mExtras = (extras == null) ? Bundle.EMPTY : extras;
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public Bundle respond(Bundle extras) {
+ return Bundle.EMPTY;
+ }
+
+ /**
+ * This function throws CursorIndexOutOfBoundsException if the cursor position is out of bounds.
+ * Subclass implementations of the get functions should call this before attempting to
+ * retrieve data.
+ *
+ * @throws CursorIndexOutOfBoundsException
+ */
+ protected void checkPosition() {
+ if (-1 == mPos || getCount() == mPos) {
+ throw new CursorIndexOutOfBoundsException(mPos, getCount());
+ }
+ }
+
+ @SuppressWarnings("FinalizeDoesntCallSuperFinalize")
+ @Override
+ protected void finalize() {
+ if (mSelfObserver != null && mSelfObserverRegistered) {
+ mContentResolver.unregisterContentObserver(mSelfObserver);
+ }
+ try {
+ if (!mClosed) close();
+ } catch(Exception ignored) { }
+ }
+
+ /**
+ * Cursors use this class to track changes others make to their URI.
+ */
+ protected static class SelfContentObserver extends ContentObserver {
+ WeakReference mCursor;
+
+ public SelfContentObserver(AbstractCursor cursor) {
+ super(null);
+ mCursor = new WeakReference<>(cursor);
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return false;
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ AbstractCursor cursor = mCursor.get();
+ if (cursor != null) {
+ cursor.onChange(false);
+ }
+ }
+ }
+}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/AbstractWindowedCursor.java b/sqlcipher/src/main/java/net/zetetic/database/AbstractWindowedCursor.java
new file mode 100644
index 0000000..7c5cace
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/AbstractWindowedCursor.java
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+// modified from original source see README at the top level of this project
+
+package net.zetetic.database;
+
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.database.StaleDataException;
+
+/**
+ * A base class for Cursors that store their data in {@link android.database.CursorWindow}s.
+ *
+ * The cursor owns the cursor window it uses. When the cursor is closed,
+ * its window is also closed. Likewise, when the window used by the cursor is
+ * changed, its old window is closed. This policy of strict ownership ensures
+ * that cursor windows are not leaked.
+ *
+ * Subclasses are responsible for filling the cursor window with data during
+ * {@link #onMove(int, int)}, allocating a new cursor window if necessary.
+ * During {@link #requery()}, the existing cursor window should be cleared and
+ * filled with new data.
+ *
+ * If the contents of the cursor change or become invalid, the old window must be closed
+ * (because it is owned by the cursor) and set to null.
+ *
+ */
+@SuppressWarnings("unused")
+public abstract class AbstractWindowedCursor extends AbstractCursor {
+ /**
+ * The cursor window owned by this cursor.
+ */
+ protected CursorWindow mWindow;
+
+ @Override
+ public byte[] getBlob(int columnIndex) {
+ checkPosition();
+ return mWindow.getBlob(mPos, columnIndex);
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ checkPosition();
+ return mWindow.getString(mPos, columnIndex);
+ }
+
+ @Override
+ public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
+ mWindow.copyStringToBuffer(mPos, columnIndex, buffer);
+ }
+
+ @Override
+ public short getShort(int columnIndex) {
+ checkPosition();
+ return mWindow.getShort(mPos, columnIndex);
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ checkPosition();
+ return mWindow.getInt(mPos, columnIndex);
+ }
+
+ @Override
+ public long getLong(int columnIndex) {
+ checkPosition();
+ return mWindow.getLong(mPos, columnIndex);
+ }
+
+ @Override
+ public float getFloat(int columnIndex) {
+ checkPosition();
+ return mWindow.getFloat(mPos, columnIndex);
+ }
+
+ @Override
+ public double getDouble(int columnIndex) {
+ checkPosition();
+ return mWindow.getDouble(mPos, columnIndex);
+ }
+
+ @Override
+ public boolean isNull(int columnIndex) {
+ return mWindow.getType(mPos, columnIndex) == Cursor.FIELD_TYPE_NULL;
+ }
+
+ @Override
+ public int getType(int columnIndex) {
+ return mWindow.getType(mPos, columnIndex);
+ }
+
+ @Override
+ protected void checkPosition() {
+ super.checkPosition();
+ if (mWindow == null) {
+ throw new StaleDataException("Attempting to access a closed CursorWindow." +
+ "Most probable cause: cursor is deactivated prior to calling this method.");
+ }
+ }
+
+ public CursorWindow getWindow() {
+ return mWindow;
+ }
+
+ /**
+ * Sets a new cursor window for the cursor to use.
+ *
+ * The cursor takes ownership of the provided cursor window; the cursor window
+ * will be closed when the cursor is closed or when the cursor adopts a new
+ * cursor window.
+ *
+ * If the cursor previously had a cursor window, then it is closed when the
+ * new cursor window is assigned.
+ *
+ *
+ * @param window The new cursor window, typically a remote cursor window.
+ */
+ public void setWindow(CursorWindow window) {
+ if (window != mWindow) {
+ closeWindow();
+ mWindow = window;
+ }
+ }
+
+ /**
+ * Returns true if the cursor has an associated cursor window.
+ *
+ * @return True if the cursor has an associated cursor window.
+ */
+ public boolean hasWindow() {
+ return mWindow != null;
+ }
+
+ /**
+ * Closes the cursor window and sets {@link #mWindow} to null.
+ * @hide
+ */
+ protected void closeWindow() {
+ if (mWindow != null) {
+ mWindow.close();
+ mWindow = null;
+ }
+ }
+
+ /**
+ * If there is a window, clear it. Otherwise, creates a new window.
+ *
+ * @param name The window name.
+ * @hide
+ */
+ protected void clearOrCreateWindow(String name) {
+ if (mWindow == null) {
+ mWindow = new CursorWindow(name);
+ } else {
+ mWindow.clear();
+ }
+ }
+
+ @Override
+ protected void onDeactivateOrClose() {
+ super.onDeactivateOrClose();
+ closeWindow();
+ }
+}
\ No newline at end of file
diff --git a/sqlcipher/src/main/java/net/zetetic/database/CursorWindow.java b/sqlcipher/src/main/java/net/zetetic/database/CursorWindow.java
new file mode 100644
index 0000000..a604305
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/CursorWindow.java
@@ -0,0 +1,512 @@
+/*
+ * 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.
+ */
+// modified from original source see README at the top level of this project
+
+package net.zetetic.database;
+
+import android.database.CharArrayBuffer;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+
+import net.zetetic.database.sqlcipher.SQLiteClosable;
+
+/**
+ * A buffer containing multiple cursor rows.
+ */
+@SuppressWarnings("unused")
+public class CursorWindow extends SQLiteClosable {
+
+ private static final int WINDOW_SIZE_KB = 16;
+ public static int PREFERRED_CURSOR_WINDOW_SIZE = WINDOW_SIZE_KB * 1024;
+ public static final int DEFAULT_CURSOR_WINDOW_SIZE = WINDOW_SIZE_KB * 1024;
+ private final int mWindowSizeBytes;
+
+ /**
+ * The native CursorWindow object pointer. (FOR INTERNAL USE ONLY)
+ */
+ public long mWindowPtr;
+
+ private int mStartPos;
+ private final String mName;
+
+ private static native long nativeCreate(String name, int cursorWindowSize);
+ private static native void nativeDispose(long windowPtr);
+
+ private static native void nativeClear(long windowPtr);
+
+ private static native int nativeGetNumRows(long windowPtr);
+ private static native boolean nativeSetNumColumns(long windowPtr, int columnNum);
+ private static native boolean nativeAllocRow(long windowPtr);
+ private static native void nativeFreeLastRow(long windowPtr);
+
+ private static native int nativeGetType(long windowPtr, int row, int column);
+ private static native byte[] nativeGetBlob(long windowPtr, int row, int column);
+ private static native String nativeGetString(long windowPtr, int row, int column);
+ private static native long nativeGetLong(long windowPtr, int row, int column);
+ private static native double nativeGetDouble(long windowPtr, int row, int column);
+
+ private static native boolean nativePutBlob(long windowPtr, byte[] value, int row, int column);
+ private static native boolean nativePutString(long windowPtr, String value, int row, int column);
+ private static native boolean nativePutLong(long windowPtr, long value, int row, int column);
+ private static native boolean nativePutDouble(long windowPtr, double value, int row, int column);
+ private static native boolean nativePutNull(long windowPtr, int row, int column);
+
+ private static native String nativeGetName(long windowPtr);
+
+ /**
+ * Creates a new empty cursor with default cursor size (currently 2MB)
+ */
+ public CursorWindow(String name) {
+ this(name, DEFAULT_CURSOR_WINDOW_SIZE);
+ }
+
+ /**
+ * Creates a new empty cursor window and gives it a name.
+ *
+ * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to
+ * set the number of columns before adding any rows to the cursor.
+ *
+ *
+ * @param name The name of the cursor window, or null if none.
+ * @param windowSizeBytes Size of cursor window in bytes.
+ *
+ * Note: Memory is dynamically allocated as data rows are added to
+ * the window. Depending on the amount of data stored, the actual
+ * amount of memory allocated can be lower than specified size,
+ * but cannot exceed it. Value is a non-negative number of bytes.
+ */
+ public CursorWindow(String name, int windowSizeBytes) {
+ /* In
+ https://developer.android.com/reference/android/database/CursorWindow#CursorWindow(java.lang.String,%20long)
+ windowSizeBytes is long. However windowSizeBytes is
+ eventually transformed into a size_t in cpp, and I can not
+ guarantee that long->size_t would be possible. I thus keep
+ int. This means that we can create cursor of size up to 4GiB
+ while upstream can theoretically create cursor of size up to
+ 16 EiB. It is probably an acceptable restriction.*/
+ mStartPos = 0;
+ mWindowSizeBytes = windowSizeBytes;
+ mName = name != null && name.length() != 0 ? name : "";
+ mWindowPtr = nativeCreate(mName, windowSizeBytes);
+ if (mWindowPtr == 0) {
+ throw new CursorWindowAllocationException("Cursor window allocation of " +
+ (windowSizeBytes / 1024) + " kb failed. ");
+ }
+ }
+
+ @SuppressWarnings("ThrowFromFinallyBlock")
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void dispose() {
+ if (mWindowPtr != 0) {
+ nativeDispose(mWindowPtr);
+ mWindowPtr = 0;
+ }
+ }
+
+ /**
+ * Gets the name of this cursor window, never null.
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Clears out the existing contents of the window, making it safe to reuse
+ * for new data.
+ *
+ * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}),
+ * and number of columns in the cursor are all reset to zero.
+ *
+ */
+ public void clear() {
+ mStartPos = 0;
+ nativeClear(mWindowPtr);
+ }
+
+ /**
+ * Gets the start position of this cursor window.
+ *
+ * The start position is the zero-based index of the first row that this window contains
+ * relative to the entire result set of the {@link Cursor}.
+ *
+ *
+ * @return The zero-based start position.
+ */
+ public int getStartPosition() {
+ return mStartPos;
+ }
+
+ /**
+ * Sets the start position of this cursor window.
+ *
+ * The start position is the zero-based index of the first row that this window contains
+ * relative to the entire result set of the {@link Cursor}.
+ *
+ *
+ * @param pos The new zero-based start position.
+ */
+ public void setStartPosition(int pos) {
+ mStartPos = pos;
+ }
+
+ /**
+ * Gets the number of rows in this window.
+ *
+ * @return The number of rows in this cursor window.
+ */
+ public int getNumRows() {
+ return nativeGetNumRows(mWindowPtr);
+ }
+
+ /**
+ * Sets the number of columns in this window.
+ *
+ * This method must be called before any rows are added to the window, otherwise
+ * it will fail to set the number of columns if it differs from the current number
+ * of columns.
+ *
+ *
+ * @param columnNum The new number of columns.
+ * @return True if successful.
+ */
+ public boolean setNumColumns(int columnNum) {
+ return nativeSetNumColumns(mWindowPtr, columnNum);
+ }
+
+ /**
+ * Allocates a new row at the end of this cursor window.
+ *
+ * @return True if successful, false if the cursor window is out of memory.
+ */
+ public boolean allocRow(){
+ return nativeAllocRow(mWindowPtr);
+ }
+
+ /**
+ * Frees the last row in this cursor window.
+ */
+ public void freeLastRow(){
+ nativeFreeLastRow(mWindowPtr);
+ }
+
+ /**
+ * Returns the type of the field at the specified row and column index.
+ *
+ * The returned field types are:
+ *
+ * - {@link Cursor#FIELD_TYPE_NULL}
+ * - {@link Cursor#FIELD_TYPE_INTEGER}
+ * - {@link Cursor#FIELD_TYPE_FLOAT}
+ * - {@link Cursor#FIELD_TYPE_STRING}
+ * - {@link Cursor#FIELD_TYPE_BLOB}
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The field type.
+ */
+ public int getType(int row, int column) {
+ return nativeGetType(mWindowPtr, row - mStartPos, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a byte array.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
null
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result
+ * is the blob value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the array of bytes that make up the internal representation of the
+ * string value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or
+ * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a byte array.
+ */
+ public byte[] getBlob(int row, int column) {
+ return nativeGetBlob(mWindowPtr, row - mStartPos, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a string.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
null
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the string value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is a string representation of the integer in decimal, obtained by formatting the
+ * value with the
printf
family of functions using
+ * format specifier %lld
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is a string representation of the floating-point value in decimal, obtained by
+ * formatting the value with the
printf
family of functions using
+ * format specifier %g
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a string.
+ */
+ public String getString(int row, int column) {
+ return nativeGetString(mWindowPtr, row - mStartPos, column);
+ }
+
+ /**
+ * Copies the text of the field at the specified row and column index into
+ * a {@link CharArrayBuffer}.
+ *
+ * The buffer is populated as follows:
+ *
+ * - If the buffer is too small for the value to be copied, then it is
+ * automatically resized.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer
+ * is set to an empty string.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer
+ * is set to the contents of the string.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer
+ * is set to a string representation of the integer in decimal, obtained by formatting the
+ * value with the
printf
family of functions using
+ * format specifier %lld
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is
+ * set to a string representation of the floating-point value in decimal, obtained by
+ * formatting the value with the
printf
family of functions using
+ * format specifier %g
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically
+ * resized if the requested string is larger than the buffer's current capacity.
+ */
+ public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) {
+ if (buffer == null) {
+ throw new IllegalArgumentException("CharArrayBuffer should not be null");
+ }
+ // TODO not as optimal as the original code
+ char[] chars = getString(row, column).toCharArray();
+ buffer.data = chars;
+ buffer.sizeCopied = chars.length;
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a long
.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
0L
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the value obtained by parsing the string value with
strtoll
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is the
long
value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is the floating-point value converted to a
long
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a long
.
+ */
+ public long getLong(int row, int column) {
+ return nativeGetLong(mWindowPtr, row - mStartPos, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a
+ * double
.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
0.0
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the value obtained by parsing the string value with
strtod
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is the integer value converted to a
double
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is the
double
value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a double
.
+ */
+ public double getDouble(int row, int column) {
+ return nativeGetDouble(mWindowPtr, row - mStartPos, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a
+ * short
.
+ *
+ * The result is determined by invoking {@link #getLong} and converting the
+ * result to short
.
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a short
.
+ */
+ public short getShort(int row, int column) {
+ return (short) getLong(row, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as an
+ * int
.
+ *
+ * The result is determined by invoking {@link #getLong} and converting the
+ * result to int
.
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as an int
.
+ */
+ public int getInt(int row, int column) {
+ return (int) getLong(row, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a
+ * float
.
+ *
+ * The result is determined by invoking {@link #getDouble} and converting the
+ * result to float
.
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as an float
.
+ */
+ public float getFloat(int row, int column) {
+ return (float) getDouble(row, column);
+ }
+
+ /**
+ * Copies a byte array into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putBlob(byte[] value, int row, int column) {
+ return nativePutBlob(mWindowPtr, value, row - mStartPos, column);
+ }
+
+ /**
+ * Copies a string into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putString(String value, int row, int column) {
+ return nativePutString(mWindowPtr, value, row - mStartPos, column);
+ }
+
+ /**
+ * Puts a long integer into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putLong(long value, int row, int column) {
+ return nativePutLong(mWindowPtr, value, row - mStartPos, column);
+ }
+
+ /**
+ * Puts a double-precision floating point value into the field at the
+ * specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putDouble(double value, int row, int column) {
+ return nativePutDouble(mWindowPtr, value, row - mStartPos, column);
+ }
+
+ /**
+ * Puts a null value into the field at the specified row and column index.
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putNull(int row, int column) {
+ return nativePutNull(mWindowPtr, row - mStartPos, column);
+ }
+
+ public boolean isNull(int row, int column) {
+ return getType(row, column) == Cursor.FIELD_TYPE_NULL;
+ }
+
+ public boolean isBlob(int row, int column) {
+ int type = getType(row, column);
+ return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL;
+ }
+
+ @Override
+ protected void onAllReferencesReleased() {
+ dispose();
+ }
+
+ @Override
+ public String toString() {
+ return getName() + " {" + Long.toHexString(mWindowPtr) + "}";
+ }
+
+ public int getWindowSizeBytes() {
+ return mWindowSizeBytes;
+ }
+}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/CursorWindowAllocationException.java b/sqlcipher/src/main/java/net/zetetic/database/CursorWindowAllocationException.java
new file mode 100644
index 0000000..7c2baef
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/CursorWindowAllocationException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.zetetic.database;
+
+/**
+ * This exception is thrown when a CursorWindow couldn't be allocated,
+ * most probably due to memory not being available.
+ *
+ * @hide
+ */
+public class CursorWindowAllocationException extends RuntimeException {
+ public CursorWindowAllocationException(String description) {
+ super(description);
+ }
+}
+
diff --git a/sqlcipher/src/main/java/net/zetetic/database/DatabaseErrorHandler.java b/sqlcipher/src/main/java/net/zetetic/database/DatabaseErrorHandler.java
index 276fc65..2c049ea 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/DatabaseErrorHandler.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/DatabaseErrorHandler.java
@@ -21,6 +21,8 @@
package net.zetetic.database;
+import android.database.sqlite.SQLiteException;
+
import net.zetetic.database.sqlcipher.SQLiteDatabase;
/**
@@ -32,6 +34,7 @@ public interface DatabaseErrorHandler {
* The method invoked when database corruption is detected.
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
* is detected.
+ * @param exception the exception reported by sqlite that indicated the database was corrupted.
*/
- void onCorruption(SQLiteDatabase dbObj);
+ void onCorruption(SQLiteDatabase dbObj, SQLiteException exception);
}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/DatabaseUtils.java b/sqlcipher/src/main/java/net/zetetic/database/DatabaseUtils.java
index 41358ae..4fc618a 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/DatabaseUtils.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/DatabaseUtils.java
@@ -24,7 +24,6 @@
import net.zetetic.database.sqlcipher.SQLiteProgram;
import net.zetetic.database.sqlcipher.SQLiteStatement;
-import android.database.CursorWindow;
import android.database.SQLException;
import android.database.sqlite.SQLiteAbortException;
import android.database.sqlite.SQLiteConstraintException;
@@ -36,7 +35,6 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
-import android.util.Log;
import android.database.Cursor;
@@ -115,14 +113,14 @@ public static final void writeExceptionToParcel(Parcel reply, Exception e) {
logException = false;
} else {
reply.writeException(e);
- Log.e(TAG, "Writing exception to parcel", e);
+ Logger.e(TAG, "Writing exception to parcel", e);
return;
}
reply.writeInt(code);
reply.writeString(e.getMessage());
if (logException) {
- Log.e(TAG, "Writing exception to parcel", e);
+ Logger.e(TAG, "Writing exception to parcel", e);
}
}
@@ -1081,7 +1079,7 @@ private void buildSQL() throws SQLException {
sb.append(sbv);
mInsertSQL = sb.toString();
- if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL);
+ if (DEBUG) Logger.v(TAG, "insert statement is " + mInsertSQL);
}
private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
@@ -1125,13 +1123,13 @@ private long insertInternal(ContentValues values, boolean allowReplace) {
try {
SQLiteStatement stmt = getStatement(allowReplace);
stmt.clearBindings();
- if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName);
+ if (DEBUG) Logger.v(TAG, "--- inserting in table " + mTableName);
for (Map.Entry e: values.valueSet()) {
final String key = e.getKey();
int i = getColumnIndex(key);
DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
if (DEBUG) {
- Log.v(TAG, "binding " + e.getValue() + " to column " +
+ Logger.v(TAG, "binding " + e.getValue() + " to column " +
i + " (" + key + ")");
}
}
@@ -1139,7 +1137,7 @@ private long insertInternal(ContentValues values, boolean allowReplace) {
mDb.setTransactionSuccessful();
return result;
} catch (SQLException e) {
- Log.e(TAG, "Error inserting " + values + " into table " + mTableName, e);
+ Logger.e(TAG, "Error inserting " + values + " into table " + mTableName, e);
return -1;
} finally {
mDb.endTransaction();
@@ -1279,10 +1277,10 @@ public long execute() {
+ "execute");
}
try {
- if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
+ if (DEBUG) Logger.v(TAG, "--- doing insert or replace in table " + mTableName);
return mPreparedStatement.executeInsert();
} catch (SQLException e) {
- Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
+ Logger.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
return -1;
} finally {
// you can only call this once per prepare
diff --git a/sqlcipher/src/main/java/net/zetetic/database/DefaultDatabaseErrorHandler.java b/sqlcipher/src/main/java/net/zetetic/database/DefaultDatabaseErrorHandler.java
index b79bf73..95db892 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/DefaultDatabaseErrorHandler.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/DefaultDatabaseErrorHandler.java
@@ -26,7 +26,6 @@
import net.zetetic.database.sqlcipher.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
-import android.util.Log;
import android.util.Pair;
/**
@@ -56,8 +55,8 @@ public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler {
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
* is detected.
*/
- public void onCorruption(SQLiteDatabase dbObj) {
- Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
+ public void onCorruption(SQLiteDatabase dbObj, SQLiteException exception) {
+ Logger.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
// If this is a SEE build, do not delete any database files.
// It may be that the user has specified an incorrect password.
@@ -107,12 +106,12 @@ private void deleteDatabaseFile(String fileName) {
if (fileName.equalsIgnoreCase(":memory:") || fileName.trim().length() == 0) {
return;
}
- Log.e(TAG, "deleting the database file: " + fileName);
+ Logger.e(TAG, "deleting the database file: " + fileName);
try {
SQLiteDatabase.deleteDatabase(new File(fileName));
} catch (Exception e) {
/* print warning and ignore exception */
- Log.w(TAG, "delete failed: " + e.getMessage());
+ Logger.w(TAG, "delete failed: " + e.getMessage());
}
}
}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/LogTarget.java b/sqlcipher/src/main/java/net/zetetic/database/LogTarget.java
new file mode 100644
index 0000000..ddbf6ad
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/LogTarget.java
@@ -0,0 +1,6 @@
+package net.zetetic.database;
+
+public interface LogTarget {
+ boolean isLoggable (String tag, int priority);
+ void log(int priority, String tag, String message, Throwable throwable);
+}
\ No newline at end of file
diff --git a/sqlcipher/src/main/java/net/zetetic/database/LogcatTarget.java b/sqlcipher/src/main/java/net/zetetic/database/LogcatTarget.java
new file mode 100644
index 0000000..33a40e8
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/LogcatTarget.java
@@ -0,0 +1,22 @@
+package net.zetetic.database;
+
+import android.util.Log;
+
+public class LogcatTarget implements LogTarget {
+
+ @Override
+ public boolean isLoggable(String tag, int priority) {
+ return Log.isLoggable(tag, priority);
+ }
+
+ public void log(int priority, String tag, String message, Throwable throwable){
+ switch (priority){
+ case Logger.VERBOSE -> Log.v(tag, message, throwable);
+ case Logger.DEBUG -> Log.d(tag, message, throwable);
+ case Logger.INFO -> Log.i(tag, message, throwable);
+ case Logger.WARN -> Log.w(tag, message, throwable);
+ case Logger.ERROR -> Log.e(tag, message, throwable);
+ case Logger.ASSERT -> Log.wtf(tag, message, throwable);
+ }
+ }
+}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/Logger.java b/sqlcipher/src/main/java/net/zetetic/database/Logger.java
new file mode 100644
index 0000000..94673c0
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/Logger.java
@@ -0,0 +1,80 @@
+package net.zetetic.database;
+
+public class Logger {
+
+ static {
+ setTarget(new LogcatTarget());
+ }
+
+ public static final int VERBOSE = 2;
+ public static final int DEBUG = 3;
+ public static final int INFO = 4;
+ public static final int WARN = 5;
+ public static final int ERROR = 6;
+ public static final int ASSERT = 7;
+
+ private static LogTarget target;
+
+ public static void setTarget(LogTarget target){
+ Logger.target = target;
+ }
+
+ private static LogTarget getTarget(){
+ if(Logger.target == null){
+ setTarget(new NoopTarget());
+ }
+ return Logger.target;
+ }
+
+ public static boolean isLoggable(String tag, int priority){
+ return getTarget().isLoggable(tag, priority);
+ }
+
+ public static void i(String tag, String message) {
+ getTarget().log(Logger.INFO, tag, message, null);
+ }
+
+ public static void i(String tag, String message, Throwable throwable) {
+ getTarget().log(Logger.INFO, tag, message, throwable);
+ }
+
+ public static void d(String tag, String message) {
+ getTarget().log(Logger.DEBUG, tag, message, null);
+ }
+
+ public static void d(String tag, String message, Throwable throwable) {
+ getTarget().log(Logger.DEBUG, tag, message, throwable);
+ }
+
+ public static void e(String tag, String message) {
+ getTarget().log(Logger.ERROR, tag, message, null);
+ }
+
+ public static void e(String tag, String message, Throwable throwable) {
+ getTarget().log(Logger.ERROR, tag, message, throwable);
+ }
+
+ public static void v(String tag, String message) {
+ getTarget().log(Logger.VERBOSE, tag, message, null);
+ }
+
+ public static void v(String tag, String message, Throwable throwable) {
+ getTarget().log(Logger.VERBOSE, tag, message, throwable);
+ }
+
+ public static void w(String tag, String message) {
+ getTarget().log(Logger.WARN, tag, message, null);
+ }
+
+ public static void w(String tag, String message, Throwable throwable) {
+ getTarget().log(Logger.WARN, tag, message, throwable);
+ }
+
+ public static void wtf(String tag, String message) {
+ getTarget().log(Logger.ASSERT, tag, message, null);
+ }
+
+ public static void wtf(String tag, String message, Throwable throwable) {
+ getTarget().log(Logger.ASSERT, tag, message, throwable);
+ }
+}
\ No newline at end of file
diff --git a/sqlcipher/src/main/java/net/zetetic/database/MatrixCursor.java b/sqlcipher/src/main/java/net/zetetic/database/MatrixCursor.java
new file mode 100644
index 0000000..0048692
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/MatrixCursor.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.zetetic.database;
+
+import android.database.CursorIndexOutOfBoundsException;
+
+import java.util.ArrayList;
+
+
+/**
+ * A mutable cursor implementation backed by an array of {@code Object}s. Use
+ * {@link #newRow()} to add rows. Automatically expands internal capacity
+ * as needed.
+ */
+public class MatrixCursor extends AbstractCursor {
+
+ private final String[] columnNames;
+ private Object[] data;
+ private int rowCount = 0;
+ private final int columnCount;
+
+ /**
+ * Constructs a new cursor with the given initial capacity.
+ *
+ * @param columnNames names of the columns, the ordering of which
+ * determines column ordering elsewhere in this cursor
+ * @param initialCapacity in rows
+ */
+ public MatrixCursor(String[] columnNames, int initialCapacity) {
+ this.columnNames = columnNames;
+ this.columnCount = columnNames.length;
+
+ if (initialCapacity < 1) {
+ initialCapacity = 1;
+ }
+
+ this.data = new Object[columnCount * initialCapacity];
+ }
+
+ /**
+ * Constructs a new cursor.
+ *
+ * @param columnNames names of the columns, the ordering of which
+ * determines column ordering elsewhere in this cursor
+ */
+ public MatrixCursor(String[] columnNames) {
+ this(columnNames, 16);
+ }
+
+ /**
+ * Gets value at the given column for the current row.
+ */
+ private Object get(int column) {
+ if (column < 0 || column >= columnCount) {
+ throw new CursorIndexOutOfBoundsException("Requested column: "
+ + column + ", # of columns: " + columnCount);
+ }
+ if (mPos < 0) {
+ throw new CursorIndexOutOfBoundsException("Before first row.");
+ }
+ if (mPos >= rowCount) {
+ throw new CursorIndexOutOfBoundsException("After last row.");
+ }
+ return data[mPos * columnCount + column];
+ }
+
+ /**
+ * Adds a new row to the end and returns a builder for that row. Not safe
+ * for concurrent use.
+ *
+ * @return builder which can be used to set the column values for the new
+ * row
+ */
+ public RowBuilder newRow() {
+ rowCount++;
+ int endIndex = rowCount * columnCount;
+ ensureCapacity(endIndex);
+ int start = endIndex - columnCount;
+ return new RowBuilder(start, endIndex);
+ }
+
+ /**
+ * Adds a new row to the end with the given column values. Not safe
+ * for concurrent use.
+ *
+ * @throws IllegalArgumentException if {@code columnValues.length !=
+ * columnNames.length}
+ * @param columnValues in the same order as the the column names specified
+ * at cursor construction time
+ */
+ public void addRow(Object[] columnValues) {
+ if (columnValues.length != columnCount) {
+ throw new IllegalArgumentException("columnNames.length = "
+ + columnCount + ", columnValues.length = "
+ + columnValues.length);
+ }
+
+ int start = rowCount++ * columnCount;
+ ensureCapacity(start + columnCount);
+ System.arraycopy(columnValues, 0, data, start, columnCount);
+ }
+
+ /**
+ * Adds a new row to the end with the given column values. Not safe
+ * for concurrent use.
+ *
+ * @throws IllegalArgumentException if {@code columnValues.size() !=
+ * columnNames.length}
+ * @param columnValues in the same order as the the column names specified
+ * at cursor construction time
+ */
+ public void addRow(Iterable> columnValues) {
+ int start = rowCount * columnCount;
+ int end = start + columnCount;
+ ensureCapacity(end);
+
+ if (columnValues instanceof ArrayList>) {
+ addRow((ArrayList>) columnValues, start);
+ return;
+ }
+
+ int current = start;
+ Object[] localData = data;
+ for (Object columnValue : columnValues) {
+ if (current == end) {
+ // TODO: null out row?
+ throw new IllegalArgumentException(
+ "columnValues.size() > columnNames.length");
+ }
+ localData[current++] = columnValue;
+ }
+
+ if (current != end) {
+ // TODO: null out row?
+ throw new IllegalArgumentException(
+ "columnValues.size() < columnNames.length");
+ }
+
+ // Increase row count here in case we encounter an exception.
+ rowCount++;
+ }
+
+ /** Optimization for {@link ArrayList}. */
+ private void addRow(ArrayList> columnValues, int start) {
+ int size = columnValues.size();
+ if (size != columnCount) {
+ throw new IllegalArgumentException("columnNames.length = "
+ + columnCount + ", columnValues.size() = " + size);
+ }
+
+ rowCount++;
+ Object[] localData = data;
+ for (int i = 0; i < size; i++) {
+ localData[start + i] = columnValues.get(i);
+ }
+ }
+
+ /** Ensures that this cursor has enough capacity. */
+ private void ensureCapacity(int size) {
+ if (size > data.length) {
+ Object[] oldData = this.data;
+ int newSize = data.length * 2;
+ if (newSize < size) {
+ newSize = size;
+ }
+ this.data = new Object[newSize];
+ System.arraycopy(oldData, 0, this.data, 0, oldData.length);
+ }
+ }
+
+ public void fillWindow(int position, CursorWindow window) {
+ DatabaseUtils.cursorFillWindow(this, position, window);
+ }
+
+ /**
+ * Builds a row, starting from the left-most column and adding one column
+ * value at a time. Follows the same ordering as the column names specified
+ * at cursor construction time.
+ */
+ public class RowBuilder {
+
+ private int index;
+ private final int endIndex;
+
+ RowBuilder(int index, int endIndex) {
+ this.index = index;
+ this.endIndex = endIndex;
+ }
+
+ /**
+ * Sets the next column value in this row.
+ *
+ * @throws CursorIndexOutOfBoundsException if you try to add too many
+ * values
+ * @return this builder to support chaining
+ */
+ public RowBuilder add(Object columnValue) {
+ if (index == endIndex) {
+ throw new CursorIndexOutOfBoundsException(
+ "No more columns left.");
+ }
+
+ data[index++] = columnValue;
+ return this;
+ }
+ }
+
+ // AbstractCursor implementation.
+
+ @Override
+ public int getCount() {
+ return rowCount;
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return columnNames;
+ }
+
+ @Override
+ public String getString(int column) {
+ Object value = get(column);
+ if (value == null) return null;
+ return value.toString();
+ }
+
+ @Override
+ public short getShort(int column) {
+ Object value = get(column);
+ if (value == null) return 0;
+ if (value instanceof Number) return ((Number) value).shortValue();
+ return Short.parseShort(value.toString());
+ }
+
+ @Override
+ public int getInt(int column) {
+ Object value = get(column);
+ if (value == null) return 0;
+ if (value instanceof Number) return ((Number) value).intValue();
+ return Integer.parseInt(value.toString());
+ }
+
+ @Override
+ public long getLong(int column) {
+ Object value = get(column);
+ if (value == null) return 0;
+ if (value instanceof Number) return ((Number) value).longValue();
+ return Long.parseLong(value.toString());
+ }
+
+ @Override
+ public float getFloat(int column) {
+ Object value = get(column);
+ if (value == null) return 0.0f;
+ if (value instanceof Number) return ((Number) value).floatValue();
+ return Float.parseFloat(value.toString());
+ }
+
+ @Override
+ public double getDouble(int column) {
+ Object value = get(column);
+ if (value == null) return 0.0d;
+ if (value instanceof Number) return ((Number) value).doubleValue();
+ return Double.parseDouble(value.toString());
+ }
+
+ @Override
+ public int getType(int column) {
+ return DatabaseUtils.getTypeOfObject(get(column));
+ }
+
+ @Override
+ public boolean onMove(int oldPosition, int newPosition) {
+ return true;
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ return get(column) == null;
+ }
+
+
+}
\ No newline at end of file
diff --git a/sqlcipher/src/main/java/net/zetetic/database/NoopTarget.java b/sqlcipher/src/main/java/net/zetetic/database/NoopTarget.java
new file mode 100644
index 0000000..534dfd6
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/NoopTarget.java
@@ -0,0 +1,12 @@
+package net.zetetic.database;
+
+public class NoopTarget implements LogTarget {
+
+ @Override
+ public boolean isLoggable(String tag, int priority) {
+ return false;
+ }
+
+ @Override
+ public void log(int priority, String tag, String message, Throwable throwable) {}
+}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/CloseGuard.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/CloseGuard.java
index 93aabc2..a3e1789 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/CloseGuard.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/CloseGuard.java
@@ -19,7 +19,8 @@
*/
package net.zetetic.database.sqlcipher;
-import android.util.Log;
+
+import net.zetetic.database.Logger;
/**
* CloseGuard is a mechanism for flagging implicit finalizer cleanup of
@@ -229,7 +230,7 @@ public static interface Reporter {
*/
private static final class DefaultReporter implements Reporter {
@Override public void report (String message, Throwable allocationSite) {
- Log.w(message, allocationSite);
+ android.util.Log.w(message, allocationSite);
}
}
}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnection.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnection.java
index e17f6b4..130530a 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnection.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnection.java
@@ -21,7 +21,6 @@
package net.zetetic.database.sqlcipher;
import android.database.Cursor;
-import android.database.CursorWindow;
import android.database.sqlite.SQLiteBindOrColumnIndexOutOfRangeException;
import android.database.sqlite.SQLiteDatabaseLockedException;
import android.database.sqlite.SQLiteException;
@@ -29,11 +28,12 @@
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
-import android.util.Log;
import android.util.LruCache;
import android.util.Printer;
+import net.zetetic.database.CursorWindow;
import net.zetetic.database.DatabaseUtils;
+import net.zetetic.database.Logger;
import net.zetetic.database.sqlcipher.SQLiteDebug.DbStats;
import java.text.SimpleDateFormat;
@@ -157,8 +157,8 @@ private static native int nativeExecuteForBlobFileDescriptor(
private static native long nativeExecuteForLastInsertedRowId(
long connectionPtr, long statementPtr);
private static native long nativeExecuteForCursorWindow(
- long connectionPtr, long statementPtr, CursorWindow win,
- int startPos, int requiredPos, boolean countAllRows);
+ long connectionPtr, long statementPtr, long winPtr,
+ int startPos, int requiredPos, boolean countAllRows);
private static native int nativeGetDbLookaside(long connectionPtr);
private static native void nativeCancel(long connectionPtr);
private static native void nativeResetCancel(long connectionPtr, boolean cancelable);
@@ -216,7 +216,7 @@ void close() {
void changePassword(byte[] newPassword){
int result = nativeReKey(mConnectionPtr, newPassword);
- Log.i(TAG, String.format("Database rekey operation returned:%s", result));
+ Logger.i(TAG, String.format("Database rekey operation returned:%s", result));
if(result != 0){
throw new SQLiteException(String.format("Failed to rekey database, result code:%s", result));
}
@@ -231,7 +231,7 @@ private void open() {
}
if(mConfiguration.password != null && mConfiguration.password.length > 0){
int rc = nativeKey(mConnectionPtr, mConfiguration.password);
- Log.i(TAG, String.format("Database keying operation returned:%s", rc));
+ Logger.i(TAG, String.format("Database keying operation returned:%s", rc));
}
if(mConfiguration.databaseHook != null){
mConfiguration.databaseHook.postKey(this);
@@ -372,7 +372,7 @@ private void setJournalMode(String newValue) {
// If we don't change the journal mode, nothing really bad happens.
// In the worst case, an application that enables WAL might not actually
// get it, although it can still use connection pooling.
- Log.w(TAG, "Could not change the database journal mode of '"
+ Logger.w(TAG, "Could not change the database journal mode of '"
+ mConfiguration.label + "' from '" + value + "' to '" + newValue
+ "' because the database is locked. This usually means that "
+ "there are other open connections to the database which prevents "
@@ -927,7 +927,7 @@ public int executeForCursorWindow(String sql, Object[] bindArgs,
attachCancellationSignal(cancellationSignal);
try {
final long result = nativeExecuteForCursorWindow(
- mConnectionPtr, statement.mStatementPtr, window,
+ mConnectionPtr, statement.mStatementPtr, window.mWindowPtr,
startPos, requiredPos, countAllRows);
actualPos = (int)(result >> 32);
countedRows = (int)result;
@@ -1003,7 +1003,7 @@ private void releasePreparedStatement(PreparedStatement statement) {
// which will in turn call finalizePreparedStatement() to finalize and
// recycle the statement.
if (DEBUG) {
- Log.d(TAG, "Could not reset prepared statement due to an exception. "
+ Logger.d(TAG, "Could not reset prepared statement due to an exception. "
+ "Removing it from the cache. SQL: "
+ trimSqlForDisplay(statement.mSql), ex);
}
@@ -1462,7 +1462,7 @@ private void logOperationLocked(int cookie, String detail) {
if (detail != null) {
msg.append(", ").append(detail);
}
- Log.d(TAG, msg.toString());
+ Logger.d(TAG, msg.toString());
}
private int newOperationCookieLocked(int index) {
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java
index 0750189..98960a6 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteConnectionPool.java
@@ -20,13 +20,12 @@
package net.zetetic.database.sqlcipher;
-import net.zetetic.database.sqlcipher.CloseGuard;
+import net.zetetic.database.Logger;
import net.zetetic.database.sqlcipher.SQLiteDebug.DbStats;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.SystemClock;
-import android.util.Log;
import android.util.Printer;
import java.io.Closeable;
@@ -231,7 +230,7 @@ private void dispose(boolean finalized) {
final int pendingCount = mAcquiredConnections.size();
if (pendingCount != 0) {
- Log.i(TAG, "The connection pool for " + mConfiguration.label
+ Logger.i(TAG, "The connection pool for " + mConfiguration.label
+ " has been closed but there are still "
+ pendingCount + " connections in use. They will be closed "
+ "as they are released back to the pool.");
@@ -407,7 +406,7 @@ private boolean recycleConnectionLocked(SQLiteConnection connection,
try {
connection.reconfigure(mConfiguration); // might throw
} catch (RuntimeException ex) {
- Log.e(TAG, "Failed to reconfigure released connection, closing it: "
+ Logger.e(TAG, "Failed to reconfigure released connection, closing it: "
+ connection, ex);
status = AcquiredConnectionStatus.DISCARD;
}
@@ -497,7 +496,7 @@ void onConnectionLeaked() {
// several seconds while waiting for a leaked connection to be detected and recreated,
// then perhaps its authors will have added incentive to fix the problem!
- Log.w(TAG, "A SQLiteConnection object for database '"
+ Logger.w(TAG, "A SQLiteConnection object for database '"
+ mConfiguration.label + "' was leaked! Please fix your application "
+ "to end transactions in progress properly and to close the database "
+ "when it is no longer needed.");
@@ -539,7 +538,7 @@ private void closeConnectionAndLogExceptionsLocked(SQLiteConnection connection)
try {
connection.close(); // might throw
} catch (RuntimeException ex) {
- Log.e(TAG, "Failed to close connection, its fate is now in the hands "
+ Logger.e(TAG, "Failed to close connection, its fate is now in the hands "
+ "of the merciful GC: " + connection, ex);
}
}
@@ -555,7 +554,7 @@ private void reconfigureAllConnectionsLocked() {
try {
mAvailablePrimaryConnection.reconfigure(mConfiguration); // might throw
} catch (RuntimeException ex) {
- Log.e(TAG, "Failed to reconfigure available primary connection, closing it: "
+ Logger.e(TAG, "Failed to reconfigure available primary connection, closing it: "
+ mAvailablePrimaryConnection, ex);
closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection);
mAvailablePrimaryConnection = null;
@@ -568,7 +567,7 @@ private void reconfigureAllConnectionsLocked() {
try {
connection.reconfigure(mConfiguration); // might throw
} catch (RuntimeException ex) {
- Log.e(TAG, "Failed to reconfigure available non-primary connection, closing it: "
+ Logger.e(TAG, "Failed to reconfigure available non-primary connection, closing it: "
+ connection, ex);
closeConnectionAndLogExceptionsLocked(connection);
mAvailableNonPrimaryConnections.remove(i--);
@@ -617,15 +616,27 @@ private SQLiteConnection waitForConnection(String sql, int connectionFlags,
// Try to acquire a connection.
SQLiteConnection connection = null;
- if (!wantPrimaryConnection) {
- connection = tryAcquireNonPrimaryConnectionLocked(
- sql, connectionFlags); // might throw
- }
- if (connection == null) {
- connection = tryAcquirePrimaryConnectionLocked(connectionFlags); // might throw
+
+ // When in WAL mode, we want to avoid the startup penalty of creating both a primary and
+ // non-primary connection for initial read operations on the database. If the pool of non-primary
+ // connections has not been initialized yet, and the primary connection is available, use the primary.
+ // If the primary connection is required, always use that.
+ if ((mAvailablePrimaryConnection != null && mAvailableNonPrimaryConnections.isEmpty()) || wantPrimaryConnection) {
+ connection = tryAcquirePrimaryConnectionLocked(connectionFlags);
+ if (connection != null) {
+ return connection;
+ }
}
- if (connection != null) {
- return connection;
+
+ // If a primary connection is not required by the caller, and either the non-primary connection pool
+ // has already been established or the primary connection is not available, then try to get
+ // non primary connection. This may establish a new non-primary connection a free one is not
+ // already available.
+ if (!wantPrimaryConnection) {
+ connection = tryAcquireNonPrimaryConnectionLocked(sql, connectionFlags);
+ if (connection != null) {
+ return connection;
+ }
}
// No connections available. Enqueue a waiter in priority order.
@@ -785,7 +796,7 @@ private void logConnectionPoolBusyLocked(long waitMillis, int connectionFlags) {
}
}
- Log.w(TAG, msg.toString());
+ Logger.w(TAG, msg.toString());
}
// Can't throw.
@@ -922,7 +933,7 @@ private void finishAcquireConnectionLocked(SQLiteConnection connection, int conn
mAcquiredConnections.put(connection, AcquiredConnectionStatus.NORMAL);
} catch (RuntimeException ex) {
- Log.e(TAG, "Failed to prepare acquired connection for session, closing it: "
+ Logger.e(TAG, "Failed to prepare acquired connection for session, closing it: "
+ connection +", connectionFlags=" + connectionFlags);
closeConnectionAndLogExceptionsLocked(connection);
throw ex; // rethrow!
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteCursor.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteCursor.java
index a04bbd9..4b9454b 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteCursor.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteCursor.java
@@ -20,15 +20,12 @@
package net.zetetic.database.sqlcipher;
-import android.annotation.SuppressLint;
-import android.database.AbstractWindowedCursor;
-import android.database.CursorWindow;
-import net.zetetic.database.DatabaseUtils;
-import android.os.Build;
-import android.util.Log;
+import net.zetetic.database.AbstractWindowedCursor;
+import net.zetetic.database.CursorWindow;
+import net.zetetic.database.DatabaseUtils;
+import net.zetetic.database.Logger;
-import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
@@ -42,9 +39,8 @@
public class SQLiteCursor extends AbstractWindowedCursor {
static final String TAG = "SQLiteCursor";
static final int NO_COUNT = -1;
- private static final int CURSOR_WINDOW_EXTRA = 512;
private static boolean CURSOR_WINDOW_NEEDS_RECREATED = false;
- private static final int DEFAULT_CURSOR_WINDOW_SIZE = (int)(8 * Math.pow(1024, 2));
+ private static final int DEFAULT_CURSOR_WINDOW_SIZE = -1;
public static int PREFERRED_CURSOR_WINDOW_SIZE = DEFAULT_CURSOR_WINDOW_SIZE;
/** The name of the table to edit */
@@ -138,45 +134,24 @@ public int getCount() {
}
public static void setCursorWindowSize(int size) {
- PREFERRED_CURSOR_WINDOW_SIZE = size;
+ CursorWindow.PREFERRED_CURSOR_WINDOW_SIZE = size;
CURSOR_WINDOW_NEEDS_RECREATED = true;
}
public static void resetCursorWindowSize() {
- PREFERRED_CURSOR_WINDOW_SIZE = DEFAULT_CURSOR_WINDOW_SIZE;
+ CursorWindow.PREFERRED_CURSOR_WINDOW_SIZE = CursorWindow.DEFAULT_CURSOR_WINDOW_SIZE;
CURSOR_WINDOW_NEEDS_RECREATED = true;
}
- /*
- ** The AbstractWindowClass contains protected methods clearOrCreateWindow() and
- ** closeWindow(), which are used by the android.database.sqlite.* version of this
- ** class. But, since they are marked with "@hide", the following replacement
- ** versions are required.
- */
private void awc_clearOrCreateWindow(String name) {
- int cursorWindowAllocationSize = PREFERRED_CURSOR_WINDOW_SIZE + CURSOR_WINDOW_EXTRA;
+ int cursorWindowAllocationSize = CursorWindow.PREFERRED_CURSOR_WINDOW_SIZE;
if (CURSOR_WINDOW_NEEDS_RECREATED) {
awc_closeWindow();
CURSOR_WINDOW_NEEDS_RECREATED = false;
}
CursorWindow win = getWindow();
if ( win==null ) {
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- win = new CursorWindow(name, cursorWindowAllocationSize);
- } else {
- try {
- @SuppressLint("DiscouragedPrivateApi")
- Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
- if (field != null) {
- field.setAccessible(true);
- field.set(null, cursorWindowAllocationSize);
- Log.i(TAG, String.format("Set CursorWindow allocation size to %s", cursorWindowAllocationSize));
- }
- } catch (Exception ex) {
- Log.e(TAG, "Failed to override CursorWindow allocation size", ex);
- }
- win = new CursorWindow(name);
- }
+ win = new CursorWindow(name, cursorWindowAllocationSize);
setWindow(win);
}else{
win.clear();
@@ -195,8 +170,8 @@ private void fillWindow(int requiredPos) {
int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos, 0);
mCount = mQuery.fillWindow(mWindow, startPos, requiredPos, true);
mCursorWindowCapacity = mWindow.getNumRows();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "received count(*) from native_fill_window: " + mCount);
+ if (Logger.isLoggable(TAG, Logger.DEBUG)) {
+ Logger.d(TAG, "received count(*) from native_fill_window: " + mCount);
}
} else {
int startPos = DatabaseUtils.cursorPickFillWindowStartPosition(requiredPos,
@@ -230,7 +205,7 @@ public int getColumnIndex(String columnName) {
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
- Log.e(TAG, "requesting column name with table name -- " + columnName, e);
+ Logger.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
@@ -286,7 +261,7 @@ public boolean requery() {
return super.requery();
} catch (IllegalStateException e) {
// for backwards compatibility, just return false
- Log.w(TAG, "requery() failed " + e.getMessage(), e);
+ Logger.w(TAG, "requery() failed " + e.getMessage(), e);
return false;
}
}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java
index cb13085..1a26f71 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDatabase.java
@@ -25,6 +25,7 @@
import net.zetetic.database.DatabaseErrorHandler;
import net.zetetic.database.DatabaseUtils;
import net.zetetic.database.DefaultDatabaseErrorHandler;
+import net.zetetic.database.Logger;
import net.zetetic.database.sqlcipher.SQLiteDebug.DbStats;
import android.database.SQLException;
@@ -35,7 +36,6 @@
import android.os.OperationCanceledException;
import android.text.TextUtils;
import android.util.EventLog;
-import android.util.Log;
import android.util.Pair;
import android.util.Printer;
@@ -343,9 +343,9 @@ String getLabel() {
/**
* Sends a corruption message to the database error handler.
*/
- void onCorruption() {
+ void onCorruption(SQLiteException exception) {
EventLog.writeEvent(EVENT_DB_CORRUPT, getLabel());
- mErrorHandler.onCorruption(this);
+ mErrorHandler.onCorruption(this, exception);
}
/**
@@ -1012,11 +1012,11 @@ private void open() {
try {
openInner();
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
openInner();
}
} catch (SQLiteException ex) {
- Log.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
+ Logger.e(TAG, "Failed to open database '" + getLabel() + "'.", ex);
close();
throw ex;
}
@@ -1180,8 +1180,9 @@ public long insert(String table, int conflictAlgorithm, ContentValues values) th
@Override
public int delete(String table, String whereClause, Object[] whereArgs) {
- String[] args = new String[whereArgs.length];
- for(int index = 0; index < whereArgs.length; index++) {
+ int length = whereArgs == null ? 0 : whereArgs.length;
+ String[] args = new String[length];
+ for(int index = 0; index < length; index++) {
args[index] = whereArgs[index].toString();
}
return delete(table, whereClause, args);
@@ -1189,8 +1190,9 @@ public int delete(String table, String whereClause, Object[] whereArgs) {
@Override
public int update(String table, int conflictAlgorithm, ContentValues values, String whereClause, Object[] whereArgs) {
- String[] args = new String[whereArgs.length];
- for(int index = 0; index < whereArgs.length; index++) {
+ int length = whereArgs == null ? 0 : whereArgs.length;
+ String[] args = new String[length];
+ for(int index = 0; index < length; index++) {
args[index] = whereArgs[index].toString();
}
return updateWithOnConflict(table, values, whereClause, args, conflictAlgorithm);
@@ -1634,7 +1636,7 @@ 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", e);
+ Logger.e(TAG, "Error inserting", e);
return -1;
}
}
@@ -1682,7 +1684,7 @@ public long replace(String table, String nullColumnHack, ContentValues initialVa
return insertWithOnConflict(table, nullColumnHack, initialValues,
CONFLICT_REPLACE);
} catch (SQLException e) {
- Log.e(TAG, "Error inserting", e);
+ Logger.e(TAG, "Error inserting", e);
return -1;
}
}
@@ -2270,15 +2272,15 @@ public boolean enableWriteAheadLogging() {
}
if (mConfigurationLocked.isInMemoryDb()) {
- Log.i(TAG, "can't enable WAL for memory databases.");
+ Logger.i(TAG, "can't enable WAL for memory databases.");
return false;
}
// make sure this database has NO attached databases because sqlite's write-ahead-logging
// doesn't work for databases with attached databases
if (mHasAttachedDbsLocked) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "this database: " + mConfigurationLocked.label
+ if (Logger.isLoggable(TAG, Logger.DEBUG)) {
+ Logger.d(TAG, "this database: " + mConfigurationLocked.label
+ " has attached databases. can't enable WAL.");
}
return false;
@@ -2477,7 +2479,7 @@ public boolean isDatabaseIntegrityOk() {
String rslt = prog.simpleQueryForString();
if (!rslt.equalsIgnoreCase("ok")) {
// integrity_checker failed on main or attached databases
- Log.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt);
+ Logger.e(TAG, "PRAGMA integrity_check on " + p.second + " returned: " + rslt);
return false;
}
} finally {
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDebug.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDebug.java
index 8184e01..4ea08df 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDebug.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteDebug.java
@@ -22,11 +22,10 @@
import java.util.ArrayList;
-import android.os.Build;
-/* import android.os.SystemProperties; */
-import android.util.Log;
import android.util.Printer;
+import net.zetetic.database.Logger;
+
/**
* Provides debugging info about all SQLite databases running in the current process.
*
@@ -41,7 +40,7 @@ public final class SQLiteDebug {
* Enable using "adb shell setprop log.tag.SQLiteLog VERBOSE".
*/
public static final boolean DEBUG_SQL_LOG =
- Log.isLoggable("SQLiteLog", Log.VERBOSE);
+ Logger.isLoggable("SQLiteLog", Logger.VERBOSE);
/**
* Controls the printing of SQL statements as they are executed.
@@ -49,7 +48,7 @@ public final class SQLiteDebug {
* Enable using "adb shell setprop log.tag.SQLiteStatements VERBOSE".
*/
public static final boolean DEBUG_SQL_STATEMENTS =
- Log.isLoggable("SQLiteStatements", Log.VERBOSE);
+ Logger.isLoggable("SQLiteStatements", Logger.VERBOSE);
/**
* Controls the printing of wall-clock time taken to execute SQL statements
@@ -58,7 +57,7 @@ public final class SQLiteDebug {
* Enable using "adb shell setprop log.tag.SQLiteTime VERBOSE".
*/
public static final boolean DEBUG_SQL_TIME =
- Log.isLoggable("SQLiteTime", Log.VERBOSE);
+ Logger.isLoggable("SQLiteTime", Logger.VERBOSE);
/**
* True to enable database performance testing instrumentation.
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteGlobal.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteGlobal.java
index e030339..91e4503 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteGlobal.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteGlobal.java
@@ -42,6 +42,7 @@ public final class SQLiteGlobal {
private static final Object sLock = new Object();
private static int sDefaultPageSize = 4096;
+ private static int sWALConnectionPoolSize = 10;
private static native int nativeReleaseMemory();
@@ -61,6 +62,7 @@ public static int releaseMemory() {
/**
* Gets the default page size to use when creating a database.
*/
+ @SuppressWarnings("deprecation")
public static int getDefaultPageSize() {
synchronized (sLock) {
if (sDefaultPageSize == 0) {
@@ -108,11 +110,18 @@ public static int getWALAutoCheckpoint() {
return Math.max(1, value);
}
+ /**
+ * Sets the connection pool size for WAL mode.
+ */
+ public static void setWALConnectionPoolSize(int value) {
+ sWALConnectionPoolSize = value;
+ }
+
/**
* Gets the connection pool size when in WAL mode.
*/
public static int getWALConnectionPoolSize() {
- int value = 10;
- return Math.max(2, value);
+ return sWALConnectionPoolSize;
}
+
}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteNotADatabaseException.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteNotADatabaseException.java
new file mode 100644
index 0000000..7ab7a17
--- /dev/null
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteNotADatabaseException.java
@@ -0,0 +1,22 @@
+package net.zetetic.database.sqlcipher;
+
+import android.database.sqlite.SQLiteException;
+
+/**
+ * An exception that is specific to the "SQLITE_NOTADB" error code.
+ *
+ * SQLITE_NOTADB
+ */
+public class SQLiteNotADatabaseException extends SQLiteException {
+ public SQLiteNotADatabaseException() {
+ super();
+ }
+
+ public SQLiteNotADatabaseException(String error) {
+ super(error);
+ }
+
+ public SQLiteNotADatabaseException(String error, Throwable cause) {
+ super(error, cause);
+ }
+}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteOpenHelper.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteOpenHelper.java
index c639c7f..9d9f4fd 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteOpenHelper.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteOpenHelper.java
@@ -23,10 +23,10 @@
import android.content.Context;
import net.zetetic.database.DatabaseErrorHandler;
+import net.zetetic.database.Logger;
import net.zetetic.database.sqlcipher.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteException;
-import android.util.Log;
import androidx.sqlite.db.SupportSQLiteOpenHelper;
@@ -363,7 +363,7 @@ instance has not yet been created via getWritableDatabase(). This allows
if (writable) {
throw ex;
}
- Log.e(TAG, "Couldn't open " + mName
+ Logger.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mPassword, mFactory,
@@ -414,7 +414,7 @@ instance has not yet been created via getWritableDatabase(). This allows
onOpen(db);
if (db.isReadOnly()) {
- Log.w(TAG, "Opened " + mName + " in read-only mode");
+ Logger.w(TAG, "Opened " + mName + " in read-only mode");
}
mDatabase = db;
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteProgram.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteProgram.java
index c0836a2..75909e0 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteProgram.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteProgram.java
@@ -21,6 +21,7 @@
package net.zetetic.database.sqlcipher;
import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteException;
import android.os.CancellationSignal;
import androidx.sqlite.db.SupportSQLiteProgram;
@@ -113,8 +114,8 @@ protected final int getConnectionFlags() {
}
/** @hide */
- protected final void onCorruption() {
- mDatabase.onCorruption();
+ protected final void onCorruption(SQLiteException exception) {
+ mDatabase.onCorruption(exception);
}
/**
@@ -211,14 +212,11 @@ public void bindAllArgsAsStrings(String[] bindArgs) {
/**
* Given a varargs of Object bindArgs, this method binds all of them in one single call.
*
- * @param bindArgs the varargs of bind args, none of which must be null.
+ * @param bindArgs the varargs of bind args.
*/
public void bindAllArgs(Object... bindArgs){
if (bindArgs != null) {
for (int i = bindArgs.length; i != 0; i--) {
- if (bindArgs[i - 1] == null) {
- throw new IllegalArgumentException("the bind value at index " + i + " is null");
- }
bind(i, bindArgs[i - 1]);
}
}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java
index 028f38f..aae818b 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQuery.java
@@ -20,12 +20,13 @@
package net.zetetic.database.sqlcipher;
-import android.database.CursorWindow;
import android.database.sqlite.SQLiteDatabaseCorruptException;
import android.database.sqlite.SQLiteException;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
-import android.util.Log;
+
+import net.zetetic.database.CursorWindow;
+import net.zetetic.database.Logger;
/**
* Represents a query that reads the resulting rows into a {@link SQLiteQuery}.
@@ -70,10 +71,10 @@ window, startPos, requiredPos, countAllRows, getConnectionFlags(),
mCancellationSignal);
return numRows;
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} catch (SQLiteException ex) {
- Log.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
+ Logger.e(TAG, "exception: " + ex.getMessage() + "; query: " + getSql());
throw ex;
} finally {
window.releaseReference();
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQueryBuilder.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQueryBuilder.java
index 5d364bc..0e06c1f 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQueryBuilder.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteQueryBuilder.java
@@ -26,7 +26,8 @@
import android.os.OperationCanceledException;
import android.provider.BaseColumns;
import android.text.TextUtils;
-import android.util.Log;
+
+import net.zetetic.database.Logger;
import java.util.Iterator;
import java.util.Map;
@@ -397,8 +398,8 @@ public Cursor query(SQLiteDatabase db, String[] projectionIn,
projectionIn, selection, groupBy, having,
sortOrder, limit);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Performing query: " + sql);
+ if (Logger.isLoggable(TAG, Logger.DEBUG)) {
+ Logger.d(TAG, "Performing query: " + sql);
}
return db.rawQueryWithFactory(
mFactory, sql, selectionArgs,
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteSession.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteSession.java
index d25e405..d1f1a5f 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteSession.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteSession.java
@@ -20,12 +20,13 @@
package net.zetetic.database.sqlcipher;
-import android.database.CursorWindow;
import android.database.DatabaseUtils;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.os.ParcelFileDescriptor;
+import net.zetetic.database.CursorWindow;
+
/**
* Provides a single client the ability to use a database.
*
@@ -852,8 +853,8 @@ public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int conne
* @throws OperationCanceledException if the operation was canceled.
*/
public int executeForCursorWindow(String sql, Object[] bindArgs,
- CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
- int connectionFlags, CancellationSignal cancellationSignal) {
+ CursorWindow window, int startPos, int requiredPos, boolean countAllRows,
+ int connectionFlags, CancellationSignal cancellationSignal) {
if (sql == null) {
throw new IllegalArgumentException("sql must not be null.");
}
diff --git a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteStatement.java b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteStatement.java
index 01b0f97..889266a 100644
--- a/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteStatement.java
+++ b/sqlcipher/src/main/java/net/zetetic/database/sqlcipher/SQLiteStatement.java
@@ -50,7 +50,7 @@ public void executeRaw() {
try {
getSession().executeRaw(getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} finally {
releaseReference();
@@ -69,7 +69,7 @@ public void execute() {
try {
getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} finally {
releaseReference();
@@ -90,7 +90,7 @@ public int executeUpdateDelete() {
return getSession().executeForChangedRowCount(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} finally {
releaseReference();
@@ -112,7 +112,7 @@ public long executeInsert() {
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} finally {
releaseReference();
@@ -133,7 +133,7 @@ public long simpleQueryForLong() {
return getSession().executeForLong(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} finally {
releaseReference();
@@ -154,7 +154,7 @@ public String simpleQueryForString() {
return getSession().executeForString(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} finally {
releaseReference();
@@ -175,7 +175,7 @@ public ParcelFileDescriptor simpleQueryForBlobFileDescriptor() {
return getSession().executeForBlobFileDescriptor(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
- onCorruption();
+ onCorruption(ex);
throw ex;
} finally {
releaseReference();
diff --git a/sqlcipher/src/main/jni/Application.mk b/sqlcipher/src/main/jni/Application.mk
index d71dbdb..2a25aa5 100644
--- a/sqlcipher/src/main/jni/Application.mk
+++ b/sqlcipher/src/main/jni/Application.mk
@@ -1 +1,3 @@
APP_STL:=c++_static
+APP_LDFLAGS += -Wl,--exclude-libs,ALL
+APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
diff --git a/sqlcipher/src/main/jni/sqlcipher/ALog-priv.h b/sqlcipher/src/main/jni/sqlcipher/ALog-priv.h
index 30d7e78..ce3b709 100644
--- a/sqlcipher/src/main/jni/sqlcipher/ALog-priv.h
+++ b/sqlcipher/src/main/jni/sqlcipher/ALog-priv.h
@@ -14,7 +14,7 @@
* limitations under the License.
*/
/*
-** Modified to support SQLite extensions by the SQLite developers:
+** Modified to support SQLite extensions by the SQLite developers:
** sqlite-dev@sqlite.org.
*/
@@ -44,7 +44,7 @@
#endif
#ifndef ALOGV
-#if LOG_NDEBUG
+#if defined(LOG_NDEBUG) || defined(SQLCIPHER_OMIT_LOG)
#define ALOGV(...) ((void)0)
#else
#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
@@ -52,20 +52,36 @@
#endif
#ifndef ALOGD
+#if defined(LOG_NDEBUG) || defined(SQLCIPHER_OMIT_LOG)
+#define ALOGD(...) ((void)0)
+#else
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
+#endif
#ifndef ALOGI
+#if defined(LOG_NDEBUG) || defined(SQLCIPHER_OMIT_LOG)
+#define ALOGI(...) ((void)0)
+#else
#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif
+#endif
#ifndef ALOGW
+#if defined(SQLCIPHER_OMIT_LOG)
+#define ALOGW(...) ((void)0)
+#else
#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif
+#endif
#ifndef ALOGE
+#if defined(SQLCIPHER_OMIT_LOG)
+#define ALOGE(...) ((void)0)
+#else
#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
+#endif
/*
** Not quite the same as the core android LOG_FATAL_IF (which also
diff --git a/sqlcipher/src/main/jni/sqlcipher/Android.mk b/sqlcipher/src/main/jni/sqlcipher/Android.mk
index f245646..6ed942b 100644
--- a/sqlcipher/src/main/jni/sqlcipher/Android.mk
+++ b/sqlcipher/src/main/jni/sqlcipher/Android.mk
@@ -19,12 +19,16 @@ LOCAL_CPPFLAGS += -Wno-conversion-null
$(info SQLCipher LOCAL_CFLAGS:${LOCAL_CFLAGS})
-LOCAL_SRC_FILES:= \
+LOCAL_SRC_FILES:= \
android_database_SQLiteCommon.cpp \
android_database_SQLiteConnection.cpp \
+ android_database_CursorWindow.cpp \
android_database_SQLiteGlobal.cpp \
android_database_SQLiteDebug.cpp \
- JNIHelp.cpp JniConstants.cpp
+ JNIHelp.cpp \
+ JniConstants.cpp \
+ JNIString.cpp \
+ CursorWindow.cpp
LOCAL_SRC_FILES += sqlite3.c
@@ -32,6 +36,7 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH) $(LOCAL_PATH)/nativehelper/ $(LOCAL_PATH)/andr
LOCAL_MODULE:= libsqlcipher
LOCAL_LDLIBS += -ldl -llog
+LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384
LOCAL_STATIC_LIBRARIES += static-libcrypto
include $(BUILD_SHARED_LIBRARY)
@@ -39,4 +44,4 @@ include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := static-libcrypto
LOCAL_SRC_FILES := $(LOCAL_PATH)/android-libs/$(TARGET_ARCH_ABI)/libcrypto.a
-include $(PREBUILT_STATIC_LIBRARY)
\ No newline at end of file
+include $(PREBUILT_STATIC_LIBRARY)
diff --git a/sqlcipher/src/main/jni/sqlcipher/CursorWindow.cpp b/sqlcipher/src/main/jni/sqlcipher/CursorWindow.cpp
new file mode 100644
index 0000000..c338e07
--- /dev/null
+++ b/sqlcipher/src/main/jni/sqlcipher/CursorWindow.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2006-2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// modified from original source see README at the top level of this project
+
+#undef LOG_TAG
+#define LOG_TAG "CursorWindow"
+
+#include "CursorWindow.h"
+#include "ALog-priv.h"
+
+#include
+#include
+#include
+
+namespace android {
+
+ CursorWindow::CursorWindow(const char* name, void* data, size_t size, bool readOnly) :
+ mData(data), mSize(size), mReadOnly(readOnly) {
+ mName = strdup(name);
+ mHeader = static_cast(mData);
+ }
+
+ CursorWindow::~CursorWindow() {
+ free(mName);
+ free(mData);
+ }
+
+ status_t CursorWindow::create(const char* name, size_t size, CursorWindow** outWindow) {
+ status_t result;
+ size_t requestedSize = size;
+ void* data = malloc(requestedSize);
+ if (!data) {
+ return NO_MEMORY;
+ }
+ auto* window = new CursorWindow(name, data, requestedSize, false);
+ result = window->clear();
+ if (!result) {
+ ALOGD("Created new CursorWindow: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%zu, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outWindow = window;
+ return OK;
+ }
+ delete window;
+ return result;
+ }
+
+ status_t CursorWindow::clear() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk);
+ mHeader->firstChunkOffset = sizeof(Header);
+ mHeader->numRows = 0;
+ mHeader->numColumns = 0;
+ auto* firstChunk = static_cast(offsetToPtr(mHeader->firstChunkOffset));
+ firstChunk->nextChunkOffset = 0;
+ return OK;
+ }
+
+ status_t CursorWindow::setNumColumns(uint32_t numColumns) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ uint32_t cur = mHeader->numColumns;
+ if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) {
+ ALOGE("Trying to go from %d columns to %d", cur, numColumns);
+ return INVALID_OPERATION;
+ }
+ mHeader->numColumns = numColumns;
+ return OK;
+ }
+
+ status_t CursorWindow::allocRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ // Fill in the row slot
+ RowSlot* rowSlot = allocRowSlot();
+ if (rowSlot == nullptr) {
+ return NO_MEMORY;
+ }
+ // Allocate the slots for the field directory
+ size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
+ uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/);
+ if (!fieldDirOffset) {
+ mHeader->numRows--;
+ ALOGD("The row failed, so back out the new row accounting "
+ "from allocRowSlot %d", mHeader->numRows);
+ return NO_MEMORY;
+ }
+
+ auto* fieldDir = static_cast(offsetToPtr(fieldDirOffset));
+ memset(fieldDir, 0, fieldDirSize);
+ rowSlot = getRowSlot(mHeader->numRows - 1);
+ rowSlot->offset = fieldDirOffset;
+ return OK;
+ }
+
+ status_t CursorWindow::freeLastRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ if (mHeader->numRows > 0) {
+ mHeader->numRows--;
+ }
+ return OK;
+ }
+
+ status_t CursorWindow::maybeInflate() {
+ size_t initialSize = 16 * 1024;
+ size_t newSize = mSize <= initialSize
+ ? 2048 * 1024
+ : mSize * 2;
+ uint32_t freeOffset = mHeader->freeOffset;
+ ALOGI("Request to resize CursorWindow allocation: current window size %zu bytes, "
+ "free space %zu bytes, new window size %zu bytes",
+ mSize, freeSpace(), newSize);
+ if((mData = realloc(mData, newSize))){
+ mHeader = static_cast(mData);
+ mHeader->freeOffset = freeOffset;
+ mSize = newSize;
+ ALOGI("Resized CursorWindow allocation: current window size %zu bytes",
+ newSize);
+ return OK;
+ } else {
+ ALOGE("Failed to resize CursorWindow allocation");
+ return NO_MEMORY;
+ }
+ }
+
+ uint32_t CursorWindow::alloc(size_t size, bool aligned) { // NOLINT(*-no-recursion)
+ uint32_t padding;
+ if (aligned) {
+ // 4 byte alignment
+ padding = (~mHeader->freeOffset + 1) & 3;
+ } else {
+ padding = 0;
+ }
+ uint32_t offset = mHeader->freeOffset + padding;
+ uint32_t nextFreeOffset = offset + size;
+ if (nextFreeOffset > mSize) {
+ status_t result = maybeInflate();
+ if(result == OK){
+ return alloc(size, aligned);
+ }
+ ALOGI("Window is full: requested allocation %zu bytes, "
+ "free space %zu bytes, window size %zu bytes",
+ size, freeSpace(), mSize);
+ return 0;
+ }
+ mHeader->freeOffset = nextFreeOffset;
+ return offset;
+ }
+
+ CursorWindow::RowSlot* CursorWindow::getRowSlot(uint32_t row) {
+ uint32_t chunkPos = row;
+ auto* chunk = static_cast(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos >= ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunk = static_cast(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ return &chunk->slots[chunkPos];
+ }
+
+ CursorWindow::RowSlot* CursorWindow::allocRowSlot() {
+ uint32_t chunkPos = mHeader->numRows;
+ uint32_t chunkOffset = mHeader->firstChunkOffset;
+ auto* chunk = static_cast(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos > ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunkOffset = chunk->nextChunkOffset;
+ chunk = static_cast(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
+ if (!chunk->nextChunkOffset) {
+ uint32_t nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+ chunk = static_cast(offsetToPtr(chunkOffset));
+ chunk->nextChunkOffset = nextChunkOffset;
+ if (!chunk->nextChunkOffset) {
+ return nullptr;
+ }
+ }
+ chunk = static_cast(offsetToPtr(chunk->nextChunkOffset));
+ chunk->nextChunkOffset = 0;
+ chunkPos = 0;
+ }
+ mHeader->numRows += 1;
+ return &chunk->slots[chunkPos];
+ }
+
+ CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) {
+ if (row >= mHeader->numRows || column >= mHeader->numColumns) {
+ ALOGE("Failed to read row %d, column %d from a CursorWindow which "
+ "has %d rows, %d columns.",
+ row, column, mHeader->numRows, mHeader->numColumns);
+ return nullptr;
+ }
+ RowSlot* rowSlot = getRowSlot(row);
+ if (!rowSlot) {
+ ALOGE("Failed to find rowSlot for row %d.", row);
+ return nullptr;
+ }
+ auto* fieldDir = static_cast(offsetToPtr(rowSlot->offset));
+ return &fieldDir[column];
+ }
+
+ status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) {
+ return putBlobOrString(row, column, value, size, FIELD_TYPE_BLOB);
+ }
+
+ status_t CursorWindow::putString(uint32_t row, uint32_t column, const char* value,
+ size_t sizeIncludingNull) {
+ return putBlobOrString(row, column, value, sizeIncludingNull, FIELD_TYPE_STRING);
+ }
+
+ status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ uint32_t offset = alloc(size);
+ if (!offset) {
+ return NO_MEMORY;
+ }
+ memcpy(offsetToPtr(offset), value, size);
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+ fieldSlot->type = type;
+ fieldSlot->data.buffer.offset = offset;
+ fieldSlot->data.buffer.size = size;
+ return OK;
+ }
+
+ status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+ fieldSlot->type = FIELD_TYPE_INTEGER;
+ fieldSlot->data.l = value;
+ return OK;
+ }
+
+ status_t CursorWindow::putDouble(uint32_t row, uint32_t column, double value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+ fieldSlot->type = FIELD_TYPE_FLOAT;
+ fieldSlot->data.d = value;
+ return OK;
+ }
+
+ status_t CursorWindow::putNull(uint32_t row, uint32_t column) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+ fieldSlot->type = FIELD_TYPE_NULL;
+ fieldSlot->data.buffer.offset = 0;
+ fieldSlot->data.buffer.size = 0;
+ return OK;
+ }
+
+} // namespace android
diff --git a/sqlcipher/src/main/jni/sqlcipher/CursorWindow.h b/sqlcipher/src/main/jni/sqlcipher/CursorWindow.h
new file mode 100644
index 0000000..6882a3a
--- /dev/null
+++ b/sqlcipher/src/main/jni/sqlcipher/CursorWindow.h
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+// modified from original source see README at the top level of this project
+
+#ifndef _ANDROID__DATABASE_WINDOW_H
+#define _ANDROID__DATABASE_WINDOW_H
+
+#include "ALog-priv.h"
+#include
+#include
+
+#include "Errors.h"
+
+namespace android {
+
+/**
+ * This class stores a set of rows from a database in a buffer. The beginning of the
+ * window has first chunk of RowSlots, which are offsets to the row directory, followed by
+ * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case
+ * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
+ * FieldSlot per column, which has the size, offset, and type of the data for that field.
+ * Note that the data types come from sqlite3.h.
+ *
+ * Strings are stored in UTF-8.
+ */
+ class CursorWindow {
+ CursorWindow(const char* name, void* data, size_t size, bool readOnly);
+
+ public:
+ /* Field types. */
+ enum {
+ FIELD_TYPE_NULL = 0,
+ FIELD_TYPE_INTEGER = 1,
+ FIELD_TYPE_FLOAT = 2,
+ FIELD_TYPE_STRING = 3,
+ FIELD_TYPE_BLOB = 4,
+ };
+
+ /* Opaque type that describes a field slot. */
+ struct FieldSlot {
+ private:
+ int32_t type;
+ union {
+ double d;
+ int64_t l;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+ } buffer;
+ } data;
+
+ friend class CursorWindow;
+ } __attribute((packed));
+
+ ~CursorWindow();
+
+ static status_t create(const char* name, size_t size, CursorWindow** outCursorWindow);
+
+ inline const char* name() { return mName; }
+ inline size_t size() const { return mSize; }
+ inline size_t freeSpace() { return mSize - mHeader->freeOffset; }
+ inline uint32_t getNumRows() { return mHeader->numRows; }
+ inline uint32_t getNumColumns() { return mHeader->numColumns; }
+
+ status_t clear();
+ status_t setNumColumns(uint32_t numColumns);
+
+ /**
+ * Allocate a row slot and its directory.
+ * The row is initialized will null entries for each field.
+ */
+ status_t allocRow();
+ status_t freeLastRow();
+ status_t maybeInflate();
+
+ status_t putBlob(uint32_t row, uint32_t column, const void* value, size_t size);
+ status_t putString(uint32_t row, uint32_t column, const char* value, size_t sizeIncludingNull);
+ status_t putLong(uint32_t row, uint32_t column, int64_t value);
+ status_t putDouble(uint32_t row, uint32_t column, double value);
+ status_t putNull(uint32_t row, uint32_t column);
+
+ /**
+ * Gets the field slot at the specified row and column.
+ * Returns null if the requested row or column is not in the window.
+ */
+ FieldSlot* getFieldSlot(uint32_t row, uint32_t column);
+
+ static inline int32_t getFieldSlotType(FieldSlot* fieldSlot) {
+ return fieldSlot->type;
+ }
+
+ static inline int64_t getFieldSlotValueLong(FieldSlot* fieldSlot) {
+ return fieldSlot->data.l;
+ }
+
+ static inline double getFieldSlotValueDouble(FieldSlot* fieldSlot) {
+ return fieldSlot->data.d;
+ }
+
+ inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
+ size_t* outSizeIncludingNull) {
+ *outSizeIncludingNull = fieldSlot->data.buffer.size;
+ return static_cast(offsetToPtr(fieldSlot->data.buffer.offset));
+ }
+
+ inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
+ *outSize = fieldSlot->data.buffer.size;
+ return offsetToPtr(fieldSlot->data.buffer.offset);
+ }
+
+ private:
+ static const size_t ROW_SLOT_CHUNK_NUM_ROWS = 100;
+
+ struct Header {
+ // Offset of the lowest unused byte in the window.
+ uint32_t freeOffset;
+
+ // Offset of the first row slot chunk.
+ uint32_t firstChunkOffset;
+
+ uint32_t numRows;
+ uint32_t numColumns;
+ };
+
+ struct RowSlot {
+ uint32_t offset;
+ };
+
+ struct RowSlotChunk {
+ RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
+ uint32_t nextChunkOffset;
+ };
+
+ char* mName;
+ void* mData;
+ size_t mSize;
+ bool mReadOnly;
+ Header* mHeader;
+
+ inline void* offsetToPtr(uint32_t offset) {
+ return static_cast(mData) + offset;
+ }
+
+ inline uint32_t offsetFromPtr(void* ptr) {
+ return static_cast(ptr) - static_cast(mData);
+ }
+
+ /**
+ * Allocate a portion of the window. Returns the offset
+ * of the allocation, or 0 if there isn't enough space.
+ * If aligned is true, the allocation gets 4 byte alignment.
+ */
+ uint32_t alloc(size_t size, bool aligned = false);
+
+ RowSlot* getRowSlot(uint32_t row);
+ RowSlot* allocRowSlot();
+
+ status_t putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type);
+ };
+
+} // namespace android
+
+#endif
diff --git a/sqlcipher/src/main/jni/sqlcipher/Errors.h b/sqlcipher/src/main/jni/sqlcipher/Errors.h
new file mode 100644
index 0000000..64b0564
--- /dev/null
+++ b/sqlcipher/src/main/jni/sqlcipher/Errors.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_ERRORS_H
+#define ANDROID_ERRORS_H
+
+#include
+#include
+
+namespace android {
+
+// use this type to return error codes
+#ifdef HAVE_MS_C_RUNTIME
+ typedef int status_t;
+#else
+ typedef int32_t status_t;
+#endif
+
+/* the MS C runtime lacks a few error codes */
+
+/*
+ * Error codes.
+ * All error codes are negative values.
+ */
+
+// Win32 #defines NO_ERROR as well. It has the same value, so there's no
+// real conflict, though it's a bit awkward.
+#ifdef _WIN32
+# undef NO_ERROR
+#endif
+
+ enum {
+ OK = 0, // Everything's swell.
+ NO_ERROR = 0, // No errors.
+
+ UNKNOWN_ERROR = 0x80000000,
+
+ NO_MEMORY = -ENOMEM,
+ INVALID_OPERATION = -ENOSYS,
+ BAD_VALUE = -EINVAL,
+ BAD_TYPE = 0x80000001,
+ NAME_NOT_FOUND = -ENOENT,
+ PERMISSION_DENIED = -EPERM,
+ NO_INIT = -ENODEV,
+ ALREADY_EXISTS = -EEXIST,
+ DEAD_OBJECT = -EPIPE,
+ FAILED_TRANSACTION = 0x80000002,
+ JPARKS_BROKE_IT = -EPIPE,
+#if !defined(HAVE_MS_C_RUNTIME)
+ BAD_INDEX = -EOVERFLOW,
+ NOT_ENOUGH_DATA = -ENODATA,
+ WOULD_BLOCK = -EWOULDBLOCK,
+ TIMED_OUT = -ETIMEDOUT,
+ UNKNOWN_TRANSACTION = -EBADMSG,
+#else
+ BAD_INDEX = -E2BIG,
+ NOT_ENOUGH_DATA = 0x80000003,
+ WOULD_BLOCK = 0x80000004,
+ TIMED_OUT = 0x80000005,
+ UNKNOWN_TRANSACTION = 0x80000006,
+#endif
+ FDS_NOT_ALLOWED = 0x80000007,
+ };
+
+// Restore define; enumeration is in "android" namespace, so the value defined
+// there won't work for Win32 code in a different namespace.
+#ifdef _WIN32
+# define NO_ERROR 0L
+#endif
+
+}; // namespace android
+
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_ERRORS_H
diff --git a/sqlcipher/src/main/jni/sqlcipher/JNIString.cpp b/sqlcipher/src/main/jni/sqlcipher/JNIString.cpp
new file mode 100644
index 0000000..7841156
--- /dev/null
+++ b/sqlcipher/src/main/jni/sqlcipher/JNIString.cpp
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+// Note this code is adapted from AOSP implementation of String, now located at
+// https://android.googlesource.com/platform/libcore/+/master/libart/src/main/java/java/lang/StringFactory.java
+
+#include
+
+#define REPLACEMENT_CHAR 0xfffd;
+
+namespace android {
+
+ jsize utf8ToJavaCharArray(const char* d, jchar v[], jint byteCount) {
+ jint idx = 0;
+ jint last = byteCount;
+ jint s = 0;
+ outer:
+ while (idx < last) {
+ jbyte b0 = d[idx++];
+ if ((b0 & 0x80) == 0) {
+ // 0xxxxxxx
+ // Range: U-00000000 - U-0000007F
+ jint val = b0 & 0xff;
+ v[s++] = (jchar) val;
+ } else if (((b0 & 0xe0) == 0xc0) || ((b0 & 0xf0) == 0xe0) ||
+ ((b0 & 0xf8) == 0xf0) || ((b0 & 0xfc) == 0xf8) || ((b0 & 0xfe) == 0xfc)) {
+ jint utfCount = 1;
+ if ((b0 & 0xf0) == 0xe0) utfCount = 2;
+ else if ((b0 & 0xf8) == 0xf0) utfCount = 3;
+ else if ((b0 & 0xfc) == 0xf8) utfCount = 4;
+ else if ((b0 & 0xfe) == 0xfc) utfCount = 5;
+
+ // 110xxxxx (10xxxxxx)+
+ // Range: U-00000080 - U-000007FF (count == 1)
+ // Range: U-00000800 - U-0000FFFF (count == 2)
+ // Range: U-00010000 - U-001FFFFF (count == 3)
+ // Range: U-00200000 - U-03FFFFFF (count == 4)
+ // Range: U-04000000 - U-7FFFFFFF (count == 5)
+
+ if (idx + utfCount > last) {
+ v[s++] = REPLACEMENT_CHAR;
+ continue;
+ }
+
+ // Extract usable bits from b0
+ jint val = b0 & (0x1f >> (utfCount - 1));
+ for (int i = 0; i < utfCount; ++i) {
+ jbyte b = d[idx++];
+ if ((b & 0xc0) != 0x80) {
+ v[s++] = REPLACEMENT_CHAR;
+ idx--; // Put the input char back
+ goto outer;
+ }
+ // Push new bits in from the right side
+ val <<= 6;
+ val |= b & 0x3f;
+ }
+
+ // Note: Java allows overlong char
+ // specifications To disallow, check that val
+ // is greater than or equal to the minimum
+ // value for each count:
+ //
+ // count min value
+ // ----- ----------
+ // 1 0x80
+ // 2 0x800
+ // 3 0x10000
+ // 4 0x200000
+ // 5 0x4000000
+
+ // Allow surrogate values (0xD800 - 0xDFFF) to
+ // be specified using 3-byte UTF values only
+ if ((utfCount != 2) && (val >= 0xD800) && (val <= 0xDFFF)) {
+ v[s++] = REPLACEMENT_CHAR;
+ continue;
+ }
+
+ // Reject chars greater than the Unicode maximum of U+10FFFF.
+ if (val > 0x10FFFF) {
+ v[s++] = REPLACEMENT_CHAR;
+ continue;
+ }
+
+ // Encode chars from U+10000 up as surrogate pairs
+ if (val < 0x10000) {
+ v[s++] = (jchar) val;
+ } else {
+ int x = val & 0xffff;
+ int u = (val >> 16) & 0x1f;
+ int w = (u - 1) & 0xffff;
+ int hi = 0xd800 | (w << 6) | (x >> 10);
+ int lo = 0xdc00 | (x & 0x3ff);
+ v[s++] = (jchar) hi;
+ v[s++] = (jchar) lo;
+ }
+ } else {
+ // Illegal values 0x8*, 0x9*, 0xa*, 0xb*, 0xfd-0xff
+ v[s++] = REPLACEMENT_CHAR;
+ }
+ }
+ return s;
+ }
+}
\ No newline at end of file
diff --git a/sqlcipher/src/main/jni/sqlcipher/android_database_CursorWindow.cpp b/sqlcipher/src/main/jni/sqlcipher/android_database_CursorWindow.cpp
new file mode 100644
index 0000000..a835f6d
--- /dev/null
+++ b/sqlcipher/src/main/jni/sqlcipher/android_database_CursorWindow.cpp
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// modified from original source see README at the top level of this project
+
+#undef LOG_TAG
+#define LOG_TAG "CursorWindow"
+#define __STDC_FORMAT_MACROS
+
+#include
+#include
+#include
+#include
+#include
+
+#include "JNIHelp.h"
+#include "CursorWindow.h"
+#include "android_database_SQLiteCommon.h"
+
+namespace android {
+
+ static struct {
+ jfieldID data;
+ jfieldID sizeCopied;
+ } gCharArrayBufferClassInfo;
+
+ static jstring gEmptyString = nullptr;
+
+ static void throwExceptionWithCol(JNIEnv* env, jint column) {
+ char buf[64];
+ snprintf(buf, sizeof(buf), "Couldn't set column %d", column);
+ jniThrowException(env, "java/lang/IllegalStateException", buf);
+ }
+
+ static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
+ char buf[64];
+ snprintf(buf, sizeof(buf), "Couldn't read row %d column %d", row, column);
+ jniThrowException(env, "java/lang/IllegalStateException", buf);
+ }
+
+ static void throwUnknownTypeException(JNIEnv * env, jint type) {
+ char buf[32];
+ snprintf(buf, sizeof(buf), "UNKNOWN type %d", type);
+ jniThrowException(env, "java/lang/IllegalStateException", buf);
+ }
+
+ static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
+ CursorWindow* window;
+ const char* nameStr = env->GetStringUTFChars(nameObj, nullptr);
+ status_t status = CursorWindow::create(nameStr, cursorWindowSize, &window);
+ env->ReleaseStringUTFChars(nameObj, nameStr);
+
+ if (status || !window) {
+ ALOGE("Could not allocate CursorWindow of size %d due to error %d.",
+ cursorWindowSize, status);
+ return 0;
+ }
+
+ ALOGD("nativeInitializeEmpty: window = %p", window);
+ return reinterpret_cast(window);
+ }
+
+ static void nativeDispose(JNIEnv* env, jclass clazz, jlong windowPtr) {
+ auto* window = reinterpret_cast(windowPtr);
+ if (window) {
+ ALOGD("Closing window %p", window);
+ delete window;
+ }
+ }
+
+ static jstring nativeGetName(JNIEnv* env, jclass clazz, jlong windowPtr) {
+ auto* window = reinterpret_cast(windowPtr);
+ return env->NewStringUTF(window->name());
+ }
+
+ static void nativeClear(JNIEnv * env, jclass clazz, jlong windowPtr) {
+ auto* window = reinterpret_cast(windowPtr);
+ ALOGD("Clearing window %p", window);
+ status_t status = window->clear();
+ if (status) {
+ ALOGD("Could not clear window. error=%d", status);
+ }
+ }
+
+ static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jlong windowPtr) {
+ auto* window = reinterpret_cast(windowPtr);
+ return window->getNumRows();
+ }
+
+ static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jint columnNum) {
+ auto* window = reinterpret_cast(windowPtr);
+ if(window == nullptr){
+ throwExceptionWithCol(env, columnNum);
+ return false;
+ }
+ status_t status = window->setNumColumns(columnNum);
+ return status == OK;
+ }
+
+ static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
+ auto* window = reinterpret_cast(windowPtr);
+ status_t status = window->allocRow();
+ return status == OK;
+ }
+
+ static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jlong windowPtr) {
+ auto* window = reinterpret_cast(windowPtr);
+ window->freeLastRow();
+ }
+
+ static jint nativeGetType(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ ALOGD("returning column type affinity for %d,%d from %p", row, column, window);
+ CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return CursorWindow::FIELD_TYPE_NULL;
+ }
+ return android::CursorWindow::getFieldSlotType(fieldSlot);
+ }
+
+ static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return nullptr;
+ }
+ int32_t type = android::CursorWindow::getFieldSlotType(fieldSlot);
+ if (type == CursorWindow::FIELD_TYPE_BLOB || type == CursorWindow::FIELD_TYPE_STRING) {
+ size_t size;
+ const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
+ jbyteArray byteArray = env->NewByteArray(size);
+ if (!byteArray) {
+ env->ExceptionClear();
+ throw_sqlite3_exception(env, "Native could not create new byte[]");
+ return NULL;
+ }
+ env->SetByteArrayRegion(byteArray, 0, size, static_cast(value));
+ return byteArray;
+ } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
+ throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
+ } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
+ throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
+ } else if (type == CursorWindow::FIELD_TYPE_NULL) {
+ // do nothing
+ } else {
+ throwUnknownTypeException(env, type);
+ }
+ return nullptr;
+ }
+
+ extern int utf8ToJavaCharArray(const char* d, jchar v[], jint byteCount);
+
+ static jstring nativeGetString(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return nullptr;
+ }
+ int32_t type = android::CursorWindow::getFieldSlotType(fieldSlot);
+ if (type == CursorWindow::FIELD_TYPE_STRING) {
+ size_t sizeIncludingNull;
+ const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
+ if (sizeIncludingNull <= 1) {
+ return gEmptyString;
+ }
+ const size_t MaxStackStringSize = 65536; // max size for a stack char array
+ if (sizeIncludingNull > MaxStackStringSize) {
+ auto* chars = new jchar[sizeIncludingNull - 1];
+ jint size = utf8ToJavaCharArray(value, chars, sizeIncludingNull - 1);
+ jstring string = env->NewString(chars, size);
+ delete[] chars;
+ return string;
+ } else {
+ jchar chars[sizeIncludingNull - 1];
+ jint size = utf8ToJavaCharArray(value, chars, sizeIncludingNull - 1);
+ return env->NewString(chars, size);
+ }
+ } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
+ int64_t value = android::CursorWindow::getFieldSlotValueLong(fieldSlot);
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%" PRId64, value);
+ return env->NewStringUTF(buf);
+ } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
+ double value = android::CursorWindow::getFieldSlotValueDouble(fieldSlot);
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%g", value);
+ return env->NewStringUTF(buf);
+ } else if (type == CursorWindow::FIELD_TYPE_NULL) {
+ return nullptr;
+ } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
+ throw_sqlite3_exception(env, "Unable to convert BLOB to string");
+ return nullptr;
+ } else {
+ throwUnknownTypeException(env, type);
+ return nullptr;
+ }
+ }
+
+ static jlong nativeGetLong(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return 0;
+ }
+
+ int32_t type = android::CursorWindow::getFieldSlotType(fieldSlot);
+ if (type == CursorWindow::FIELD_TYPE_INTEGER) {
+ return android::CursorWindow::getFieldSlotValueLong(fieldSlot);
+ } else if (type == CursorWindow::FIELD_TYPE_STRING) {
+ size_t sizeIncludingNull;
+ const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
+ return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L;
+ } else if (type == CursorWindow::FIELD_TYPE_FLOAT) {
+ return jlong(android::CursorWindow::getFieldSlotValueDouble(fieldSlot));
+ } else if (type == CursorWindow::FIELD_TYPE_NULL) {
+ return 0;
+ } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
+ throw_sqlite3_exception(env, "Unable to convert BLOB to long");
+ return 0;
+ } else {
+ throwUnknownTypeException(env, type);
+ return 0;
+ }
+ }
+
+ static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ CursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return 0.0;
+ }
+ int32_t type = android::CursorWindow::getFieldSlotType(fieldSlot);
+ if (type == CursorWindow::FIELD_TYPE_FLOAT) {
+ return android::CursorWindow::getFieldSlotValueDouble(fieldSlot);
+ } else if (type == CursorWindow::FIELD_TYPE_STRING) {
+ size_t sizeIncludingNull;
+ const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
+ return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0;
+ } else if (type == CursorWindow::FIELD_TYPE_INTEGER) {
+ return jdouble(android::CursorWindow::getFieldSlotValueLong(fieldSlot));
+ } else if (type == CursorWindow::FIELD_TYPE_NULL) {
+ return 0.0;
+ } else if (type == CursorWindow::FIELD_TYPE_BLOB) {
+ throw_sqlite3_exception(env, "Unable to convert BLOB to double");
+ return 0.0;
+ } else {
+ throwUnknownTypeException(env, type);
+ return 0.0;
+ }
+ }
+
+ static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jbyteArray valueObj, jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ jsize len = env->GetArrayLength(valueObj);
+ void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
+ status_t status = window->putBlob(row, column, value, len);
+ env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
+ if (status) {
+ ALOGD("Failed to put blob. error=%d", status);
+ return false;
+ }
+ ALOGD("%d,%d is BLOB with %u bytes", row, column, len);
+ return true;
+ }
+
+ static jboolean nativePutString(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jstring valueObj, jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
+ const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
+ if (!valueStr) {
+ ALOGD("value can't be transferred to UTFChars");
+ return false;
+ }
+ status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
+ env->ReleaseStringUTFChars(valueObj, valueStr);
+ if (status) {
+ ALOGD("Failed to put string. error=%d", status);
+ return false;
+ }
+ ALOGD("%d,%d is TEXT with %zu bytes", row, column, sizeIncludingNull);
+ return true;
+ }
+
+ static jboolean nativePutLong(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jlong value, jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ status_t status = window->putLong(row, column, value);
+ if (status) {
+ ALOGD("Failed to put long. error=%d", status);
+ return false;
+ }
+ ALOGD("%d,%d is INTEGER 0x%016" PRIx64, row, column, value);
+ return true;
+ }
+
+ static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jdouble value, jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ status_t status = window->putDouble(row, column, value);
+ if (status) {
+ ALOGD("Failed to put double. error=%d", status);
+ return false;
+ }
+ ALOGD("%d,%d is FLOAT %lf", row, column, value);
+ return true;
+ }
+
+ static jboolean nativePutNull(JNIEnv* env, jclass clazz, jlong windowPtr,
+ jint row, jint column) {
+ auto* window = reinterpret_cast(windowPtr);
+ status_t status = window->putNull(row, column);
+ if (status) {
+ ALOGD("Failed to put null. error=%d", status);
+ return false;
+ }
+ ALOGD("%d,%d is NULL", row, column);
+ return true;
+ }
+
+ static const JNINativeMethod sMethods[] =
+ {
+ /* name, signature, funcPtr */
+ { "nativeCreate", "(Ljava/lang/String;I)J",
+ (void*)nativeCreate },
+ { "nativeDispose", "(J)V",
+ (void*)nativeDispose },
+ { "nativeGetName", "(J)Ljava/lang/String;",
+ (void*)nativeGetName },
+ { "nativeClear", "(J)V",
+ (void*)nativeClear },
+ { "nativeGetNumRows", "(J)I",
+ (void*)nativeGetNumRows },
+ { "nativeSetNumColumns", "(JI)Z",
+ (void*)nativeSetNumColumns },
+ { "nativeAllocRow", "(J)Z",
+ (void*)nativeAllocRow },
+ { "nativeFreeLastRow", "(J)V",
+ (void*)nativeFreeLastRow },
+ { "nativeGetType", "(JII)I",
+ (void*)nativeGetType },
+ { "nativeGetBlob", "(JII)[B",
+ (void*)nativeGetBlob },
+ { "nativeGetString", "(JII)Ljava/lang/String;",
+ (void*)nativeGetString },
+ { "nativeGetLong", "(JII)J",
+ (void*)nativeGetLong },
+ { "nativeGetDouble", "(JII)D",
+ (void*)nativeGetDouble },
+ { "nativePutBlob", "(J[BII)Z",
+ (void*)nativePutBlob },
+ { "nativePutString", "(JLjava/lang/String;II)Z",
+ (void*)nativePutString },
+ { "nativePutLong", "(JJII)Z",
+ (void*)nativePutLong },
+ { "nativePutDouble", "(JDII)Z",
+ (void*)nativePutDouble },
+ { "nativePutNull", "(JII)Z",
+ (void*)nativePutNull },
+ };
+
+ int register_android_database_CursorWindow(JNIEnv* env)
+ {
+ jclass clazz;
+ FIND_CLASS(clazz, "android/database/CharArrayBuffer");
+
+ GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz, "data", "[C");
+ GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz, "sizeCopied", "I");
+
+ gEmptyString = static_cast(env->NewGlobalRef(env->NewStringUTF("")));
+ return jniRegisterNativeMethods(env,
+ "net/zetetic/database/CursorWindow", sMethods, NELEM(sMethods));
+ }
+
+} // namespace android
diff --git a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteCommon.cpp b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteCommon.cpp
index 3751f70..eac7759 100644
--- a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteCommon.cpp
+++ b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteCommon.cpp
@@ -24,12 +24,12 @@ namespace android {
/* throw a SQLiteException with a message appropriate for the error in handle */
void throw_sqlite3_exception(JNIEnv* env, sqlite3* handle) {
- throw_sqlite3_exception(env, handle, NULL);
+ throw_sqlite3_exception(env, handle, nullptr);
}
/* throw a SQLiteException with the given message */
void throw_sqlite3_exception(JNIEnv* env, const char* message) {
- throw_sqlite3_exception(env, NULL, message);
+ throw_sqlite3_exception(env, nullptr, message);
}
/* throw a SQLiteException with a message appropriate for the error in handle
@@ -74,7 +74,7 @@ void throw_sqlite3_exception(JNIEnv* env, int errcode,
/* Upstream treats treat "unsupported file format" error as corruption (SQLiteDatabaseCorruptException).
However, SQLITE_NOTADB can occur with mismatched keys, which is not a corruption case, so SQLCipher
treats this as a general exception */
- exceptionClass = "android/database/sqlite/SQLiteException";
+ exceptionClass = "net/zetetic/database/sqlcipher/SQLiteNotADatabaseException";
break;
case SQLITE_CONSTRAINT:
exceptionClass = "android/database/sqlite/SQLiteConstraintException";
@@ -84,7 +84,7 @@ void throw_sqlite3_exception(JNIEnv* env, int errcode,
break;
case SQLITE_DONE:
exceptionClass = "android/database/sqlite/SQLiteDoneException";
- sqlite3Message = NULL; // SQLite error message is irrelevant in this case
+ sqlite3Message = nullptr; // SQLite error message is irrelevant in this case
break;
case SQLITE_FULL:
exceptionClass = "android/database/sqlite/SQLiteFullException";
diff --git a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteConnection.cpp b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteConnection.cpp
index 08d9928..fe82135 100644
--- a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteConnection.cpp
+++ b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteConnection.cpp
@@ -25,19 +25,23 @@
#include "ALog-priv.h"
#include
-#include
+#include
+#include
#include
-#include
+#include
#include
#include "android_database_SQLiteCommon.h"
-
+#include "CursorWindow.h"
#include
// Set to 1 to use UTF16 storage for localized indexes.
#define UTF16_STORAGE 0
+#undef LOG_TAG
+#define LOG_TAG SQLITE_LOG_TAG
+
namespace android {
/* Busy timeout in milliseconds.
@@ -94,21 +98,21 @@ struct SQLiteConnection {
// Called each time a statement begins execution, when tracing is enabled.
static void sqliteTraceCallback(void *data, const char *sql) {
- SQLiteConnection* connection = static_cast(data);
- ALOG(LOG_VERBOSE, SQLITE_TRACE_TAG, "%s: \"%s\"\n",
- connection->label.c_str(), sql);
+ auto* connection = static_cast(data);
+ ALOGV("%s: \"%s\"\n",
+ connection->label.c_str(), sql);
}
// Called each time a statement finishes execution, when profiling is enabled.
static void sqliteProfileCallback(void *data, const char *sql, sqlite3_uint64 tm) {
- SQLiteConnection* connection = static_cast(data);
- ALOG(LOG_VERBOSE, SQLITE_PROFILE_TAG, "%s: \"%s\" took %0.3f ms\n",
- connection->label.c_str(), sql, tm * 0.000001f);
+ auto* connection = static_cast(data);
+ ALOGV("%s: \"%s\" took %0.3f ms\n",
+ connection->label.c_str(), sql, tm * 0.000001f);
}
// Called after each SQLite VM instruction when cancelation is enabled.
static int sqliteProgressHandlerCallback(void* data) {
- SQLiteConnection* connection = static_cast(data);
+ auto* connection = static_cast(data);
return connection->canceled;
}
@@ -227,7 +231,7 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla
}
// Create wrapper object.
- SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label);
+ auto* connection = new SQLiteConnection(db, openFlags, path, label);
// Enable tracing and profiling if requested.
if (enableTrace) {
@@ -242,7 +246,7 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla
}
static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ auto* connection = reinterpret_cast(connectionPtr);
if (connection) {
ALOGV("Closing connection %p", connection->db);
@@ -268,13 +272,13 @@ static void sqliteCustomFunctionCallback(sqlite3_context *context,
// Get the callback function object.
// Create a new local reference to it in case the callback tries to do something
// dumb like unregister the function (thereby destroying the global ref) while it is running.
- jobject functionObjGlobal = reinterpret_cast(sqlite3_user_data(context));
+ auto functionObjGlobal = reinterpret_cast(sqlite3_user_data(context));
jobject functionObj = env->NewLocalRef(functionObjGlobal);
jobjectArray argsArray = env->NewObjectArray(argc, gStringClassInfo.clazz, NULL);
if (argsArray) {
for (int i = 0; i < argc; i++) {
- const jchar* arg = static_cast(sqlite3_value_text16(argv[i]));
+ const auto* arg = static_cast(sqlite3_value_text16(argv[i]));
if (!arg) {
ALOGW("NULL argument in custom_function_callback. This should not happen.");
} else {
@@ -307,7 +311,7 @@ static void sqliteCustomFunctionCallback(sqlite3_context *context,
// Called when a custom function is destroyed.
static void sqliteCustomFunctionDestructor(void* data) {
- jobject functionObjGlobal = reinterpret_cast(data);
+ auto functionObjGlobal = reinterpret_cast(data);
JNIEnv* env = 0;
gpJavaVM->GetEnv((void**)&env, JNI_VERSION_1_4);
env->DeleteGlobalRef(functionObjGlobal);
@@ -315,9 +319,9 @@ static void sqliteCustomFunctionDestructor(void* data) {
static void nativeRegisterCustomFunction(JNIEnv* env, jclass clazz, jlong connectionPtr,
jobject functionObj) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ auto* connection = reinterpret_cast(connectionPtr);
- jstring nameStr = jstring(env->GetObjectField(
+ auto nameStr = jstring(env->GetObjectField(
functionObj, gSQLiteCustomFunctionClassInfo.name));
jint numArgs = env->GetIntField(functionObj, gSQLiteCustomFunctionClassInfo.numArgs);
@@ -344,7 +348,7 @@ static void nativeRegisterLocalizedCollators(JNIEnv* env, jclass clazz, jlong co
static jlong nativePrepareStatement(JNIEnv* env, jclass clazz, jlong connectionPtr,
jstring sqlString) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ auto* connection = reinterpret_cast(connectionPtr);
jsize sqlLength = env->GetStringLength(sqlString);
const jchar* sql = env->GetStringCritical(sqlString, NULL);
@@ -375,8 +379,8 @@ static jlong nativePrepareStatement(JNIEnv* env, jclass clazz, jlong connectionP
static void nativeFinalizeStatement(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
// We ignore the result of sqlite3_finalize because it is really telling us about
// whether any errors occurred while executing the statement. The statement itself
@@ -387,30 +391,26 @@ static void nativeFinalizeStatement(JNIEnv* env, jclass clazz, jlong connectionP
static jint nativeGetParameterCount(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* statement = reinterpret_cast(statementPtr);
return sqlite3_bind_parameter_count(statement);
}
static jboolean nativeIsReadOnly(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* statement = reinterpret_cast(statementPtr);
return sqlite3_stmt_readonly(statement) != 0;
}
static jint nativeGetColumnCount(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* statement = reinterpret_cast(statementPtr);
return sqlite3_column_count(statement);
}
static jstring nativeGetColumnName(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index) {
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
- const jchar* name = static_cast(sqlite3_column_name16(statement, index));
+ auto* statement = reinterpret_cast(statementPtr);
+ const auto* name = static_cast(sqlite3_column_name16(statement, index));
if (name) {
size_t length = 0;
while (name[length]) {
@@ -418,14 +418,13 @@ static jstring nativeGetColumnName(JNIEnv* env, jclass clazz, jlong connectionPt
}
return env->NewString(name, length);
}
- return NULL;
+ return nullptr;
}
static void nativeBindNull(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = sqlite3_bind_null(statement, index);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
@@ -434,9 +433,8 @@ static void nativeBindNull(JNIEnv* env, jclass clazz, jlong connectionPtr,
static void nativeBindLong(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index, jlong value) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = sqlite3_bind_int64(statement, index, value);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
@@ -445,9 +443,8 @@ static void nativeBindLong(JNIEnv* env, jclass clazz, jlong connectionPtr,
static void nativeBindDouble(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index, jdouble value) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = sqlite3_bind_double(statement, index, value);
if (err != SQLITE_OK) {
throw_sqlite3_exception(env, connection->db, NULL);
@@ -456,9 +453,8 @@ static void nativeBindDouble(JNIEnv* env, jclass clazz, jlong connectionPtr,
static void nativeBindString(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index, jstring valueString) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
jsize valueLength = env->GetStringLength(valueString);
const jchar* value = env->GetStringCritical(valueString, NULL);
int err = sqlite3_bind_text16(statement, index, value, valueLength * sizeof(jchar),
@@ -471,11 +467,10 @@ static void nativeBindString(JNIEnv* env, jclass clazz, jlong connectionPtr,
static void nativeBindBlob(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr, jint index, jbyteArray valueArray) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
jsize valueLength = env->GetArrayLength(valueArray);
- jbyte* value = static_cast(env->GetPrimitiveArrayCritical(valueArray, NULL));
+ auto* value = static_cast(env->GetPrimitiveArrayCritical(valueArray, NULL));
int err = sqlite3_bind_blob(statement, index, value, valueLength, SQLITE_TRANSIENT);
env->ReleasePrimitiveArrayCritical(valueArray, value, JNI_ABORT);
if (err != SQLITE_OK) {
@@ -485,9 +480,8 @@ static void nativeBindBlob(JNIEnv* env, jclass clazz, jlong connectionPtr,
static void nativeResetStatementAndClearBindings(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = sqlite3_reset(statement);
if (err == SQLITE_OK) {
err = sqlite3_clear_bindings(statement);
@@ -507,9 +501,8 @@ static int executeNonQueryRaw(JNIEnv* env, SQLiteConnection* connection, sqlite3
static void nativeExecuteRaw(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
executeNonQueryRaw(env, connection, statement);
}
@@ -526,26 +519,23 @@ static int executeNonQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3_st
static void nativeExecute(JNIEnv* env, jclass clazz, jlong connectionPtr,
jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
executeNonQuery(env, connection, statement);
}
static jint nativeExecuteForChangedRowCount(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = executeNonQuery(env, connection, statement);
return err == SQLITE_DONE ? sqlite3_changes(connection->db) : -1;
}
static jlong nativeExecuteForLastInsertedRowId(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = executeNonQuery(env, connection, statement);
return err == SQLITE_DONE && sqlite3_changes(connection->db) > 0
? sqlite3_last_insert_rowid(connection->db) : -1;
@@ -561,9 +551,8 @@ static int executeOneRowQuery(JNIEnv* env, SQLiteConnection* connection, sqlite3
static jlong nativeExecuteForLong(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = executeOneRowQuery(env, connection, statement);
if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
return sqlite3_column_int64(statement, 0);
@@ -573,9 +562,8 @@ static jlong nativeExecuteForLong(JNIEnv* env, jclass clazz,
static jstring nativeExecuteForString(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = executeOneRowQuery(env, connection, statement);
if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
const jchar* text = static_cast(sqlite3_column_text16(statement, 0));
@@ -584,7 +572,7 @@ static jstring nativeExecuteForString(JNIEnv* env, jclass clazz,
return env->NewString(text, length);
}
}
- return NULL;
+ return nullptr;
}
static int createAshmemRegionWithData(JNIEnv* env, const void* data, size_t length) {
@@ -594,9 +582,8 @@ static int createAshmemRegionWithData(JNIEnv* env, const void* data, size_t leng
static jint nativeExecuteForBlobFileDescriptor(JNIEnv* env, jclass clazz,
jlong connectionPtr, jlong statementPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
- sqlite3_stmt* statement = reinterpret_cast(statementPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
int err = executeOneRowQuery(env, connection, statement);
if (err == SQLITE_ROW && sqlite3_column_count(statement) >= 1) {
const void* blob = sqlite3_column_blob(statement, 0);
@@ -610,245 +597,203 @@ static jint nativeExecuteForBlobFileDescriptor(JNIEnv* env, jclass clazz,
return -1;
}
-/*
-** Note: The following symbols must be in the same order as the corresponding
-** elements in the aMethod[] array in function nativeExecuteForCursorWindow().
-*/
-enum CWMethodNames {
- CW_CLEAR = 0,
- CW_SETNUMCOLUMNS = 1,
- CW_ALLOCROW = 2,
- CW_FREELASTROW = 3,
- CW_PUTNULL = 4,
- CW_PUTLONG = 5,
- CW_PUTDOUBLE = 6,
- CW_PUTSTRING = 7,
- CW_PUTBLOB = 8
-};
-
-/*
-** An instance of this structure represents a single CursorWindow java method.
-*/
-struct CWMethod {
- jmethodID id; /* Method id */
- const char *zName; /* Method name */
- const char *zSig; /* Method JNI signature */
+enum CopyRowResult {
+ CPR_OK,
+ CPR_FULL,
+ CPR_ERROR,
};
-/*
-** Append the contents of the row that SQL statement pStmt currently points to
-** to the CursorWindow object passed as the second argument. The CursorWindow
-** currently contains iRow rows. Return true on success or false if an error
-** occurs.
-*/
-static jboolean copyRowToWindow(
- JNIEnv *pEnv,
- jobject win,
- int iRow,
- sqlite3_stmt *pStmt,
- CWMethod *aMethod
-){
- int nCol = sqlite3_column_count(pStmt);
- int i;
- jboolean bOk;
-
- bOk = pEnv->CallBooleanMethod(win, aMethod[CW_ALLOCROW].id);
- for(i=0; bOk && iCallBooleanMethod(win, aMethod[CW_PUTNULL].id, iRow, i);
- break;
- }
-
- case SQLITE_INTEGER: {
- jlong val = sqlite3_column_int64(pStmt, i);
- bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTLONG].id, val, iRow, i);
- break;
- }
-
- case SQLITE_FLOAT: {
- jdouble val = sqlite3_column_double(pStmt, i);
- bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTDOUBLE].id, val, iRow, i);
- break;
- }
-
- case SQLITE_TEXT: {
- jchar *pStr = (jchar*)sqlite3_column_text16(pStmt, i);
- int nStr = sqlite3_column_bytes16(pStmt, i) / sizeof(jchar);
- jstring val = pEnv->NewString(pStr, nStr);
- bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTSTRING].id, val, iRow, i);
- pEnv->DeleteLocalRef(val);
- break;
- }
-
- default: {
- assert( sqlite3_column_type(pStmt, i)==SQLITE_BLOB );
- const jbyte *p = (const jbyte*)sqlite3_column_blob(pStmt, i);
- int n = sqlite3_column_bytes(pStmt, i);
- jbyteArray val = pEnv->NewByteArray(n);
- pEnv->SetByteArrayRegion(val, 0, n, p);
- bOk = pEnv->CallBooleanMethod(win, aMethod[CW_PUTBLOB].id, val, iRow, i);
- pEnv->DeleteLocalRef(val);
- break;
- }
- }
-
- if( bOk==0 ){
- pEnv->CallVoidMethod(win, aMethod[CW_FREELASTROW].id);
+static CopyRowResult copyRow(JNIEnv* env, CursorWindow* window,
+ sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
+ // Allocate a new field directory for the row.
+ status_t status = window->allocRow();
+ if (status) {
+ ALOGD("Failed allocating fieldDir at startPos %d row %d, error=%d",
+ startPos, addedRows, status);
+ return CPR_FULL;
+ }
+
+ // Pack the row into the window.
+ CopyRowResult result = CPR_OK;
+ for (int i = 0; i < numColumns; i++) {
+ int type = sqlite3_column_type(statement, i);
+ if (type == SQLITE_TEXT) {
+ // TEXT data
+ const char* text = reinterpret_cast(
+ sqlite3_column_text(statement, i));
+ // SQLite does not include the NULL terminator in size, but does
+ // ensure all strings are NULL terminated, so increase size by
+ // one to make sure we store the terminator.
+ size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
+ status = window->putString(addedRows, i, text, sizeIncludingNull);
+ if (status) {
+ ALOGD("Failed allocating %zu bytes for text at %d,%d, error=%d",
+ sizeIncludingNull, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ ALOGD("%d,%d is TEXT with %zu bytes",
+ startPos + addedRows, i, sizeIncludingNull);
+ } else if (type == SQLITE_INTEGER) {
+ // INTEGER data
+ int64_t value = sqlite3_column_int64(statement, i);
+ status = window->putLong(addedRows, i, value);
+ if (status) {
+ ALOGD("Failed allocating space for a long in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ ALOGD("%d,%d is INTEGER 0x%016" PRIx64, startPos + addedRows, i, value);
+ } else if (type == SQLITE_FLOAT) {
+ // FLOAT data
+ double value = sqlite3_column_double(statement, i);
+ status = window->putDouble(addedRows, i, value);
+ if (status) {
+ ALOGD("Failed allocating space for a double in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ ALOGD("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
+ } else if (type == SQLITE_BLOB) {
+ // BLOB data
+ const void* blob = sqlite3_column_blob(statement, i);
+ size_t size = sqlite3_column_bytes(statement, i);
+ status = window->putBlob(addedRows, i, blob, size);
+ if (status) {
+ ALOGD("Failed allocating %zu bytes for blob at %d,%d, error=%d",
+ size, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ ALOGD("%d,%d is Blob with %zu bytes",
+ startPos + addedRows, i, size);
+ } else if (type == SQLITE_NULL) {
+ // NULL field
+ status = window->putNull(addedRows, i);
+ if (status) {
+ ALOGD("Failed allocating space for a null in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ ALOGD("%d,%d is NULL", startPos + addedRows, i);
+ } else {
+ // Unknown data
+ ALOGE("Unknown column type when filling database window");
+ throw_sqlite3_exception(env, "Unknown column type when filling window");
+ result = CPR_ERROR;
+ break;
+ }
}
- }
-
- return bOk;
-}
-
-static jboolean setWindowNumColumns(
- JNIEnv *pEnv,
- jobject win,
- sqlite3_stmt *pStmt,
- CWMethod *aMethod
-){
- int nCol;
- pEnv->CallVoidMethod(win, aMethod[CW_CLEAR].id);
- nCol = sqlite3_column_count(pStmt);
- return pEnv->CallBooleanMethod(win, aMethod[CW_SETNUMCOLUMNS].id, (jint)nCol);
+ // Free the last row if if was not successfully copied.
+ if (result != CPR_OK) {
+ window->freeLastRow();
+ }
+ return result;
}
-/*
-** This method has been rewritten for org.sqlite.database.*. The original
-** android implementation used the C++ interface to populate a CursorWindow
-** object. Since the NDK does not export this interface, we invoke the Java
-** interface using standard JNI methods to do the same thing.
-**
-** This function executes the SQLite statement object passed as the 4th
-** argument and copies one or more returned rows into the CursorWindow
-** object passed as the 5th argument. The set of rows copied into the
-** CursorWindow is always contiguous.
-**
-** The only row that *must* be copied into the CursorWindow is row
-** iRowRequired. Ideally, all rows from iRowStart through to the end
-** of the query are copied into the CursorWindow. If this is not possible
-** (CursorWindow objects have a finite capacity), some compromise position
-** is found (see comments embedded in the code below for details).
-**
-** The return value is a 64-bit integer calculated as follows:
-**
-** (iStart << 32) | nRow
-**
-** where iStart is the index of the first row copied into the CursorWindow.
-** If the countAllRows argument is true, nRow is the total number of rows
-** returned by the query. Otherwise, nRow is one greater than the index of
-** the last row copied into the CursorWindow.
-*/
static jlong nativeExecuteForCursorWindow(
- JNIEnv *pEnv,
- jclass clazz,
- jlong connectionPtr, /* Pointer to SQLiteConnection C++ object */
- jlong statementPtr, /* Pointer to sqlite3_stmt object */
- jobject win, /* The CursorWindow object to populate */
- jint startPos, /* First row to add (advisory) */
- jint iRowRequired, /* Required row */
- jboolean countAllRows
-) {
- SQLiteConnection *pConnection = reinterpret_cast(connectionPtr);
- sqlite3_stmt *pStmt = reinterpret_cast(statementPtr);
-
- CWMethod aMethod[] = {
- {0, "clear", "()V"},
- {0, "setNumColumns", "(I)Z"},
- {0, "allocRow", "()Z"},
- {0, "freeLastRow", "()V"},
- {0, "putNull", "(II)Z"},
- {0, "putLong", "(JII)Z"},
- {0, "putDouble", "(DII)Z"},
- {0, "putString", "(Ljava/lang/String;II)Z"},
- {0, "putBlob", "([BII)Z"},
- };
- jclass cls; /* Class android.database.CursorWindow */
- int i; /* Iterator variable */
- int nCol; /* Number of columns returned by pStmt */
- int nRow;
- jboolean bOk;
- int iStart; /* First row copied to CursorWindow */
- int rc_step;
- /* Locate all required CursorWindow methods. */
- cls = pEnv->FindClass("android/database/CursorWindow");
- for(i=0; i<(sizeof(aMethod)/sizeof(struct CWMethod)); i++){
- aMethod[i].id = pEnv->GetMethodID(cls, aMethod[i].zName, aMethod[i].zSig);
- if( aMethod[i].id==NULL ){
- jniThrowExceptionFmt(pEnv, "java/lang/Exception",
- "Failed to find method CursorWindow.%s()", aMethod[i].zName
- );
- return 0;
+ JNIEnv* env,
+ jclass clazz,
+ jlong connectionPtr,
+ jlong statementPtr,
+ jlong windowPtr,
+ jint startPos,
+ jint requiredPos,
+ jboolean countAllRows){
+ auto* connection = reinterpret_cast(connectionPtr);
+ auto* statement = reinterpret_cast(statementPtr);
+ auto* window = reinterpret_cast(windowPtr);
+ status_t status = window->clear();
+ if (status) {
+ throw_sqlite3_exception(env, connection->db, "Failed to clear the cursor window");
+ return 0;
}
- }
+ int numColumns = sqlite3_column_count(statement);
+ status = window->setNumColumns(numColumns);
+ if (status) {
+ throw_sqlite3_exception(env, connection->db, "Failed to set the cursor window column count");
+ return 0;
+ }
+ int retryCount = 0;
+ int totalRows = 0;
+ int addedRows = 0;
+ bool windowFull = false;
+ bool gotException = false;
+ while (!gotException && (!windowFull || countAllRows)) {
+ int err = sqlite3_step(statement);
+ if (err == SQLITE_ROW) {
+ ALOGD("Stepped statement %p to row %d", statement, totalRows);
+ retryCount = 0;
+ totalRows += 1;
+
+ // Skip the row if the window is full or we haven't reached the start position yet.
+ if (startPos >= totalRows || windowFull) {
+ continue;
+ }
+ CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+ if (cpr == CPR_FULL && addedRows && startPos + addedRows <= requiredPos) {
+ // We filled the window before we got to the one row that we really wanted.
+ // Clear the window and start filling it again from here.
+ // TODO: Would be nicer if we could progressively replace earlier rows.
+ window->clear();
+ window->setNumColumns(numColumns);
+ startPos += addedRows;
+ addedRows = 0;
+ cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+ if(cpr == CPR_FULL){
+ throw_sqlite3_exception(env, "Row too big to fit in CursorWindow");
+ }
+ }
- /* Set the number of columns in the window */
- bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
- if( bOk==0 ) return 0;
-
- nRow = 0;
- iStart = startPos;
- while( (rc_step = sqlite3_step(pStmt))==SQLITE_ROW ){
- /* Only copy in rows that occur at or after row index iStart. */
- if( nRow>=iStart && bOk ){
- bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
- if( bOk==0 ){
- /* The CursorWindow object ran out of memory. If row iRowRequired was
- ** not successfully added before this happened, clear the CursorWindow
- ** and try to add the current row again. */
- if( nRow<=iRowRequired ){
- bOk = setWindowNumColumns(pEnv, win, pStmt, aMethod);
- if( bOk==0 ){
- sqlite3_reset(pStmt);
- return 0;
- }
- iStart = nRow;
- bOk = copyRowToWindow(pEnv, win, (nRow - iStart), pStmt, aMethod);
+ if (cpr == CPR_OK) {
+ addedRows += 1;
+ } else if (cpr == CPR_FULL) {
+ windowFull = true;
+ } else {
+ gotException = true;
+ }
+ } else if (err == SQLITE_DONE) {
+ // All rows processed, bail
+ ALOGD("Processed all rows");
+ break;
+ } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
+ // The table is locked, retry
+ ALOGD("Database locked, retrying");
+ if (retryCount > 50) {
+ ALOGE("Bailing on database busy retry");
+ throw_sqlite3_exception(env, connection->db, "retrycount exceeded");
+ gotException = true;
+ } else {
+ // Sleep to give the thread holding the lock a chance to finish
+ usleep(1000);
+ retryCount++;
+ }
+ } else {
+ throw_sqlite3_exception(env, connection->db);
+ gotException = true;
}
-
- /* If the CursorWindow is still full and the countAllRows flag is not
- ** set, break out of the loop here. If countAllRows is set, continue
- ** so as to set variable nRow correctly. */
- if( bOk==0 && countAllRows==0 ) break;
- }
}
- nRow++;
- }
-
- /* Finalize the statement. If this indicates an error occurred, throw an
- ** SQLiteException exception. */
- int rc = sqlite3_reset(pStmt);
- if( rc!=SQLITE_OK ){
- throw_sqlite3_exception(pEnv, sqlite3_db_handle(pStmt));
- return 0;
- }
-
- if( bOk==0 && countAllRows==0 && nRow <= iRowRequired ) {
- char *msg = sqlite3_mprintf("Row too big to fit into CursorWindow requiredPos=%d, totalRows=%d",
- iRowRequired, nRow);
- throw_sqlite3_exception(pEnv, pConnection->db, msg);
- sqlite3_free(msg);
- return 0;
- }
+ ALOGD("Resetting statement %p after fetching %d rows and adding %d rows"
+ "to the window in %zu bytes",
+ statement, totalRows, addedRows, window->size() - window->freeSpace());
+ sqlite3_reset(statement);
- if ( rc_step != SQLITE_DONE && rc_step != SQLITE_ROW ) {
- char *msg = sqlite3_mprintf("Unexpected result code=%d while stepping through result",
- rc_step);
- throw_sqlite3_exception(pEnv, pConnection->db, msg);
- sqlite3_free(msg);
- return 0;
- }
-
- jlong lRet = jlong(iStart) << 32 | jlong(nRow);
- return lRet;
+ // Report the total number of rows on request.
+ if (startPos > totalRows) {
+ ALOGE("startPos %d > actual rows %d", startPos, totalRows);
+ }
+ jlong result = jlong(startPos) << 32 | jlong(totalRows);
+ return result;
}
static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jlong connectionPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
-
+ auto* connection = reinterpret_cast(connectionPtr);
int cur = -1;
int unused;
sqlite3_db_status(connection->db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &unused, 0);
@@ -856,15 +801,14 @@ static jint nativeGetDbLookaside(JNIEnv* env, jobject clazz, jlong connectionPtr
}
static void nativeCancel(JNIEnv* env, jobject clazz, jlong connectionPtr) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ auto* connection = reinterpret_cast(connectionPtr);
connection->canceled = true;
}
static void nativeResetCancel(JNIEnv* env, jobject clazz, jlong connectionPtr,
jboolean cancelable) {
- SQLiteConnection* connection = reinterpret_cast(connectionPtr);
+ auto* connection = reinterpret_cast(connectionPtr);
connection->canceled = false;
-
if (cancelable) {
sqlite3_progress_handler(connection->db, 4, sqliteProgressHandlerCallback,
connection);
@@ -935,7 +879,7 @@ static JNINativeMethod sMethods[] =
(void*)nativeExecuteForChangedRowCount },
{ "nativeExecuteForLastInsertedRowId", "(JJ)J",
(void*)nativeExecuteForLastInsertedRowId },
- { "nativeExecuteForCursorWindow", "(JJLandroid/database/CursorWindow;IIZ)J",
+ { "nativeExecuteForCursorWindow", "(JJJIIZ)J",
(void*)nativeExecuteForCursorWindow },
{ "nativeGetDbLookaside", "(J)I",
(void*)nativeGetDbLookaside },
@@ -947,18 +891,6 @@ static JNINativeMethod sMethods[] =
{ "nativeHasCodec", "()Z", (void*)nativeHasCodec },
};
-#define FIND_CLASS(var, className) \
- var = env->FindClass(className); \
- LOG_FATAL_IF(! var, "Unable to find class " className);
-
-#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
- var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
- LOG_FATAL_IF(! var, "Unable to find method" methodName);
-
-#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
- var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
- LOG_FATAL_IF(! var, "Unable to find field " fieldName);
-
int register_android_database_SQLiteConnection(JNIEnv *env)
{
jclass clazz;
@@ -982,6 +914,7 @@ int register_android_database_SQLiteConnection(JNIEnv *env)
extern int register_android_database_SQLiteGlobal(JNIEnv *env);
extern int register_android_database_SQLiteDebug(JNIEnv *env);
+extern int register_android_database_CursorWindow(JNIEnv *env);
} // namespace android
@@ -994,6 +927,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
android::register_android_database_SQLiteConnection(env);
android::register_android_database_SQLiteDebug(env);
android::register_android_database_SQLiteGlobal(env);
+ android::register_android_database_CursorWindow(env);
return JNI_VERSION_1_4;
}
diff --git a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteDebug.cpp b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteDebug.cpp
index d716ee4..e3d3f56 100644
--- a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteDebug.cpp
+++ b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteDebug.cpp
@@ -24,9 +24,9 @@
#include
#include
-#include
-#include
-#include
+#include
+#include
+#include
#include
#include
@@ -76,14 +76,14 @@ static JNINativeMethod gMethods[] =
int register_android_database_SQLiteDebug(JNIEnv *env)
{
jclass clazz;
- FIND_CLASS(clazz, "net/zetetic/database/sqlcipher/SQLiteDebug$PagerStats");
+ FIND_CLASS(clazz, "net/zetetic/database/sqlcipher/SQLiteDebug$PagerStats")
GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.memoryUsed, clazz,
- "memoryUsed", "I");
+ "memoryUsed", "I")
GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.largestMemAlloc, clazz,
- "largestMemAlloc", "I");
+ "largestMemAlloc", "I")
GET_FIELD_ID(gSQLiteDebugPagerStatsClassInfo.pageCacheOverflow, clazz,
- "pageCacheOverflow", "I");
+ "pageCacheOverflow", "I")
return jniRegisterNativeMethods(env, "net/zetetic/database/sqlcipher/SQLiteDebug",
gMethods, NELEM(gMethods));
diff --git a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteGlobal.cpp b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteGlobal.cpp
index aa6ab07..de8e697 100644
--- a/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteGlobal.cpp
+++ b/sqlcipher/src/main/jni/sqlcipher/android_database_SQLiteGlobal.cpp
@@ -35,6 +35,8 @@ namespace android {
// a long time.
static const int SOFT_HEAP_LIMIT = 8 * 1024 * 1024;
+#undef LOG_TAG
+#define LOG_TAG SQLITE_LOG_TAG
// Called each time a message is logged.
static void sqliteLogCallback(void* data, int err, const char* msg) {
@@ -43,12 +45,12 @@ static void sqliteLogCallback(void* data, int err, const char* msg) {
if (errType == 0 || errType == SQLITE_CONSTRAINT || errType == SQLITE_SCHEMA
|| errType == SQLITE_NOTICE || err == SQLITE_WARNING_AUTOINDEX) {
if (verboseLog) {
- ALOG(LOG_VERBOSE, SQLITE_LOG_TAG, "(%d) %s\n", err, msg);
+ ALOGV("(%d) %s\n", err, msg);
}
} else if (errType == SQLITE_WARNING) {
- ALOG(LOG_WARN, SQLITE_LOG_TAG, "(%d) %s\n", err, msg);
+ ALOGW("(%d) %s\n", err, msg);
} else {
- ALOG(LOG_ERROR, SQLITE_LOG_TAG, "(%d) %s\n", err, msg);
+ ALOGE("(%d) %s\n", err, msg);
}
}
diff --git a/sqlcipher/src/main/jni/sqlcipher/nativehelper/JNIHelp.h b/sqlcipher/src/main/jni/sqlcipher/nativehelper/JNIHelp.h
index 758b7c7..34f04df 100644
--- a/sqlcipher/src/main/jni/sqlcipher/nativehelper/JNIHelp.h
+++ b/sqlcipher/src/main/jni/sqlcipher/nativehelper/JNIHelp.h
@@ -188,4 +188,16 @@ inline void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowab
_rc; })
#endif
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
+ var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method" methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
#endif /* NATIVEHELPER_JNIHELP_H_ */