Bug 713228 - Add bridge to access our own SQLite libraries from Java. r=blassey
authorGian-Carlo Pascutto <gpascutto@mozilla.com>
Thu, 19 Jan 2012 21:19:56 +0100
changeset 84926 846d6ab488d012e0c75a24f27a82b00cd43d841c
parent 84925 860e4b68b7ef37e3f9c8239f8ca3ab2e9d77b4f0
child 84927 d32def0aee9cfc41e6795012ea2a99d33a3b0068
push id21883
push userbmo@edmorley.co.uk
push dateFri, 20 Jan 2012 01:24:53 +0000
treeherdermozilla-central@5c2bc94d359c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey
bugs713228
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 713228 - Add bridge to access our own SQLite libraries from Java. r=blassey
embedding/android/GeckoAppShell.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoThread.java
mobile/android/base/Makefile.in
mobile/android/base/ProfileMigrator.java
mobile/android/base/sqlite/ByteBufferInputStream.java
mobile/android/base/sqlite/SQLiteBridge.java
mobile/android/base/sqlite/SQLiteBridgeException.java
mozglue/android/APKOpen.cpp
mozglue/android/Makefile.in
mozglue/android/SQLiteBridge.cpp
mozglue/android/SQLiteBridge.h
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -108,17 +108,18 @@ public class GeckoAppShell
 
     // helper methods
     public static native void setSurfaceView(GeckoSurfaceView sv);
     public static native void putenv(String map);
     public static native void onResume();
     public static native void onLowMemory();
     public static native void callObserver(String observerKey, String topic, String data);
     public static native void removeObserver(String observerKey);
-    public static native void loadLibs(String apkName, boolean shouldExtract);
+    public static native void loadGeckoLibsNative(String apkName);
+    public static native void loadSQLiteLibsNative(String apkName, boolean shouldExtract);
     public static native void onChangeNetworkLinkStatus(String status);
     public static native void reportJavaCrash(String stack);
 
     public static native void processNextNativeEvent();
 
     public static native void notifyBatteryChange(double aLevel, boolean aCharging, double aRemainingTime);
 
     public static native void notifySmsReceived(String aSender, String aBody, long aTimestamp);
@@ -391,17 +392,18 @@ public class GeckoAppShell
                 Iterator cacheFiles = Arrays.asList(files).iterator();
                 while (cacheFiles.hasNext()) {
                     File libFile = (File)cacheFiles.next();
                     if (libFile.getName().endsWith(".so"))
                         libFile.delete();
                 }
             }
         }
-        loadLibs(apkName, extractLibs);
+        loadSQLiteLibsNative(apkName, extractLibs);
+        loadGeckoLibsNative(apkName);
     }
 
     private static void putLocaleEnv() {
         GeckoAppShell.putenv("LANG=" + Locale.getDefault().toString());
         NumberFormat nf = NumberFormat.getInstance();
         if (nf instanceof DecimalFormat) {
             DecimalFormat df = (DecimalFormat)nf;
             DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -2096,19 +2096,20 @@ abstract public class GeckoApp
         }
         return status;
     }
 
     private void checkMigrateProfile() {
         File profileDir = getProfileDir();
         if (profileDir != null) {
             Log.i(LOGTAG, "checking profile migration in: " + profileDir.getAbsolutePath());
+            final GeckoApp app = GeckoApp.mAppContext;
+            GeckoAppShell.ensureSQLiteLibsLoaded(app.getApplication().getPackageResourcePath());
             ProfileMigrator profileMigrator =
-                new ProfileMigrator(GeckoApp.mAppContext.getContentResolver(),
-                                    profileDir);
+                new ProfileMigrator(app.getContentResolver(), profileDir);
             profileMigrator.launchBackground();
         }
     }
 
     private SynchronousQueue<String> mFilePickerResult = new SynchronousQueue<String>();
     public String showFilePicker(String aMimeType) {
         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
         intent.addCategory(Intent.CATEGORY_OPENABLE);
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -107,16 +107,17 @@ public class GeckoAppShell
     static public final int WPL_STATE_STOP = 0x00000010;
     static public final int WPL_STATE_IS_DOCUMENT = 0x00020000;
     static public final int WPL_STATE_IS_NETWORK = 0x00040000;
 
     static private File sCacheFile = null;
     static private int sFreeSpace = -1;
     static File sHomeDir = null;
     static private int sDensityDpi = 0;
+    private static Boolean sSQLiteLibsLoaded = false;
 
     private static HashMap<String, ArrayList<GeckoEventListener>> mEventListeners;
 
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void nativeInit();
     public static native void nativeRun(String args);
@@ -124,17 +125,18 @@ public class GeckoAppShell
     // helper methods
     //    public static native void setSurfaceView(GeckoSurfaceView sv);
     public static native void setSoftwareLayerClient(GeckoSoftwareLayerClient client);
     public static native void putenv(String map);
     public static native void onResume();
     public static native void onLowMemory();
     public static native void callObserver(String observerKey, String topic, String data);
     public static native void removeObserver(String observerKey);
-    public static native void loadLibs(String apkName, boolean shouldExtract);
+    public static native void loadGeckoLibsNative(String apkName);
+    public static native void loadSQLiteLibsNative(String apkName, boolean shouldExtract);
     public static native void onChangeNetworkLinkStatus(String status);
     public static native void reportJavaCrash(String stack);
     public static void notifyUriVisited(String uri) {
         sendEventToGecko(new GeckoEvent(GeckoEvent.VISTITED, uri));
     }
 
     public static native void processNextNativeEvent();
 
@@ -305,17 +307,17 @@ public class GeckoAppShell
             from.delete();
         } catch(Exception e) {
             Log.e(LOGTAG, "error trying to move file", e);
         }
         return retVal;
     }
 
     // java-side stuff
-    public static void loadGeckoLibs(String apkName) {
+    public static boolean loadLibsSetup(String apkName) {
         // The package data lib directory isn't placed in ld.so's
         // search path, so we have to manually load libraries that
         // libxul will depend on.  Not ideal.
         GeckoApp geckoApp = GeckoApp.mAppContext;
         String homeDir;
         sHomeDir = GeckoDirProvider.getFilesDir(geckoApp);
         homeDir = sHomeDir.getPath();
 
@@ -410,17 +412,33 @@ public class GeckoAppShell
                 Iterator cacheFiles = Arrays.asList(files).iterator();
                 while (cacheFiles.hasNext()) {
                     File libFile = (File)cacheFiles.next();
                     if (libFile.getName().endsWith(".so"))
                         libFile.delete();
                 }
             }
         }
-        loadLibs(apkName, extractLibs);
+        return extractLibs;
+    }
+
+    public static void ensureSQLiteLibsLoaded(String apkName) {
+        if (sSQLiteLibsLoaded)
+            return;
+        synchronized(sSQLiteLibsLoaded) {
+            if (sSQLiteLibsLoaded)
+                return;
+            loadSQLiteLibsNative(apkName, loadLibsSetup(apkName));
+            sSQLiteLibsLoaded = true;
+        }
+    }
+
+    public static void loadGeckoLibs(String apkName) {
+        boolean extractLibs = loadLibsSetup(apkName);
+        loadGeckoLibsNative(apkName);
     }
 
     private static void putLocaleEnv() {
         GeckoAppShell.putenv("LANG=" + Locale.getDefault().toString());
         NumberFormat nf = NumberFormat.getInstance();
         if (nf instanceof DecimalFormat) {
             DecimalFormat df = (DecimalFormat)nf;
             DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
--- a/mobile/android/base/GeckoThread.java
+++ b/mobile/android/base/GeckoThread.java
@@ -81,18 +81,19 @@ public class GeckoThread extends Thread 
                     libs[i].delete();
                 }
             }
         }
 
         // At some point while loading the gecko libs our default locale gets set
         // so just save it to locale here and reset it as default after the join
         Locale locale = Locale.getDefault();
-        GeckoAppShell.loadGeckoLibs(
-            app.getApplication().getPackageResourcePath());
+        String resourcePath = app.getApplication().getPackageResourcePath();
+        GeckoAppShell.ensureSQLiteLibsLoaded(resourcePath);
+        GeckoAppShell.loadGeckoLibs(resourcePath);
         Locale.setDefault(locale);
         Resources res = app.getBaseContext().getResources();
         Configuration config = res.getConfiguration();
         config.locale = locale;
         res.updateConfiguration(config, res.getDisplayMetrics());
 
         Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - runGecko");
 
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -84,16 +84,19 @@ FENNEC_JAVA_FILES = \
   GeckoInputConnection.java \
   GeckoPreferences.java \
   GeckoStateListDrawable.java \
   GeckoThread.java \
   GlobalHistory.java \
   LinkPreference.java \
   ProfileMigrator.java \
   PromptService.java \
+  sqlite/ByteBufferInputStream.java \
+  sqlite/SQLiteBridge.java \
+  sqlite/SQLiteBridgeException.java \
   SurfaceLockInfo.java \
   Tab.java \
   Tabs.java \
   TabsTray.java \
   gfx/BitmapUtils.java \
   gfx/BufferedCairoImage.java \
   gfx/CairoGLInfo.java \
   gfx/CairoImage.java \
--- a/mobile/android/base/ProfileMigrator.java
+++ b/mobile/android/base/ProfileMigrator.java
@@ -10,17 +10,17 @@
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is Mozilla Android code.
  *
  * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Gian-Carlo Pascutto <gpascutto@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
@@ -33,100 +33,93 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.sqlite.ByteBufferInputStream;
+import org.mozilla.gecko.sqlite.SQLiteBridge;
+import org.mozilla.gecko.sqlite.SQLiteBridgeException;
 
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
-import android.database.sqlite.SQLiteStatement;
 import android.content.ContentResolver;
 import android.database.Cursor;
+import android.database.SQLException;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.provider.Browser;
 import android.util.Log;
-import android.webkit.WebIconDatabase;
 
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.File;
+import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Map;
 import java.util.HashMap;
 import java.util.Date;
 import java.util.List;
 import java.util.Iterator;
 
 
 public class ProfileMigrator {
     private static final String LOGTAG = "ProfMigr";
     private File mProfileDir;
     private ContentResolver mCr;
-    private SQLiteDatabase mDb;
 
     /*
       Amount of Android history entries we will remember
       to prevent moving their last access date backwards.
     */
     private static final int MAX_HISTORY_TO_CHECK = 1000;
 
     /*
        These queries are derived from the low-level Places schema
        https://developer.mozilla.org/en/The_Places_database
     */
-    final String bookmarkQuery = "SELECT places.url AS a_url, "
+    private final String bookmarkQuery = "SELECT places.url AS a_url, "
         + "places.title AS a_title FROM "
         + "(moz_places as places JOIN moz_bookmarks as bookmarks ON "
         + "places.id = bookmarks.fk) WHERE places.hidden <> 1 "
         + "ORDER BY bookmarks.dateAdded";
-    // Don't ask why. Just curse along at the Android devs.
-    final String bookmarkUrl = "a_url";
-    final String bookmarkTitle = "a_title";
+    // Result column of relevant data
+    private final String bookmarkUrl   = "a_url";
+    private final String bookmarkTitle = "a_title";
 
-    final String historyQuery =
+    private final String historyQuery =
         "SELECT places.url AS a_url, places.title AS a_title, "
         + "history.visit_date AS a_date FROM "
         + "(moz_historyvisits AS history JOIN moz_places AS places ON "
         + "places.id = history.place_id) WHERE places.hidden <> 1 "
         + "ORDER BY history.visit_date DESC";
-    final String historyUrl = "a_url";
-    final String historyTitle = "a_title";
-    final String historyDate = "a_date";
+    private final String historyUrl   = "a_url";
+    private final String historyTitle = "a_title";
+    private final String historyDate  = "a_date";
 
-    final String faviconQuery =
+    private final String faviconQuery =
         "SELECT places.url AS a_url, favicon.data AS a_data, "
         + "favicon.mime_type AS a_mime FROM (moz_places AS places JOIN "
         + "moz_favicons AS favicon ON places.favicon_id = favicon.id)";
-    final String faviconUrl = "a_url";
-    final String faviconData = "a_data";
-    final String faviconMime = "a_mime";
+    private final String faviconUrl  = "a_url";
+    private final String faviconData = "a_data";
+    private final String faviconMime = "a_mime";
 
     public ProfileMigrator(ContentResolver cr, File profileDir) {
         mProfileDir = profileDir;
         mCr = cr;
     }
 
     public void launchBackground() {
         // Work around http://code.google.com/p/android/issues/detail?id=11291
-        // The WebIconDatabase needs to be initialized within the UI thread so
-        // just request the instance here.
-        WebIconDatabase.getInstance();
-
-        PlacesTask placesTask = new PlacesTask();
-        new Thread(placesTask).start();
+        // WebIconDatabase needs to be initialized within a looper thread.
+        GeckoAppShell.getHandler().post(new PlacesTask());
     }
 
     private class PlacesTask implements Runnable {
         // Get a list of the last times an URL was accessed
         protected Map<String, Long> gatherAndroidHistory() {
             Map<String, Long> history = new HashMap<String, Long>();
 
             Cursor cursor = BrowserDB.getRecentHistory(mCr, MAX_HISTORY_TO_CHECK);
@@ -172,47 +165,34 @@ public class ProfileMigrator {
                 BrowserDB.updateVisitedHistory(mCr, url);
                 BrowserDB.updateHistoryDate(mCr, url, date);
                 if (title != null) {
                     BrowserDB.updateHistoryTitle(mCr, url, title);
                 }
             }
         }
 
-        protected void migrateHistory(SQLiteDatabase db) {
+        protected void migrateHistory(SQLiteBridge db) {
             Map<String, Long> androidHistory = gatherAndroidHistory();
             final ArrayList<String> placesHistory = new ArrayList<String>();
 
-            Cursor cursor = null;
             try {
-                cursor =
-                    db.rawQuery(historyQuery, new String[] { });
-                final int urlCol =
-                    cursor.getColumnIndexOrThrow(historyUrl);
-                final int titleCol =
-                    cursor.getColumnIndexOrThrow(historyTitle);
-                final int dateCol =
-                    cursor.getColumnIndexOrThrow(historyDate);
+                ArrayList<Object[]> queryResult = db.query(historyQuery);
+                final int urlCol = db.getColumnIndex(historyUrl);
+                final int titleCol = db.getColumnIndex(historyTitle);
+                final int dateCol = db.getColumnIndex(historyDate);
 
-                cursor.moveToFirst();
-                while (!cursor.isAfterLast()) {
-                    String url = cursor.getString(urlCol);
-                    String title = cursor.getString(titleCol);
-                    // Convert from us (Places) to ms (Java, Android)
-                    long date = cursor.getLong(dateCol) / (long)1000;
+                for (Object[] resultRow: queryResult) {
+                    String url = (String)resultRow[urlCol];
+                    String title = (String)resultRow[titleCol];
+                    long date = Long.parseLong((String)(resultRow[dateCol])) / (long)1000;
                     addHistory(androidHistory, url, title, date);
                     placesHistory.add(url);
-                    cursor.moveToNext();
                 }
-
-                cursor.close();
-            } catch (SQLiteException e) {
-                if (cursor != null) {
-                    cursor.close();
-                }
+            } catch (SQLiteBridgeException e) {
                 Log.i(LOGTAG, "Failed to get bookmarks: " + e.getMessage());
                 return;
             }
             // GlobalHistory access communicates with Gecko
             // and must run on its thread
             GeckoAppShell.getHandler().post(new Runnable() {
                     public void run() {
                         for (String url : placesHistory) {
@@ -226,129 +206,94 @@ public class ProfileMigrator {
             if (!BrowserDB.isBookmark(mCr, url)) {
                 if (title == null) {
                     title = url;
                 }
                 BrowserDB.addBookmark(mCr, title, url);
             }
         }
 
-        protected void migrateBookmarks(SQLiteDatabase db) {
-            Cursor cursor = null;
+        protected void migrateBookmarks(SQLiteBridge db) {
             try {
-                cursor = db.rawQuery(bookmarkQuery,
-                                     new String[] {});
-                if (cursor.getCount() > 0) {
-                    final int urlCol =
-                        cursor.getColumnIndexOrThrow(bookmarkUrl);
-                    final int titleCol =
-                        cursor.getColumnIndexOrThrow(bookmarkTitle);
+                ArrayList<Object[]> queryResult = db.query(bookmarkQuery);
+                final int urlCol = db.getColumnIndex(bookmarkUrl);
+                final int titleCol = db.getColumnIndex(bookmarkTitle);
 
-                    cursor.moveToFirst();
-                    while (!cursor.isAfterLast()) {
-                        String url = cursor.getString(urlCol);
-                        String title = cursor.getString(titleCol);
-                        addBookmark(url, title);
-                        cursor.moveToNext();
-                    }
+                for (Object[] resultRow: queryResult) {
+                    String url = (String)resultRow[urlCol];
+                    String title = (String)resultRow[titleCol];
+                    addBookmark(url, title);
                 }
-                cursor.close();
-            } catch (SQLiteException e) {
-                if (cursor != null) {
-                    cursor.close();
-                }
+            } catch (SQLiteBridgeException e) {
                 Log.i(LOGTAG, "Failed to get bookmarks: " + e.getMessage());
                 return;
             }
         }
 
-        protected void addFavicon(String url, String mime, byte[] data) {
-            ByteArrayInputStream byteStream = new ByteArrayInputStream(data);
+        protected void addFavicon(String url, String mime, ByteBuffer data) {
+            ByteBufferInputStream byteStream = new ByteBufferInputStream(data);
             BitmapDrawable image = (BitmapDrawable) Drawable.createFromStream(byteStream, "src");
             if (image != null) {
                 try {
                     BrowserDB.updateFaviconForUrl(mCr, url, image);
-                } catch (SQLiteException e) {
+                } catch (SQLException e) {
                     Log.i(LOGTAG, "Migrating favicon failed: " + mime + " URL: " + url
                           + " error:" + e.getMessage());
                 }
             }
         }
 
-        protected void migrateFavicons(SQLiteDatabase db) {
-            Cursor cursor = null;
+        protected void migrateFavicons(SQLiteBridge db) {
             try {
-                cursor = db.rawQuery(faviconQuery,
-                                     new String[] {});
-                if (cursor.getCount() > 0) {
-                    final int urlCol =
-                        cursor.getColumnIndexOrThrow(faviconUrl);
-                    final int dataCol =
-                        cursor.getColumnIndexOrThrow(faviconData);
-                    final int mimeCol =
-                        cursor.getColumnIndexOrThrow(faviconMime);
+                ArrayList<Object[]> queryResult = db.query(faviconQuery);
+                final int urlCol = db.getColumnIndex(faviconUrl);
+                final int mimeCol = db.getColumnIndex(faviconMime);
+                final int dataCol = db.getColumnIndex(faviconData);
 
-                    cursor.moveToFirst();
-                    while (!cursor.isAfterLast()) {
-                        String url = cursor.getString(urlCol);
-                        String mime = cursor.getString(mimeCol);
-                        byte[] data = cursor.getBlob(dataCol);
-                        addFavicon(url, mime, data);
-                        cursor.moveToNext();
-                    }
+                for (Object[] resultRow: queryResult) {
+                    String url = (String)resultRow[urlCol];
+                    String mime = (String)resultRow[mimeCol];
+                    ByteBuffer dataBuff = (ByteBuffer)resultRow[dataCol];
+                    addFavicon(url, mime, dataBuff);
                 }
-                cursor.close();
-            } catch (SQLiteException e) {
-                if (cursor != null) {
-                    cursor.close();
-                }
+            } catch (SQLiteBridgeException e) {
                 Log.i(LOGTAG, "Failed to get favicons: " + e.getMessage());
                 return;
             }
         }
 
-        SQLiteDatabase openPlaces(String dbPath) throws SQLiteException {
-            /* http://stackoverflow.com/questions/2528489/no-such-table-android-metadata-whats-the-problem */
-            SQLiteDatabase db = SQLiteDatabase.openDatabase(dbPath,
-                                                            null,
-                                                            SQLiteDatabase.OPEN_READONLY |
-                                                            SQLiteDatabase.NO_LOCALIZED_COLLATORS);
-
-            return db;
-        }
-
         protected void migratePlaces(File aFile) {
             String dbPath = aFile.getPath() + "/places.sqlite";
             String dbPathWal = aFile.getPath() + "/places.sqlite-wal";
             String dbPathShm = aFile.getPath() + "/places.sqlite-shm";
             Log.i(LOGTAG, "Opening path: " + dbPath);
 
             File dbFile = new File(dbPath);
             if (!dbFile.exists()) {
                 Log.i(LOGTAG, "No database");
                 return;
             }
             File dbFileWal = new File(dbPathWal);
             File dbFileShm = new File(dbPathShm);
 
-            SQLiteDatabase db = null;
+            SQLiteBridge db = null;
             try {
-                db = openPlaces(dbPath);
+                db = new SQLiteBridge(dbPath);
                 migrateBookmarks(db);
                 migrateHistory(db);
                 migrateFavicons(db);
                 db.close();
 
                 // Clean up
                 dbFile.delete();
                 dbFileWal.delete();
                 dbFileShm.delete();
 
                 Log.i(LOGTAG, "Profile migration finished");
-            } catch (SQLiteException e) {
+            } catch (SQLiteBridgeException e) {
                 if (db != null) {
                     db.close();
                 }
                 Log.i(LOGTAG, "Error on places database:" + e.getMessage());
                 return;
             }
         }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sqlite/ByteBufferInputStream.java
@@ -0,0 +1,71 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011-2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Gian-Carlo Pascutto <gpascutto@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.sqlite;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/*
+ * Helper class to make the ByteBuffers returned by SQLite BLOB
+ * easier to use.
+ */
+public class ByteBufferInputStream extends InputStream {
+    private ByteBuffer mByteBuffer;
+
+    public ByteBufferInputStream(ByteBuffer aByteBuffer) {
+        mByteBuffer = aByteBuffer;
+    }
+
+    @Override
+    public synchronized int read() throws IOException {
+        if (!mByteBuffer.hasRemaining()) {
+            return -1;
+        }
+        return mByteBuffer.get();
+    }
+
+    @Override
+    public synchronized int read(byte[] aBytes, int aOffset, int aLen)
+        throws IOException {
+        int toRead = Math.min(aLen, mByteBuffer.remaining());
+        mByteBuffer.get(aBytes, aOffset, toRead);
+        return toRead;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sqlite/SQLiteBridge.java
@@ -0,0 +1,101 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Gian-Carlo Pascutto <gpascutto@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.sqlite;
+
+import org.mozilla.gecko.sqlite.SQLiteBridgeException;
+import android.util.Log;
+
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/*
+ * This class allows using the mozsqlite3 library included with Firefox
+ * to read SQLite databases, instead of the Android SQLiteDataBase API,
+ * which might use whatever outdated DB is present on the Android system.
+ */
+public class SQLiteBridge {
+    private static final String LOGTAG = "SQLiteBridge";
+    // Path to the database. We reopen it every query.
+    private String mDb;
+    // Remember column names from last query result.
+    private ArrayList<String> mColumns;
+
+    // JNI code in $(topdir)/storage/android/..
+    private static native void sqliteCall(String aDb, String aQuery,
+                                          String[] aParams,
+                                          ArrayList<String> aColumns,
+                                          ArrayList<Object[]> aRes)
+        throws SQLiteBridgeException;
+
+    // Takes the path to the database we want to access.
+    public SQLiteBridge(String aDb) throws SQLiteBridgeException {
+        mDb = aDb;
+    }
+
+    // Do an SQL query without parameters
+    public ArrayList<Object[]> query(String aQuery) throws SQLiteBridgeException {
+        String[] params = new String[0];
+        return query(aQuery, params);
+    }
+
+    // Do an SQL query, substituting the parameters in the query with the passed
+    // parameters. The parameters are subsituded in order, so named parameters
+    // are not supported.
+    // The result is returned as an ArrayList<Object[]>, with each
+    // row being an entry in the ArrayList, and each column being one Object
+    // in the Object[] array. The columns are of type null,
+    // direct ByteBuffer (BLOB), or String (everything else).
+    public ArrayList<Object[]> query(String aQuery, String[] aParams)
+        throws SQLiteBridgeException {
+        ArrayList<Object[]> result = new ArrayList<Object[]>();
+        mColumns = new ArrayList<String>();
+        sqliteCall(mDb, aQuery, aParams, mColumns, result);
+        return result;
+    }
+
+    // Gets the index in the row Object[] for the given column name.
+    // Returns -1 if not found.
+    public int getColumnIndex(String aColumnName) {
+        return mColumns.lastIndexOf(aColumnName);
+    }
+
+    public void close() {
+        // nop, provided for API compatibility with SQLiteDatabase.
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sqlite/SQLiteBridgeException.java
@@ -0,0 +1,47 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Gian-Carlo Pascutto <gpascutto@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko.sqlite;
+
+public class SQLiteBridgeException extends Exception {
+    static final long serialVersionUID = 1L;
+
+    public SQLiteBridgeException() {}
+    public SQLiteBridgeException(String msg) {
+        super(msg);
+    }
+}
\ No newline at end of file
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -57,16 +57,18 @@
 #include <unistd.h>
 #include <zlib.h>
 #include <linux/ashmem.h>
 #include "dlfcn.h"
 #include "APKOpen.h"
 #include <sys/time.h>
 #include <sys/resource.h>
 #include "Zip.h"
+#include "sqlite3.h"
+#include "SQLiteBridge.h"
 
 /* Android headers don't define RUSAGE_THREAD */
 #ifndef RUSAGE_THREAD
 #define RUSAGE_THREAD 1
 #endif
 
 enum StartupEvent {
 #define mozilla_StartupTimeline_Event(ev, z) ev,
@@ -296,16 +298,17 @@ SHELL_WRAPPER3(notifyGetSmsFailed, jint,
 SHELL_WRAPPER3(notifySmsDeleted, jboolean, jint, jlong);
 SHELL_WRAPPER3(notifySmsDeleteFailed, jint, jint, jlong);
 SHELL_WRAPPER2(notifyNoMessageInList, jint, jlong);
 SHELL_WRAPPER8(notifyListCreated, jint, jint, jstring, jstring, jstring, jlong, jint, jlong);
 SHELL_WRAPPER7(notifyGotNextMessage, jint, jstring, jstring, jstring, jlong, jint, jlong);
 SHELL_WRAPPER3(notifyReadingMessageListFailed, jint, jint, jlong);
 
 static void * xul_handle = NULL;
+static void * sqlite_handle = NULL;
 static time_t apk_mtime = 0;
 #ifdef DEBUG
 extern "C" int extractLibs = 1;
 #else
 extern "C" int extractLibs = 0;
 #endif
 
 static void
@@ -613,44 +616,40 @@ report_mapping(char *name, void *base, u
   char * entry = strstr(file_ids, name);
   if (entry)
     info->file_id = strndup(entry + strlen(name) + 1, 32);
 }
 
 extern "C" void simple_linker_init(void);
 
 static void
-loadLibs(const char *apkName)
+loadGeckoLibs(const char *apkName)
 {
   chdir(getenv("GRE_HOME"));
 
-  simple_linker_init();
-
   struct stat status;
   if (!stat(apkName, &status))
     apk_mtime = status.st_mtime;
 
   struct timeval t0, t1;
   gettimeofday(&t0, 0);
   struct rusage usage1;
   getrusage(RUSAGE_THREAD, &usage1);
   
   Zip *zip = new Zip(apkName);
 
-  lib_mapping = (struct mapping_info *)calloc(MAX_MAPPING_INFO, sizeof(*lib_mapping));
 #ifdef MOZ_CRASHREPORTER
   file_ids = (char *)extractBuf("lib.id", zip);
 #endif
 
 #define MOZLOAD(name) mozload("lib" name ".so", zip)
   MOZLOAD("mozalloc");
   MOZLOAD("nspr4");
   MOZLOAD("plc4");
   MOZLOAD("plds4");
-  MOZLOAD("mozsqlite3");
   MOZLOAD("nssutil3");
   MOZLOAD("nss3");
   MOZLOAD("ssl3");
   MOZLOAD("smime3");
   xul_handle = MOZLOAD("xul");
   MOZLOAD("xpcom");
   MOZLOAD("nssckbi");
   MOZLOAD("freebl3");
@@ -707,30 +706,77 @@ loadLibs(const char *apkName)
                       (usage2.ru_utime.tv_sec - usage1.ru_utime.tv_sec)*1000 + (usage2.ru_utime.tv_usec - usage1.ru_utime.tv_usec)/1000,
                       (usage2.ru_stime.tv_sec - usage1.ru_stime.tv_sec)*1000 + (usage2.ru_stime.tv_usec - usage1.ru_stime.tv_usec)/1000,
                       usage2.ru_majflt-usage1.ru_majflt);
 
   StartupTimeline_Record(LINKER_INITIALIZED, &t0);
   StartupTimeline_Record(LIBRARIES_LOADED, &t1);
 }
 
-extern "C" NS_EXPORT void JNICALL
-Java_org_mozilla_gecko_GeckoAppShell_loadLibs(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName, jboolean jShouldExtract)
+static void loadSQLiteLibs(const char *apkName)
 {
-  if (jShouldExtract)
-    extractLibs = 1;
+  chdir(getenv("GRE_HOME"));
+
+  simple_linker_init();
+
+  struct stat status;
+  if (!stat(apkName, &status))
+    apk_mtime = status.st_mtime;
+
+  Zip *zip = new Zip(apkName);
+  lib_mapping = (struct mapping_info *)calloc(MAX_MAPPING_INFO, sizeof(*lib_mapping));
+
+#ifdef MOZ_CRASHREPORTER
+  file_ids = (char *)extractBuf("lib.id", zip);
+#endif
+
+#define MOZLOAD(name) mozload("lib" name ".so", zip)
+  sqlite_handle = MOZLOAD("mozsqlite3");
+#undef MOZLOAD
 
+  delete zip;
+
+#ifdef MOZ_CRASHREPORTER
+  free(file_ids);
+  file_ids = NULL;
+#endif
+
+  if (!sqlite_handle)
+    __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get a handle to libmozsqlite3!");
+
+  setup_sqlite_functions(sqlite_handle);
+}
+
+extern "C" NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_loadGeckoLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName)
+{
   const char* str;
-  // XXX: java doesn't give us true UTF8, we should figure out something 
+  // XXX: java doesn't give us true UTF8, we should figure out something
   // better to do here
   str = jenv->GetStringUTFChars(jApkName, NULL);
   if (str == NULL)
     return;
 
-  loadLibs(str);
+  loadGeckoLibs(str);
+  jenv->ReleaseStringUTFChars(jApkName, str);
+}
+
+extern "C" NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_loadSQLiteLibsNative(JNIEnv *jenv, jclass jGeckoAppShellClass, jstring jApkName, jboolean jShouldExtract) {
+  if (jShouldExtract)
+    extractLibs = 1;
+
+  const char* str;
+  // XXX: java doesn't give us true UTF8, we should figure out something
+  // better to do here
+  str = jenv->GetStringUTFChars(jApkName, NULL);
+  if (str == NULL)
+    return;
+
+  loadSQLiteLibs(str);
   jenv->ReleaseStringUTFChars(jApkName, str);
 }
 
 typedef int GeckoProcessType;
 typedef int nsresult;
 
 extern "C" NS_EXPORT int
 ChildProcessInit(int argc, char* argv[])
@@ -740,17 +786,18 @@ ChildProcessInit(int argc, char* argv[])
     if (strcmp(argv[i], "-greomni"))
       continue;
 
     i = i + 1;
     break;
   }
 
   fillLibCache(argv[argc - 1]);
-  loadLibs(argv[i]);
+  loadSQLiteLibs(argv[i]);
+  loadGeckoLibs(argv[i]);
 
   // don't pass the last arg - it's only recognized by the lib cache
   argc--;
 
   typedef GeckoProcessType (*XRE_StringToChildProcessType_t)(char*);
   typedef nsresult (*XRE_InitChildProcess_t)(int, char**, GeckoProcessType);
   XRE_StringToChildProcessType_t fXRE_StringToChildProcessType =
     (XRE_StringToChildProcessType_t)__wrap_dlsym(xul_handle, "XRE_StringToChildProcessType");
--- a/mozglue/android/Makefile.in
+++ b/mozglue/android/Makefile.in
@@ -48,20 +48,22 @@ FORCE_STATIC_LIB = 1
 
 DEFINES += \
   -DANDROID_PACKAGE_NAME='"$(ANDROID_PACKAGE_NAME)"' \
   $(NULL)
 
 CPPSRCS = \
   nsGeckoUtils.cpp \
   APKOpen.cpp \
+  SQLiteBridge.cpp \
   $(NULL)
 
 LOCAL_INCLUDES += -I$(srcdir)/../linker
 LOCAL_INCLUDES += -I$(topsrcdir)/toolkit/components/startup
+LOCAL_INCLUDES += -I$(topsrcdir)/db/sqlite3/src
 ifdef MOZ_OLD_LINKER
 LOCAL_INCLUDES += -I$(topsrcdir)/other-licenses/android
 ifeq ($(CPU_ARCH),arm)
 DEFINES += -DANDROID_ARM_LINKER
 endif
 endif
 
 EXPORTS = APKOpen.h
new file mode 100644
--- /dev/null
+++ b/mozglue/android/SQLiteBridge.cpp
@@ -0,0 +1,312 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2012
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Gian-Carlo Pascutto <gpascutto@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include <stdlib.h>
+#include <jni.h>
+#include <android/log.h>
+#include "dlfcn.h"
+#include "APKOpen.h"
+#include "SQLiteBridge.h"
+
+#ifdef DEBUG
+#define LOG(x...) __android_log_print(ANDROID_LOG_INFO, "GeckoJNI", x)
+#else
+#define LOG(x...)
+#endif
+
+#define SQLITE_WRAPPER_INT(name) name ## _t f_ ## name;
+
+SQLITE_WRAPPER_INT(sqlite3_open)
+SQLITE_WRAPPER_INT(sqlite3_errmsg)
+SQLITE_WRAPPER_INT(sqlite3_prepare_v2)
+SQLITE_WRAPPER_INT(sqlite3_bind_parameter_count)
+SQLITE_WRAPPER_INT(sqlite3_bind_text)
+SQLITE_WRAPPER_INT(sqlite3_step)
+SQLITE_WRAPPER_INT(sqlite3_column_count)
+SQLITE_WRAPPER_INT(sqlite3_finalize)
+SQLITE_WRAPPER_INT(sqlite3_close)
+SQLITE_WRAPPER_INT(sqlite3_column_name)
+SQLITE_WRAPPER_INT(sqlite3_column_type)
+SQLITE_WRAPPER_INT(sqlite3_column_blob)
+SQLITE_WRAPPER_INT(sqlite3_column_bytes)
+SQLITE_WRAPPER_INT(sqlite3_column_text)
+
+void setup_sqlite_functions(void *sqlite_handle)
+{
+#define GETFUNC(name) f_ ## name = (name ## _t) __wrap_dlsym(sqlite_handle, #name)
+  GETFUNC(sqlite3_open);
+  GETFUNC(sqlite3_errmsg);
+  GETFUNC(sqlite3_prepare_v2);
+  GETFUNC(sqlite3_bind_parameter_count);
+  GETFUNC(sqlite3_bind_text);
+  GETFUNC(sqlite3_step);
+  GETFUNC(sqlite3_column_count);
+  GETFUNC(sqlite3_finalize);
+  GETFUNC(sqlite3_close);
+  GETFUNC(sqlite3_column_name);
+  GETFUNC(sqlite3_column_type);
+  GETFUNC(sqlite3_column_blob);
+  GETFUNC(sqlite3_column_bytes);
+  GETFUNC(sqlite3_column_text);
+#undef GETFUNC
+}
+
+static bool initialized = false;
+static jclass stringClass;
+static jclass objectClass;
+static jclass byteBufferClass;
+static jclass arrayListClass;
+static jmethodID jByteBufferAllocateDirect;
+static jmethodID jArrayListAdd;
+static jobject jNull;
+
+static void
+JNI_Throw(JNIEnv* jenv, const char* name, const char* msg)
+{
+    jclass cls = jenv->FindClass(name);
+    if (cls == NULL) {
+        LOG("Couldn't find exception class (or exception pending)\n");
+        return;
+    }
+    int rc = jenv->ThrowNew(cls, msg);
+    if (rc < 0) {
+        LOG("Error throwing exception\n");
+    }
+    jenv->DeleteLocalRef(cls);
+}
+
+static void
+JNI_Setup(JNIEnv* jenv)
+{
+    if (initialized) return;
+
+    objectClass     = jenv->FindClass("java/lang/Object");
+    stringClass     = jenv->FindClass("java/lang/String");
+    byteBufferClass = jenv->FindClass("java/nio/ByteBuffer");
+    arrayListClass  = jenv->FindClass("java/util/ArrayList");
+    jNull           = jenv->NewGlobalRef(NULL);
+
+    if (stringClass == NULL || objectClass == NULL
+        || byteBufferClass == NULL || arrayListClass == NULL) {
+        LOG("Error finding classes");
+        JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException",
+                  "FindClass error");
+        return;
+    }
+
+    // public static ByteBuffer allocateDirect(int capacity)
+    jByteBufferAllocateDirect =
+        jenv->GetStaticMethodID(byteBufferClass, "allocateDirect", "(I)Ljava/nio/ByteBuffer;");
+    // boolean add(Object o)
+    jArrayListAdd =
+        jenv->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
+
+    if (jByteBufferAllocateDirect == NULL || jArrayListAdd == NULL) {
+        LOG("Error finding methods");
+        JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException",
+                  "GetMethodId error");
+        return;
+    }
+
+    initialized = true;
+}
+
+extern "C" NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_sqlite_SQLiteBridge_sqliteCall(JNIEnv* jenv, jclass,
+                                                      jstring jDb,
+                                                      jstring jQuery,
+                                                      jobjectArray jParams,
+                                                      jobject jColumns,
+                                                      jobject jArrayList)
+{
+    JNI_Setup(jenv);
+
+    const char* queryStr;
+    queryStr = jenv->GetStringUTFChars(jQuery, NULL);
+
+    const char* dbPath;
+    dbPath = jenv->GetStringUTFChars(jDb, NULL);
+
+    const char *pzTail;
+    sqlite3_stmt *ppStmt;
+    sqlite3 *db;
+    int rc;
+    rc = f_sqlite3_open(dbPath, &db);
+    jenv->ReleaseStringUTFChars(jDb, dbPath);
+
+    if (rc != SQLITE_OK) {
+        LOG("Can't open database: %s\n", f_sqlite3_errmsg(db));
+        goto error_close;
+    }
+
+    rc = f_sqlite3_prepare_v2(db, queryStr, -1, &ppStmt, &pzTail);
+    if (rc != SQLITE_OK || ppStmt == NULL) {
+        LOG("Can't prepare statement: %s\n", f_sqlite3_errmsg(db));
+        goto error_close;
+    }
+    jenv->ReleaseStringUTFChars(jQuery, queryStr);
+
+    // Check if number of parameters matches
+    jsize numPars;
+    numPars = jenv->GetArrayLength(jParams);
+    int sqlNumPars;
+    sqlNumPars = f_sqlite3_bind_parameter_count(ppStmt);
+    if (numPars != sqlNumPars) {
+        LOG("Passed parameter count (%d) doesn't match SQL parameter count (%d)\n",
+            numPars, sqlNumPars);
+        goto error_close;
+    }
+    // Bind parameters, if any
+    if (numPars > 0) {
+        for (int i = 0; i < numPars; i++) {
+            jobject jObjectParam = jenv->GetObjectArrayElement(jParams, i);
+            // IsInstanceOf or isAssignableFrom? String is final, so IsInstanceOf
+            // should be OK.
+            jboolean isString = jenv->IsInstanceOf(jObjectParam, stringClass);
+            if (isString != JNI_TRUE) {
+                LOG("Parameter is not of String type");
+                goto error_close;
+            }
+            jstring jStringParam = (jstring)jObjectParam;
+            const char* paramStr = jenv->GetStringUTFChars(jStringParam, NULL);
+            // SQLite parameters index from 1.
+            rc = f_sqlite3_bind_text(ppStmt, i + 1, paramStr, -1, SQLITE_TRANSIENT);
+            jenv->ReleaseStringUTFChars(jStringParam, paramStr);
+            if (rc != SQLITE_OK) {
+                LOG("Error binding query parameter");
+                goto error_close;
+            }
+        }
+    }
+
+    // Execute the query and step through the results
+    rc = f_sqlite3_step(ppStmt);
+    if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+        LOG("Can't step statement: (%d) %s\n", rc, f_sqlite3_errmsg(db));
+        goto error_close;
+    }
+
+    // Get the column names
+    int cols;
+    cols = f_sqlite3_column_count(ppStmt);
+    for (int i = 0; i < cols; i++) {
+        const char* colName = f_sqlite3_column_name(ppStmt, i);
+        jstring jStr = jenv->NewStringUTF(colName);
+        jenv->CallBooleanMethod(jColumns, jArrayListAdd, jStr);
+        jenv->DeleteLocalRef(jStr);
+    }
+
+    // For each row, add an Object[] to the passed ArrayList,
+    // with that containing either String or ByteArray objects
+    // containing the columns
+    do {
+        // Process row
+        // Construct Object[]
+        jobjectArray jRow = jenv->NewObjectArray(cols,
+                                                 objectClass,
+                                                 NULL);
+        if (jRow == NULL) {
+            LOG("Can't allocate jRow Object[]\n");
+            goto error_close;
+        }
+
+        for (int i = 0; i < cols; i++) {
+            int colType = f_sqlite3_column_type(ppStmt, i);
+            if (colType == SQLITE_BLOB) {
+                // Treat as blob
+                const void* blob = f_sqlite3_column_blob(ppStmt, i);
+                int colLen = f_sqlite3_column_bytes(ppStmt, i);
+
+                // Construct ByteBuffer of correct size
+                jobject jByteBuffer =
+                    jenv->CallStaticObjectMethod(byteBufferClass,
+                                                 jByteBufferAllocateDirect,
+                                                 colLen);
+                if (jByteBuffer == NULL) {
+                    goto error_close;
+                }
+
+                // Get its backing array
+                void* bufferArray = jenv->GetDirectBufferAddress(jByteBuffer);
+                if (bufferArray == NULL) {
+                    LOG("Failure calling GetDirectBufferAddress\n");
+                    goto error_close;
+                }
+                memcpy(bufferArray, blob, colLen);
+
+                jenv->SetObjectArrayElement(jRow, i, jByteBuffer);
+                jenv->DeleteLocalRef(jByteBuffer);
+            } else if (colType == SQLITE_NULL) {
+                jenv->SetObjectArrayElement(jRow, i, jNull);
+            } else {
+                // Treat everything else as text
+                const char* txt = (const char*)f_sqlite3_column_text(ppStmt, i);
+                jstring jStr = jenv->NewStringUTF(txt);
+                jenv->SetObjectArrayElement(jRow, i, jStr);
+                jenv->DeleteLocalRef(jStr);
+            }
+        }
+
+        // Append Object[] to ArrayList<Object[]>
+        // JNI doesn't know about the generic, so use Object[] as Object
+        jenv->CallBooleanMethod(jArrayList, jArrayListAdd, jRow);
+
+        // Clean up
+        jenv->DeleteLocalRef(jRow);
+
+        // Get next row
+        rc = f_sqlite3_step(ppStmt);
+        // Real error?
+        if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
+            LOG("Can't re-step statement:(%d) %s\n", rc, f_sqlite3_errmsg(db));
+            goto error_close;
+        }
+    } while (rc != SQLITE_DONE);
+
+    rc = f_sqlite3_finalize(ppStmt);
+    if (rc != SQLITE_OK) {
+        LOG("Can't finalize statement: %s\n", f_sqlite3_errmsg(db));
+        goto error_close;
+    }
+
+    f_sqlite3_close(db);
+    return;
+
+error_close:
+    f_sqlite3_close(db);
+    JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", "SQLite error");
+    return;
+}
new file mode 100644
--- /dev/null
+++ b/mozglue/android/SQLiteBridge.h
@@ -0,0 +1,63 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Gian-Carlo Pascutto <gpascutto@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef SQLiteBridge_h
+#define SQLiteBridge_h
+
+#include "sqlite3.h"
+
+void setup_sqlite_functions(void *sqlite_handle);
+
+#define SQLITE_WRAPPER(name, return_type, args...) \
+typedef return_type (*name ## _t)(args);  \
+extern name ## _t f_ ## name;
+
+SQLITE_WRAPPER(sqlite3_open, int, const char*, sqlite3**)
+SQLITE_WRAPPER(sqlite3_errmsg, const char*, sqlite3*)
+SQLITE_WRAPPER(sqlite3_prepare_v2, int, sqlite3*, const char*, int, sqlite3_stmt**, const char**)
+SQLITE_WRAPPER(sqlite3_bind_parameter_count, int, sqlite3_stmt*)
+SQLITE_WRAPPER(sqlite3_bind_text, int, sqlite3_stmt*, int, const char*, int, void(*)(void*))
+SQLITE_WRAPPER(sqlite3_step, int, sqlite3_stmt*)
+SQLITE_WRAPPER(sqlite3_column_count, int, sqlite3_stmt*)
+SQLITE_WRAPPER(sqlite3_finalize, int, sqlite3_stmt*)
+SQLITE_WRAPPER(sqlite3_close, int, sqlite3*)
+SQLITE_WRAPPER(sqlite3_column_name, const char*, sqlite3_stmt*, int)
+SQLITE_WRAPPER(sqlite3_column_type, int, sqlite3_stmt*, int)
+SQLITE_WRAPPER(sqlite3_column_blob, const void*, sqlite3_stmt*, int)
+SQLITE_WRAPPER(sqlite3_column_bytes, int, sqlite3_stmt*, int)
+SQLITE_WRAPPER(sqlite3_column_text, const unsigned char*, sqlite3_stmt*, int)
+
+#endif /* SQLiteBridge_h */