Bug 713228 - Add bridge to access our own SQLite libraries from Java. r=blassey

This commit is contained in:
Gian-Carlo Pascutto 2012-01-19 21:19:56 +01:00
parent f61b7a7d84
commit 86a031a384
13 changed files with 745 additions and 132 deletions

View File

@ -113,7 +113,8 @@ public class GeckoAppShell
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);
@ -396,7 +397,8 @@ public class GeckoAppShell
}
}
}
loadLibs(apkName, extractLibs);
loadSQLiteLibsNative(apkName, extractLibs);
loadGeckoLibsNative(apkName);
}
private static void putLocaleEnv() {

View File

@ -2101,9 +2101,10 @@ abstract public class GeckoApp
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();
}
}

View File

@ -112,6 +112,7 @@ public class GeckoAppShell
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;
@ -129,7 +130,8 @@ public class GeckoAppShell
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) {
@ -310,7 +312,7 @@ public class GeckoAppShell
}
// 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.
@ -415,7 +417,23 @@ public class GeckoAppShell
}
}
}
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() {

View File

@ -86,8 +86,9 @@ public class GeckoThread extends Thread {
// 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();

View File

@ -89,6 +89,9 @@ FENNEC_JAVA_FILES = \
LinkPreference.java \
ProfileMigrator.java \
PromptService.java \
sqlite/ByteBufferInputStream.java \
sqlite/SQLiteBridge.java \
sqlite/SQLiteBridgeException.java \
SurfaceLockInfo.java \
Tab.java \
Tabs.java \

View File

@ -15,7 +15,7 @@
* 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):
@ -38,24 +38,22 @@
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;
@ -70,7 +68,6 @@ 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
@ -82,32 +79,32 @@ public class ProfileMigrator {
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;
@ -116,12 +113,8 @@ public class ProfileMigrator {
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 {
@ -177,37 +170,24 @@ public class ProfileMigrator {
}
}
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;
}
@ -231,90 +211,55 @@ public class ProfileMigrator {
}
}
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();
}
}
cursor.close();
} catch (SQLiteException e) {
if (cursor != null) {
cursor.close();
for (Object[] resultRow: queryResult) {
String url = (String)resultRow[urlCol];
String title = (String)resultRow[titleCol];
addBookmark(url, title);
}
} 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();
}
}
cursor.close();
} catch (SQLiteException e) {
if (cursor != null) {
cursor.close();
for (Object[] resultRow: queryResult) {
String url = (String)resultRow[urlCol];
String mime = (String)resultRow[mimeCol];
ByteBuffer dataBuff = (ByteBuffer)resultRow[dataCol];
addFavicon(url, mime, dataBuff);
}
} 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";
@ -329,9 +274,9 @@ public class ProfileMigrator {
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);
@ -343,7 +288,7 @@ public class ProfileMigrator {
dbFileShm.delete();
Log.i(LOGTAG, "Profile migration finished");
} catch (SQLiteException e) {
} catch (SQLiteBridgeException e) {
if (db != null) {
db.close();
}

View File

@ -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;
}
}

View File

@ -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.
}
}

View File

@ -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);
}
}

View File

@ -62,6 +62,8 @@
#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
@ -301,6 +303,7 @@ SHELL_WRAPPER7(notifyGotNextMessage, jint, jstring, jstring, jstring, jlong, jin
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;
@ -618,12 +621,10 @@ report_mapping(char *name, void *base, uint32_t len, uint32_t offset)
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;
@ -635,7 +636,6 @@ loadLibs(const char *apkName)
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
@ -645,7 +645,6 @@ loadLibs(const char *apkName)
MOZLOAD("nspr4");
MOZLOAD("plc4");
MOZLOAD("plds4");
MOZLOAD("mozsqlite3");
MOZLOAD("nssutil3");
MOZLOAD("nss3");
MOZLOAD("ssl3");
@ -712,20 +711,67 @@ loadLibs(const char *apkName)
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);
}
@ -745,7 +791,8 @@ ChildProcessInit(int argc, char* argv[])
}
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--;

View File

@ -53,10 +53,12 @@ DEFINES += \
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)

View File

@ -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;
}

View File

@ -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 */