This commit is contained in:
Vitor Santos Costa
2016-04-05 08:18:09 +01:00
parent 106e4bc13f
commit d680fa0755
20 changed files with 191713 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.sqlite.app.customsqlite"
android:versionCode="1"
android:versionName="1.0">
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
<activity android:name="CustomSqlite"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,17 @@
# This file is used to override default values used by the Ant build system.
#
# This file must be checked into Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="CustomSqlite" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

View File

@@ -0,0 +1,4 @@
LOCAL_PATH:= $(call my-dir)
include $(LOCAL_PATH)/sqlite/Android.mk

View File

@@ -0,0 +1 @@
APP_STL:=stlport_static

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
# location of the SDK. This is only used by Ant
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=/home/dan/adt-bundle-linux-x86-20131030/sdk/

View File

@@ -0,0 +1,20 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -0,0 +1,14 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-19

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="CustomSqlite Tests"
android:typeface="monospace"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Run the tests"
android:onClick="run_the_tests"
/>
<TextView
android:id="@+id/tv_widget"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="&lt;this text should be replaced by the test output&gt;"
android:typeface="monospace"
/>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">CustomSqlite</string>
</resources>

View File

@@ -0,0 +1,416 @@
package org.sqlite.app.customsqlite;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
import java.lang.InterruptedException;
import org.sqlite.database.sqlite.SQLiteDatabase;
import org.sqlite.database.sqlite.SQLiteStatement;
import org.sqlite.database.sqlite.SQLiteDatabaseCorruptException;
import org.sqlite.database.sqlite.SQLiteOpenHelper;
import android.database.Cursor;
import android.content.Context;
/*
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
*/
import org.sqlite.database.DatabaseErrorHandler;
class DoNotDeleteErrorHandler implements DatabaseErrorHandler {
private static final String TAG = "DoNotDeleteErrorHandler";
public void onCorruption(SQLiteDatabase dbObj) {
Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath());
}
}
public class CustomSqlite extends Activity
{
private TextView myTV; /* Text view widget */
private int myNTest; /* Number of tests attempted */
private int myNErr; /* Number of tests failed */
File DB_PATH;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
myTV = (TextView)findViewById(R.id.tv_widget);
}
public void report_version(){
SQLiteDatabase db = null;
SQLiteStatement st;
String res;
db = SQLiteDatabase.openOrCreateDatabase(":memory:", null);
st = db.compileStatement("SELECT sqlite_version()");
res = st.simpleQueryForString();
myTV.append("SQLite version " + res + "\n\n");
}
public void test_warning(String name, String warning){
myTV.append("WARNING:" + name + ": " + warning + "\n");
}
public void test_result(String name, String res, String expected){
myTV.append(name + "... ");
myNTest++;
if( res.equals(expected) ){
myTV.append("ok\n");
} else {
myNErr++;
myTV.append("FAILED\n");
myTV.append(" res= \"" + res + "\"\n");
myTV.append(" expected=\"" + expected + "\"\n");
}
}
/*
** Test if the database at DB_PATH is encrypted or not. The db
** is assumed to be encrypted if the first 6 bytes are anything
** other than "SQLite".
**
** If the test reveals that the db is encrypted, return the string
** "encrypted". Otherwise, "unencrypted".
*/
public String db_is_encrypted() throws Exception {
FileInputStream in = new FileInputStream(DB_PATH);
byte[] buffer = new byte[6];
in.read(buffer, 0, 6);
String res = "encrypted";
if( Arrays.equals(buffer, (new String("SQLite")).getBytes()) ){
res = "unencrypted";
}
return res;
}
/*
** Test that a database connection may be accessed from a second thread.
*/
public void thread_test_1(){
SQLiteDatabase.deleteDatabase(DB_PATH);
final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
String db_path2 = DB_PATH.toString() + "2";
db.execSQL("CREATE TABLE t1(x, y)");
db.execSQL("INSERT INTO t1 VALUES (1, 2), (3, 4)");
Thread t = new Thread( new Runnable() {
public void run() {
SQLiteStatement st = db.compileStatement("SELECT sum(x+y) FROM t1");
String res = st.simpleQueryForString();
test_result("thread_test_1", res, "10");
}
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
}
}
/*
** Test that a database connection may be accessed from a second thread.
*/
public void thread_test_2(){
SQLiteDatabase.deleteDatabase(DB_PATH);
final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
db.execSQL("CREATE TABLE t1(x, y)");
db.execSQL("INSERT INTO t1 VALUES (1, 2), (3, 4)");
db.enableWriteAheadLogging();
db.beginTransactionNonExclusive();
db.execSQL("INSERT INTO t1 VALUES (5, 6)");
Thread t = new Thread( new Runnable() {
public void run() {
SQLiteStatement st = db.compileStatement("SELECT sum(x+y) FROM t1");
String res = st.simpleQueryForString();
}
});
t.start();
String res = "concurrent";
int i;
for(i=0; i<20 && t.isAlive(); i++){
try { Thread.sleep(100); } catch(InterruptedException e) {}
}
if( t.isAlive() ){ res = "blocked"; }
db.endTransaction();
try { t.join(); } catch(InterruptedException e) {}
if( SQLiteDatabase.hasCodec() ){
test_result("thread_test_2", res, "blocked");
} else {
test_result("thread_test_2", res, "concurrent");
}
}
/*
** Use a Cursor to loop through the results of a SELECT query.
*/
public void csr_test_2() throws Exception {
SQLiteDatabase.deleteDatabase(DB_PATH);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
String res = "";
String expect = "";
int i;
int nRow = 0;
db.execSQL("CREATE TABLE t1(x)");
db.execSQL("BEGIN");
for(i=0; i<1000; i++){
db.execSQL("INSERT INTO t1 VALUES ('one'), ('two'), ('three')");
expect += ".one.two.three";
}
db.execSQL("COMMIT");
Cursor c = db.rawQuery("SELECT x FROM t1", null);
if( c!=null ){
boolean bRes;
for(bRes=c.moveToFirst(); bRes; bRes=c.moveToNext()){
String x = c.getString(0);
res = res + "." + x;
}
}else{
test_warning("csr_test_1", "c==NULL");
}
test_result("csr_test_2.1", res, expect);
db.execSQL("BEGIN");
for(i=0; i<1000; i++){
db.execSQL("INSERT INTO t1 VALUES (X'123456'), (X'789ABC'), (X'DEF012')");
db.execSQL("INSERT INTO t1 VALUES (45), (46), (47)");
db.execSQL("INSERT INTO t1 VALUES (8.1), (8.2), (8.3)");
db.execSQL("INSERT INTO t1 VALUES (NULL), (NULL), (NULL)");
}
db.execSQL("COMMIT");
c = db.rawQuery("SELECT x FROM t1", null);
if( c!=null ){
boolean bRes;
for(bRes=c.moveToFirst(); bRes; bRes=c.moveToNext()) nRow++;
}else{
test_warning("csr_test_1", "c==NULL");
}
test_result("csr_test_2.2", "" + nRow, "15000");
db.close();
}
public String string_from_t1_x(SQLiteDatabase db){
String res = "";
Cursor c = db.rawQuery("SELECT x FROM t1", null);
boolean bRes;
for(bRes=c.moveToFirst(); bRes; bRes=c.moveToNext()){
String x = c.getString(0);
res = res + "." + x;
}
return res;
}
public void csr_test_1() throws Exception {
SQLiteDatabase.deleteDatabase(DB_PATH);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
String res = "";
db.execSQL("CREATE TABLE t1(x)");
db.execSQL("INSERT INTO t1 VALUES ('one'), ('two'), ('three')");
res = string_from_t1_x(db);
test_result("csr_test_1.1", res, ".one.two.three");
db.close();
test_result("csr_test_1.2", db_is_encrypted(), "unencrypted");
}
public void stmt_jrnl_test_1() throws Exception {
SQLiteDatabase.deleteDatabase(DB_PATH);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
String res = "";
db.execSQL("CREATE TABLE t1(x, y UNIQUE)");
db.execSQL("BEGIN");
db.execSQL("INSERT INTO t1 VALUES(1, 1), (2, 2), (3, 3)");
db.execSQL("UPDATE t1 SET y=y+3");
db.execSQL("COMMIT");
db.close();
test_result("stmt_jrnl_test_1.1", "did not crash", "did not crash");
}
public void supp_char_test_1() throws Exception {
SQLiteDatabase.deleteDatabase(DB_PATH);
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
String res = "";
String smiley = new String( Character.toChars(0x10000) );
db.execSQL("CREATE TABLE t1(x)");
db.execSQL("INSERT INTO t1 VALUES ('a" + smiley + "b')");
res = string_from_t1_x(db);
test_result("supp_char_test1." + smiley, res, ".a" + smiley + "b");
db.close();
}
/*
** If this is a SEE build, check that encrypted databases work.
*/
public void see_test_1() throws Exception {
if( !SQLiteDatabase.hasCodec() ) return;
SQLiteDatabase.deleteDatabase(DB_PATH);
String res = "";
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
db.execSQL("PRAGMA key = 'secretkey'");
db.execSQL("CREATE TABLE t1(x)");
db.execSQL("INSERT INTO t1 VALUES ('one'), ('two'), ('three')");
res = string_from_t1_x(db);
test_result("see_test_1.1", res, ".one.two.three");
db.close();
test_result("see_test_1.2", db_is_encrypted(), "encrypted");
db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null);
db.execSQL("PRAGMA key = 'secretkey'");
res = string_from_t1_x(db);
test_result("see_test_1.3", res, ".one.two.three");
db.close();
res = "unencrypted";
try {
db = SQLiteDatabase.openOrCreateDatabase(DB_PATH.getPath(), null);
string_from_t1_x(db);
} catch ( SQLiteDatabaseCorruptException e ){
res = "encrypted";
} finally {
db.close();
}
test_result("see_test_1.4", res, "encrypted");
res = "unencrypted";
try {
db = SQLiteDatabase.openOrCreateDatabase(DB_PATH.getPath(), null);
db.execSQL("PRAGMA key = 'otherkey'");
string_from_t1_x(db);
} catch ( SQLiteDatabaseCorruptException e ){
res = "encrypted";
} finally {
db.close();
}
test_result("see_test_1.5", res, "encrypted");
}
class MyHelper extends SQLiteOpenHelper {
public MyHelper(Context ctx){
super(ctx, DB_PATH.getPath(), null, 1);
}
public void onConfigure(SQLiteDatabase db){
db.execSQL("PRAGMA key = 'secret'");
}
public void onCreate(SQLiteDatabase db){
db.execSQL("CREATE TABLE t1(x)");
}
public void onUpgrade(SQLiteDatabase db, int iOld, int iNew){
}
}
/*
** Check that SQLiteOpenHelper works.
*/
public void helper_test_1() throws Exception {
/* SQLiteDatabase.deleteDatabase(DB_PATH); */
MyHelper helper = new MyHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("INSERT INTO t1 VALUES ('x'), ('y'), ('z')");
String res = string_from_t1_x(db);
test_result("helper.1", res, ".x.y.z");
helper.close();
}
/*
** If this is a SEE build, check that SQLiteOpenHelper still works.
*/
public void see_test_2() throws Exception {
if( !SQLiteDatabase.hasCodec() ) return;
SQLiteDatabase.deleteDatabase(DB_PATH);
MyHelper helper = new MyHelper(this);
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("INSERT INTO t1 VALUES ('x'), ('y'), ('z')");
String res = string_from_t1_x(db);
test_result("see_test_2.1", res, ".x.y.z");
test_result("see_test_2.2", db_is_encrypted(), "encrypted");
helper.close();
helper = new MyHelper(this);
db = helper.getReadableDatabase();
test_result("see_test_2.3", res, ".x.y.z");
db = helper.getWritableDatabase();
test_result("see_test_2.4", res, ".x.y.z");
test_result("see_test_2.5", db_is_encrypted(), "encrypted");
}
public void run_the_tests(View view){
System.loadLibrary("sqliteX");
DB_PATH = getApplicationContext().getDatabasePath("test.db");
DB_PATH.getParentFile().mkdirs();
myTV.setText("");
myNErr = 0;
myNTest = 0;
try {
report_version();
helper_test_1();
supp_char_test_1();
csr_test_1();
csr_test_2();
thread_test_1();
thread_test_2();
see_test_1();
see_test_2();
stmt_jrnl_test_1();
myTV.append("\n" + myNErr + " errors from " + myNTest + " tests\n");
} catch(Exception e) {
myTV.append("Exception: " + e.toString() + "\n");
myTV.append(android.util.Log.getStackTraceString(e) + "\n");
}
}
}

View File

@@ -0,0 +1,215 @@
<h1>
SQLite Android Bindings
</h1>
<p> The SQLite library is a core part of the Android environment. Java
applications and content providers access SQLite using the interface in
the
<a href="http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html">android.database.sqlite</a> namespace.
<p> One disadvantage of using Android's built-in SQLite support is that the
application is forced to use the version of SQLite that the current version of
Android happened to ship with. If your application happens to require a newer
version of SQLite, or a build with a custom extension or
<a href=http://www.sqlite.org/vfs.html>VFS</a> installed, you're out of luck.
<p>The code in this project allows an application to use the
<a href=http://developer.android.com/tools/sdk/ndk/index.html>Android NDK</a>
to build a custom version of SQLite to be shipped with the application while
still continuing to use the standard Java interface.
<h2>Normal Usage</h2>
<h3>Installation</h3>
<p>
Android API levels 15 (Android 4.0.3) and greater are supported. If
targetting API level 16 or greater, use the default "trunk" branch of this
project. Or, for API level 15, use the "api-level-15" branch. It is not possible
to target an API level lower than 15.
<p>
Copy the following files from this project into the equivalent locations in
the application project.
<pre>
jni/Android.mk
jni/Application.mk
jni/sqlite/* (copy contents of directory recursively)
src/org/sqlite/database/* (copy contents of directory recursively)
</pre>
<p>
Following this, the directory structures should contain
[/tree?ci=trunk&re=%5ejni%7csrc/org/sqlite/data&expand | these files].
<p>
For API level 15 only, also copy the following:
<pre>
src/org/sqlite/os/* (copy contents of directory recursively)
</pre>
<p>
Directory "jni/sqlite/" contains copies of the sqlite3.h and sqlite3.c
source files. Between them, they contain the
<a href=http://www.sqlite.org/amalgamation.html>source code for the SQLite
library</a>. If necessary, replace these with the source for the specific
version of SQLite required. If SQLite is to be compiled with any special
pre-processor macros defined, add them to the "jni/sqlite/Android.mk" file
(not jni/Android.mk).
<p>
Once the files have been added to the project, run the command "ndk-build"
in the root directory of the project. This compiles the native code in
the jni/ directory (including the custom SQLite version) to shared libraries
that will be deployed to the device along with the application. Assuming
it is successful, unless you modify the sources or makefiles within the
jni/ directory structure, you should not need to run "ndk-build" again.
<h3>Application Programming</h3>
<p>
Before using any SQLite related methods or objects, the shared library
compiled using the ndk must be loaded into the application using the
following code:
<verbatim>
System.loadLibrary("sqliteX");
</verbatim>
<p>
One way to ensure that the shared library is loaded early enough is
to add it to a "static" block within the declaration of the application's
main Activity class.
<p>
The classes that make up the built-in Android SQLite interface reside in
the "android.database.sqlite" namespace. This interface provides all of
the same classes, except within the "org.sqlite.database.sqlite" namespace.
This means that to modify an application to use the custom version of
SQLite, all that is usually required is to replace all occurrences
"android.database.sqlite" within the source code with
"org.sqlite.database.sqlite". For example, the following:
<verbatim>
import android.database.sqlite.SQLiteDatabase;
</verbatim>
<p>should be replaced with:
<verbatim>
import org.sqlite.database.sqlite.SQLiteDatabase;
</verbatim>
<p>
As well as replacing all uses of the classes in the
android.database.sqlite.* namespace, the application must also be sure
to use the following two:
<verbatim>
org.sqlite.database.SQLException
org.sqlite.database.DatabaseErrorHandler
</verbatim>
<p>instead of:
<verbatim>
android.database.SQLException
android.database.DatabaseErrorHandler
</verbatim>
<p>Aside from namespace changes, there are other differences from the
stock Android interface that applications need to be aware of:
<ol>
<li> The SQLiteStatement.<a href="http://developer.android.com/reference/android/database/sqlite/SQLiteStatement.html#simpleQueryForBlobFileDescriptor()">simpleQueryForBlobFileDescriptor()</a>
API is not available.
<li> The collation sequence "UNICODE" is not available.
<li> The collation sequence "LOCALIZED", which normally changes with the
system's current locale, is always equivalent to SQLite's built
in collation BINARY.
</ol>
<h2>Using The SQLite Encryption Extension</h2>
<p>
To use the <a href=http://www.sqlite.org/see/doc/trunk/www/readme.wiki>
SQLite Encryption Extension</a> (SEE) on Android, replace the sqlite3.c
file at "jni/sqlite/sqlite3.c" with a SEE-enabled version (i.e. the
concatenation of sqlite3.c and see.c - refer to the link above for
details). Next, open the file jni/sqlite/Android.mk and locate the
following two lines:
<verbatim>
# If using SEE, uncomment the following:
# LOCAL_CFLAGS += -DSQLITE_HAS_CODEC
</verbatim>
<p>
Uncomment the second of them, then run "ndk-build" as described above to
generate the shared libraries.
<p>
After opening or creating an encrypted database, the application must
immediately execute a PRAGMA to configure the encryption key. This must
be done before any other database methods are called. For example:
<verbatim>
import org.sqlite.database.sqlite.SQLiteDatabase;
...
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase("my.db", null);
db.execSQL("PRAGMA key = 'secretkey'");
</verbatim>
<p>
Or, if you are using the
<a href=http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html>SQLiteOpenHelper</a>
helper class, the PRAGMA must be the first thing executed within the
onConfigure() callback. For example:
<verbatim>
import org.sqlite.database.sqlite.SQLiteDatabase;
import org.sqlite.database.sqlite.SQLiteHelper;
...
class MyHelper extends SQLiteOpenHelper {
...
void onConfigure(SQLiteDatabase db){
db.execSQL("PRAGMA key = 'secretkey'");
}
...
}
</verbatim>
<p>
Refer to the <a href=http://www.sqlite.org/see/doc/trunk/www/readme.wiki>
SEE documentation</a> for further details regarding encryption keys.
<p>Aside from supporting encrypted databases, SEE-enabled builds behave
differently in two more respects:
<ol>
<li> <p>The SQLiteDatabase.<a href="http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#enableWriteAheadLogging()">enableWriteAheadLogging()</a> method does not enable
connection pooling. It is not possible for connection pooling to be
used with a SEE-enabled build (even if the database is unencrypted).
<li> <p>In Android, if database corruption is encountered, or if an attempt is
made to open a file that is not an SQLite database, the default
behaviour is to delete the file and create an empty database file in
its place. In a SEE-enabled build, the default behaviour is to throw
an exception.<br><br>
The reason for this is that supplying an incorrect encryption key
is indistinguishable from opening a file that is not a database file.
And it seems too dangerous to simply delete the file in this case.
<br><br>
The default behaviour can be overriden using the
<a href="http://developer.android.com/reference/android/database/DatabaseErrorHandler.html">DatabaseErrorHandler</a> interface.
</ol>