Bug 704682 - Add passwords content provider. r=blassey,gpascutto
authorWes Johnston <wjohnston@mozilla.com>
Fri, 16 Dec 2011 15:11:09 -0800
changeset 89414 85346ae0ea9942f1ac9cff829f2f588fd8deff19
parent 89413 8fa903fa888740143d0537fb51abca5061c6e01e
child 89415 aca408a1d17b2f35dddf6d4ab25398b78d749d70
push id783
push userlsblakk@mozilla.com
push dateTue, 24 Apr 2012 17:33:42 +0000
treeherdermozilla-beta@11faed19f136 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersblassey, gpascutto
bugs704682
milestone13.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 704682 - Add passwords content provider. r=blassey,gpascutto
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/Makefile.in
mobile/android/base/db/BrowserContract.java.in
mobile/android/base/db/BrowserProvider.java.in
mobile/android/base/db/DBUtils.java
mobile/android/base/db/PasswordsProvider.java.in
mobile/android/base/sqlite/SQLiteBridge.java
mobile/android/chrome/content/browser.js
mozglue/android/SQLiteBridge.cpp
mozglue/android/SQLiteBridge.h
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -158,15 +158,21 @@
                   android:theme="@style/Gecko.TitleBar"
                   android:label="@string/settings_title"
                   android:excludeFromRecents="true"/>
 
         <provider android:name="@ANDROID_PACKAGE_NAME@.db.BrowserProvider"
                   android:authorities="@ANDROID_PACKAGE_NAME@.db.browser"
                   android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
 
+        <provider android:name="@ANDROID_PACKAGE_NAME@.db.PasswordsProvider"
+                  android:authorities="@ANDROID_PACKAGE_NAME@.db.passwords"
+                  android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"
+									android:protectionLevel="signature"/>
+
+
 #include ../sync/manifests/SyncAndroidManifest_services.xml.in
     </application>
 
     <permission android:name="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"
                 android:protectionLevel="signature"/>
 
 </manifest> 
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -67,16 +67,17 @@ FENNEC_JAVA_FILES = \
   AwesomeBar.java \
   AwesomeBarTabs.java \
   BrowserToolbar.java \
   ConfirmPreference.java \
   SyncPreference.java \
   db/AndroidBrowserDB.java \
   db/BrowserDB.java \
   db/LocalBrowserDB.java \
+  db/DBUtils.java \
   DoorHanger.java \
   DoorHangerPopup.java \
   Favicons.java \
   FloatUtils.java \
   GeckoActionBar.java \
   GeckoApp.java \
   GeckoAppShell.java \
   GeckoAsyncTask.java \
@@ -144,16 +145,17 @@ endif
 
 FENNEC_PP_JAVA_FILES = \
   App.java \
   LauncherShortcuts.java \
   NotificationHandler.java \
   Restarter.java \
   db/BrowserContract.java \
   db/BrowserProvider.java \
+  db/PasswordsProvider.java \
   SmsManager.java \
   $(NULL)
 
 
 ifneq (,$(findstring -march=armv7,$(OS_CFLAGS)))
 MIN_CPU_VERSION=7
 else
 MIN_CPU_VERSION=5
--- a/mobile/android/base/db/BrowserContract.java.in
+++ b/mobile/android/base/db/BrowserContract.java.in
@@ -38,17 +38,25 @@
 #filter substitution
 package org.mozilla.gecko.db;
 
 import android.net.Uri;
 
 public class BrowserContract {
     public static final String AUTHORITY = "@ANDROID_PACKAGE_NAME@.db.browser";
 
+    public static final String PASSWORDS_AUTHORITY = "@ANDROID_PACKAGE_NAME@.db.passwords";
+
+    public static final String DELETED_PASSWORDS_AUTHORITY = "@ANDROID_PACKAGE_NAME@.db.deleted-passwords";
+
     public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
+    
+    public static final Uri PASSWORDS_AUTHORITY_URI = Uri.parse("content://" + PASSWORDS_AUTHORITY);
+
+    public static final Uri DELETED_PASSWORDS_AUTHORITY_URI = Uri.parse("content://" + DELETED_PASSWORDS_AUTHORITY);
 
     public static final String DEFAULT_PROFILE = "default";
 
     public static final String PARAM_PROFILE = "profile";
 
     public static final String PARAM_LIMIT = "limit";
 
     public static final String PARAM_IS_SYNC = "sync";
@@ -136,9 +144,57 @@ public class BrowserContract {
 
     public static final class Schema {
         private Schema() {}
 
         public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "schema");
 
         public static final String VERSION = "version";
     }
+
+    public static final class Passwords implements CommonColumns, SyncColumns {
+        private Passwords() {}
+
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(PASSWORDS_AUTHORITY_URI, "passwords");
+
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/passwords";
+
+        public static final String ID = "id";
+
+        public static final String HOSTNAME = "hostname";
+
+        public static final String HTTP_REALM = "httpRealm";
+
+        public static final String FORM_SUBMIT_URL = "formSubmitURL";
+
+        public static final String USERNAME_FIELD = "usernameField";
+
+        public static final String PASSWORD_FIELD = "passwordField";
+
+        public static final String ENCRYPTED_USERNAME = "encryptedUsername";
+
+        public static final String ENCRYPTED_PASSWORD = "encryptedPassword";
+
+        public static final String ENC_TYPE = "encType";
+
+        public static final String TIME_CREATED = "timeCreated";
+
+        public static final String TIME_LAST_USED = "timeLastUsed";
+
+        public static final String TIME_PASSWORD_CHANGED = "timePasswordChanged";
+
+        public static final String TIMES_USED = "timesUsed";
+    }
+
+    public static final class DeletedPasswords implements CommonColumns, SyncColumns {
+        private DeletedPasswords() {}
+
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(DELETED_PASSWORDS_AUTHORITY_URI, "deleted-passwords");
+
+        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/deleted-passwords";
+
+        public static final String ID = "id";
+
+        public static final String GUID = "guid";
+
+        public static final String TIME_DELETED = "timeDeleted";
+    }
 }
--- a/mobile/android/base/db/BrowserProvider.java.in
+++ b/mobile/android/base/db/BrowserProvider.java.in
@@ -51,16 +51,18 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.CommonColumns;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Images;
 import org.mozilla.gecko.db.BrowserContract.Schema;
 import org.mozilla.gecko.db.BrowserContract.SyncColumns;
 import org.mozilla.gecko.db.BrowserContract.URLColumns;
 import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.DBUtils;
+import org.mozilla.gecko.sync.Utils;
 
 import android.content.ContentProvider;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.MatrixCursor;
@@ -203,62 +205,16 @@ public class BrowserProvider extends Con
     }
 
     private HashMap<String, DatabaseHelper> mDatabasePerProfile;
 
     static final String qualifyColumn(String table, String column) {
         return table + "." + column;
     }
 
-    public static String generateGuid() {
-        byte[] encodedBytes = GeckoAppShell.encodeBase64(generateRandomBytes(9), GeckoAppShell.BASE64_URL_SAFE);
-        return new String(encodedBytes);
-    }
-
-    private static byte[] generateRandomBytes(int length) {
-        byte[] bytes = new byte[length];
-
-        Random random = new Random(System.nanoTime());
-        random.nextBytes(bytes);
-
-        return bytes;
-    }
-
-    // This is available in Android >= 11. Implemented locally to be
-    // compatible with older versions.
-    public static String concatenateWhere(String a, String b) {
-        if (TextUtils.isEmpty(a)) {
-            return b;
-        }
-
-        if (TextUtils.isEmpty(b)) {
-            return a;
-        }
-
-        return "(" + a + ") AND (" + b + ")";
-    }
-
-    // This is available in Android >= 11. Implemented locally to be
-    // compatible with older versions.
-    public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
-        if (originalValues == null || originalValues.length == 0) {
-            return newValues;
-        }
-
-        if (newValues == null || newValues.length == 0) {
-            return originalValues;
-        }
-
-        String[] result = new String[originalValues.length + newValues.length];
-        System.arraycopy(originalValues, 0, result, 0, originalValues.length);
-        System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
-
-        return result;
-    }
-
     private static boolean hasImagesInProjection(String[] projection) {
         if (projection == null) return true;
         for (int i = 0; i < projection.length; ++i) {
             if (projection[i].equals(Images.FAVICON) ||
                 projection[i].equals(Images.THUMBNAIL))
                 return true;
         }
 
@@ -614,46 +570,46 @@ public class BrowserProvider extends Con
 
         final int match = URI_MATCHER.match(uri);
         int deleted = 0;
 
         switch (match) {
             case BOOKMARKS_ID:
                 Log.d(LOGTAG, "Delete on BOOKMARKS_ID: " + uri);
 
-                selection = concatenateWhere(selection, TABLE_BOOKMARKS + "._id = ?");
-                selectionArgs = appendSelectionArgs(selectionArgs,
+                selection = DBUtils.concatenateWhere(selection, TABLE_BOOKMARKS + "._id = ?");
+                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
                 // fall through
             case BOOKMARKS: {
                 Log.d(LOGTAG, "Deleting bookmarks: " + uri);
                 deleted = deleteBookmarks(uri, selection, selectionArgs);
                 deleteUnusedImages(uri);
                 break;
             }
 
             case HISTORY_ID:
                 Log.d(LOGTAG, "Delete on HISTORY_ID: " + uri);
 
-                selection = concatenateWhere(selection, TABLE_HISTORY + "._id = ?");
-                selectionArgs = appendSelectionArgs(selectionArgs,
+                selection = DBUtils.concatenateWhere(selection, TABLE_HISTORY + "._id = ?");
+                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
                 // fall through
             case HISTORY: {
                 Log.d(LOGTAG, "Deleting history: " + uri);
                 deleted = deleteHistory(uri, selection, selectionArgs);
                 deleteUnusedImages(uri);
                 break;
             }
 
             case IMAGES_ID:
                 Log.d(LOGTAG, "Delete on IMAGES_ID: " + uri);
 
-                selection = concatenateWhere(selection, TABLE_IMAGES + "._id = ?");
-                selectionArgs = appendSelectionArgs(selectionArgs,
+                selection = DBUtils.concatenateWhere(selection, TABLE_IMAGES + "._id = ?");
+                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
                 // fall through
             case IMAGES: {
                 Log.d(LOGTAG, "Deleting images: " + uri);
                 deleted = deleteImages(uri, selection, selectionArgs);
                 break;
             }
 
@@ -702,17 +658,17 @@ public class BrowserProvider extends Con
                 Log.d(LOGTAG, "Insert on BOOKMARKS: " + uri);
 
                 long now = System.currentTimeMillis();
                 values.put(Bookmarks.DATE_CREATED, now);
                 values.put(Bookmarks.DATE_MODIFIED, now);
 
                 // Generate GUID for new bookmark. Don't override specified GUIDs.
                 if (!values.containsKey(Bookmarks.GUID)) {
-                  values.put(Bookmarks.GUID, generateGuid());
+                  values.put(Bookmarks.GUID, Utils.generateGuid());
                 }
 
                 if (!values.containsKey(Bookmarks.POSITION)) {
                     Log.d(LOGTAG, "Inserting bookmark with no position for URI");
                     values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
                 }
 
                 String url = values.getAsString(Bookmarks.URL);
@@ -735,17 +691,17 @@ public class BrowserProvider extends Con
                 Log.d(LOGTAG, "Insert on HISTORY: " + uri);
 
                 long now = System.currentTimeMillis();
                 values.put(History.DATE_CREATED, now);
                 values.put(History.DATE_MODIFIED, now);
 
                 // Generate GUID for new history entry. Don't override specified GUIDs.
                 if (!values.containsKey(History.GUID)) {
-                  values.put(History.GUID, generateGuid());
+                  values.put(History.GUID, Utils.generateGuid());
                 }
 
                 String url = values.getAsString(History.URL);
 
                 ContentValues imageValues = extractImageValues(values,
                         values.getAsString(History.URL));
 
                 if (imageValues != null) {
@@ -762,17 +718,17 @@ public class BrowserProvider extends Con
             case IMAGES: {
                 Log.d(LOGTAG, "Insert on IMAGES: " + uri);
 
                 long now = System.currentTimeMillis();
                 values.put(History.DATE_CREATED, now);
                 values.put(History.DATE_MODIFIED, now);
 
                 // Generate GUID for new history entry
-                values.put(History.GUID, generateGuid());
+                values.put(History.GUID, Utils.generateGuid());
 
                 String url = values.getAsString(Images.URL);
 
                 Log.d(LOGTAG, "Inserting image in database with URL: " + url);
                 id = db.insertOrThrow(TABLE_IMAGES, Images.URL, values);
                 break;
             }
 
@@ -820,31 +776,31 @@ public class BrowserProvider extends Con
 
         int match = URI_MATCHER.match(uri);
         int updated = 0;
 
         switch (match) {
             case BOOKMARKS_ID:
                 Log.d(LOGTAG, "Update on BOOKMARKS_ID: " + uri);
 
-                selection = concatenateWhere(selection, TABLE_BOOKMARKS + "._id = ?");
-                selectionArgs = appendSelectionArgs(selectionArgs,
+                selection = DBUtils.concatenateWhere(selection, TABLE_BOOKMARKS + "._id = ?");
+                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
                 // fall through
             case BOOKMARKS: {
                 Log.d(LOGTAG, "Updating bookmark: " + uri);
                 updated = updateBookmarks(uri, values, selection, selectionArgs);
                 break;
             }
 
             case HISTORY_ID:
                 Log.d(LOGTAG, "Update on HISTORY_ID: " + uri);
 
-                selection = concatenateWhere(selection, TABLE_HISTORY + "._id = ?");
-                selectionArgs = appendSelectionArgs(selectionArgs,
+                selection = DBUtils.concatenateWhere(selection, TABLE_HISTORY + "._id = ?");
+                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
                 // fall through
             case HISTORY: {
                 Log.d(LOGTAG, "Updating history: " + uri);
                 updated = updateHistory(uri, values, selection, selectionArgs);
                 break;
             }
 
@@ -882,27 +838,27 @@ public class BrowserProvider extends Con
 
         switch (match) {
             case BOOKMARKS_FOLDER_ID:
             case BOOKMARKS_ID:
             case BOOKMARKS: {
                 Log.d(LOGTAG, "Query is on bookmarks: " + uri);
 
                 if (match == BOOKMARKS_ID) {
-                    selection = concatenateWhere(selection, Bookmarks._ID + " = ?");
-                    selectionArgs = appendSelectionArgs(selectionArgs,
+                    selection = DBUtils.concatenateWhere(selection, Bookmarks._ID + " = ?");
+                    selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                             new String[] { Long.toString(ContentUris.parseId(uri)) });
                 } else if (match == BOOKMARKS_FOLDER_ID) {
-                    selection = concatenateWhere(selection, Bookmarks.PARENT + " = ?");
-                    selectionArgs = appendSelectionArgs(selectionArgs,
+                    selection = DBUtils.concatenateWhere(selection, Bookmarks.PARENT + " = ?");
+                    selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                             new String[] { Long.toString(ContentUris.parseId(uri)) });
                 }
 
                 if (!shouldShowDeleted(uri))
-                    selection = concatenateWhere(Bookmarks.IS_DELETED + " = 0", selection);
+                    selection = DBUtils.concatenateWhere(Bookmarks.IS_DELETED + " = 0", selection);
 
                 if (TextUtils.isEmpty(sortOrder)) {
                     sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
                 } else {
                     Log.d(LOGTAG, "Using sort order " + sortOrder + ".");
                 }
 
                 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
@@ -915,23 +871,23 @@ public class BrowserProvider extends Con
                 break;
             }
 
             case HISTORY_ID:
             case HISTORY: {
                 Log.d(LOGTAG, "Query is on history: " + uri);
 
                 if (match == HISTORY_ID) {
-                    selection = concatenateWhere(selection, History._ID + " = ?");
-                    selectionArgs = appendSelectionArgs(selectionArgs,
+                    selection = DBUtils.concatenateWhere(selection, History._ID + " = ?");
+                    selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                             new String[] { Long.toString(ContentUris.parseId(uri)) });
                 }
 
                 if (!shouldShowDeleted(uri))
-                    selection = concatenateWhere(History.IS_DELETED + " = 0", selection);
+                    selection = DBUtils.concatenateWhere(History.IS_DELETED + " = 0", selection);
 
                 if (TextUtils.isEmpty(sortOrder))
                     sortOrder = DEFAULT_HISTORY_SORT_ORDER;
 
                 qb.setProjectionMap(HISTORY_PROJECTION_MAP);
 
                 if (hasImagesInProjection(projection))
                     qb.setTables(VIEW_HISTORY_WITH_IMAGES);
@@ -941,23 +897,23 @@ public class BrowserProvider extends Con
                 break;
             }
 
             case IMAGES_ID:
             case IMAGES: {
                 Log.d(LOGTAG, "Query is on images: " + uri);
 
                 if (match == IMAGES_ID) {
-                    selection = concatenateWhere(selection, Images._ID + " = ?");
-                    selectionArgs = appendSelectionArgs(selectionArgs,
+                    selection = DBUtils.concatenateWhere(selection, Images._ID + " = ?");
+                    selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
                             new String[] { Long.toString(ContentUris.parseId(uri)) });
                 }
 
                 if (!shouldShowDeleted(uri))
-                    selection = concatenateWhere(Images.IS_DELETED + " = 0", selection);
+                    selection = DBUtils.concatenateWhere(Images.IS_DELETED + " = 0", selection);
 
                 qb.setProjectionMap(IMAGES_PROJECTION_MAP);
                 qb.setTables(TABLE_IMAGES);
 
                 break;
             }
 
             case SCHEMA: {
@@ -1176,17 +1132,17 @@ public class BrowserProvider extends Con
             values.put(Images.IS_DELETED, 0);
 
         Log.d(LOGTAG, "Trying to update image for URL: " + url);
         int updated = db.update(TABLE_IMAGES, values, selection, selectionArgs);
 
         if (updated == 0 && insertIfNeeded) {
             // Generate GUID for new image, if one is not already provided.
             if (!values.containsKey(Images.GUID)) {
-              values.put(Images.GUID, generateGuid());
+              values.put(Images.GUID, Utils.generateGuid());
             }
 
             values.put(Images.DATE_CREATED, now);
             values.put(Images.DATE_MODIFIED, now);
 
             Log.d(LOGTAG, "No update, inserting image for URL: " + url);
             db.insert(TABLE_IMAGES, Images.FAVICON, values);
             updated = 1;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/db/DBUtils.java
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.db;
+
+import org.mozilla.gecko.sync.Utils;
+
+import android.content.ContentValues;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+import java.util.UUID;
+
+import java.util.Random;
+
+public class DBUtils {
+    public static final String qualifyColumn(String table, String column) {
+        return table + "." + column;
+    }
+
+    // This is available in Android >= 11. Implemented locally to be
+    // compatible with older versions.
+    public static String concatenateWhere(String a, String b) {
+        if (TextUtils.isEmpty(a)) {
+            return b;
+        }
+
+        if (TextUtils.isEmpty(b)) {
+            return a;
+        }
+
+        return "(" + a + ") AND (" + b + ")";
+    }
+
+    // This is available in Android >= 11. Implemented locally to be
+    // compatible with older versions.
+    public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
+        if (originalValues == null || originalValues.length == 0) {
+            return newValues;
+        }
+
+        if (newValues == null || newValues.length == 0) {
+            return originalValues;
+        }
+
+        String[] result = new String[originalValues.length + newValues.length];
+        System.arraycopy(originalValues, 0, result, 0, originalValues.length);
+        System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
+
+        return result;
+    }
+
+    public static void replaceKey(ContentValues aValues, String aOriginalKey,
+                                  String aNewKey, String aDefault) {
+        String value = aDefault;
+        if (aOriginalKey != null && aValues.containsKey(aOriginalKey)) {
+            value = aValues.get(aOriginalKey).toString();
+            aValues.remove(aOriginalKey);
+        }
+
+        if (!aValues.containsKey(aOriginalKey)) {
+            aValues.put(aNewKey, value);
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/db/PasswordsProvider.java.in
@@ -0,0 +1,412 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#filter substitution
+package @ANDROID_PACKAGE_NAME@.db;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.IllegalArgumentException;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.Random;
+
+import org.mozilla.gecko.GeckoApp;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoDirProvider;
+import org.mozilla.gecko.GeckoEvent;
+import org.mozilla.gecko.GeckoEventListener;
+import org.mozilla.gecko.db.BrowserContract.CommonColumns;
+import org.mozilla.gecko.db.DBUtils;
+import org.mozilla.gecko.db.BrowserContract.Passwords;
+import org.mozilla.gecko.db.BrowserContract.DeletedPasswords;
+import org.mozilla.gecko.db.BrowserContract.SyncColumns;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.sqlite.SQLiteBridge;
+import org.mozilla.gecko.sqlite.SQLiteBridgeException;
+import org.mozilla.gecko.sync.Utils;
+
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+public class PasswordsProvider extends ContentProvider {
+    private Context mContext = null;
+    private static final String LOGTAG = "GeckoPasswordsProvider";
+
+    static final String DATABASE_NAME = "signons.sqlite";
+
+    static final int DATABASE_VERSION = 5;
+    
+    static final String TABLE_PASSWORDS = "moz_logins";
+    static final String TABLE_DELETED_PASSWORDS = "deleted_logins";
+
+    private static final int PASSWORDS = 500;
+    private static final int DELETED_PASSWORDS = 502;
+
+    static final String DEFAULT_PASSWORDS_SORT_ORDER = Passwords.HOSTNAME + " ASC";
+    static final String DEFAULT_DELETED_PASSWORDS_SORT_ORDER = DeletedPasswords.TIME_DELETED + " ASC";
+
+    private static final UriMatcher URI_MATCHER;
+
+    private static HashMap<String, String> PASSWORDS_PROJECTION_MAP;
+    private static HashMap<String, String> DELETED_PASSWORDS_PROJECTION_MAP;
+
+    private HashMap<String, SQLiteBridge> mDatabasePerProfile;
+
+    private static ArrayList<String> mSyncColumns;
+
+    static {
+        URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
+
+        // content://org.mozilla.gecko.providers.browser/passwords/#
+        URI_MATCHER.addURI(BrowserContract.PASSWORDS_AUTHORITY, "passwords", PASSWORDS);
+
+        PASSWORDS_PROJECTION_MAP = new HashMap<String, String>();
+        PASSWORDS_PROJECTION_MAP.put(Passwords.ID, Passwords.ID);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.HOSTNAME, Passwords.HOSTNAME);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.HTTP_REALM, Passwords.HTTP_REALM);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.FORM_SUBMIT_URL, Passwords.FORM_SUBMIT_URL);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.USERNAME_FIELD, Passwords.USERNAME_FIELD);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.PASSWORD_FIELD, Passwords.PASSWORD_FIELD);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_USERNAME, Passwords.ENCRYPTED_USERNAME);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.ENCRYPTED_PASSWORD, Passwords.ENCRYPTED_PASSWORD);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.GUID, Passwords.GUID);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.ENC_TYPE, Passwords.ENC_TYPE);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_CREATED, Passwords.TIME_CREATED);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_LAST_USED, Passwords.TIME_LAST_USED);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.TIME_PASSWORD_CHANGED, Passwords.TIME_PASSWORD_CHANGED);
+        PASSWORDS_PROJECTION_MAP.put(Passwords.TIMES_USED, Passwords.TIMES_USED);
+
+        URI_MATCHER.addURI(BrowserContract.DELETED_PASSWORDS_AUTHORITY, "deleted-passwords", DELETED_PASSWORDS);
+
+        mSyncColumns = new ArrayList<String>();
+        mSyncColumns.add(Passwords._ID);
+        mSyncColumns.add(Passwords.DATE_CREATED);
+        mSyncColumns.add(Passwords.DATE_MODIFIED);
+
+        DELETED_PASSWORDS_PROJECTION_MAP = new HashMap<String, String>();
+        DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.ID, DeletedPasswords.ID);
+        DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.GUID, DeletedPasswords.GUID);
+        DELETED_PASSWORDS_PROJECTION_MAP.put(DeletedPasswords.TIME_DELETED, DeletedPasswords.TIME_DELETED);
+    }
+
+    private SQLiteBridge getDB(Context context, final String databasePath) {
+        SQLiteBridge mBridge = null;
+        try {
+            mBridge = new SQLiteBridge(databasePath);
+
+            boolean dbNeedsSetup = true;
+            try {
+                int version = mBridge.getVersion();
+                Log.i(LOGTAG, version + " == " + DATABASE_VERSION);
+                dbNeedsSetup = version != DATABASE_VERSION;
+            } catch(Exception ex) {
+                Log.e(LOGTAG, "Error getting version ", ex);
+                // if Gecko is not running, we should bail out. Otherwise we try to
+                // let Gecko build the database for us
+                if (!GeckoApp.checkLaunchState(GeckoApp.LaunchState.GeckoRunning)) {
+                    mBridge = null;
+                    throw new UnsupportedOperationException("Need to launch Gecko to set password database up");
+                }
+            }
+            if (dbNeedsSetup) {
+                Log.i(LOGTAG, "Sending init to gecko");
+                mBridge = null;
+                GeckoAppShell.sendEventToGecko(new GeckoEvent("Passwords:Init", databasePath));
+            }
+        } catch(SQLiteBridgeException ex) {
+            Log.e(LOGTAG, "Error getting database", ex);
+        }
+        return mBridge;
+    }
+
+    private SQLiteBridge getDatabaseForProfile(String profile) {
+        // Each profile has a separate signons.sqlite database. The target
+        // profile is provided using a URI query argument in each request
+        // to our content provider.
+
+        if (TextUtils.isEmpty(profile)) {
+            Log.d(LOGTAG, "No profile provided, using default");
+            profile = BrowserContract.DEFAULT_PROFILE;
+        }
+
+        SQLiteBridge db = mDatabasePerProfile.get(profile);
+
+        if (db == null) {
+            synchronized (this) {
+                try {
+                    db = getDB(getContext(), getDatabasePath(profile));
+                } catch(UnsupportedOperationException ex) {
+                    Log.i(LOGTAG, "Gecko has not set the database up yet");
+                    return db;
+                }
+                mDatabasePerProfile.put(profile, db);
+            }
+        }
+
+        Log.d(LOGTAG, "Successfully created database helper for profile: " + profile);
+
+        return db;
+    }
+
+    private String getDatabasePath(String profile) {
+        File profileDir = null;
+        try {
+            profileDir = GeckoDirProvider.getProfileDir(mContext, profile);
+        } catch (IOException ex) {
+            Log.e(LOGTAG, "Error getting profile dir", ex);
+        }
+
+        if (profileDir == null) {
+            Log.d(LOGTAG, "Couldn't find directory for profile: " + profile);
+            return null;
+        }
+
+        String databasePath = new File(profileDir, DATABASE_NAME).getAbsolutePath();
+        return databasePath;
+    }
+
+    private SQLiteBridge getDatabase(Uri uri) {
+        String profile = null;
+
+        if (uri != null)
+            profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
+
+        return getDatabaseForProfile(profile);
+    }
+
+    @Override
+    public boolean onCreate() {
+        mContext = getContext();
+        mDatabasePerProfile = new HashMap<String, SQLiteBridge>();
+
+        return true;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        final int match = URI_MATCHER.match(uri);
+
+        switch (match) {
+            case PASSWORDS:
+                return Passwords.CONTENT_TYPE;
+            case DELETED_PASSWORDS:
+                return DeletedPasswords.CONTENT_TYPE;
+        }
+
+        Log.d(LOGTAG, "URI has unrecognized type: " + uri);
+        return null;
+    }
+
+    private String getTable(Uri uri) {
+        final int match = URI_MATCHER.match(uri);
+        switch (match) {
+            case DELETED_PASSWORDS:
+                return TABLE_DELETED_PASSWORDS;
+            case PASSWORDS: {
+                return TABLE_PASSWORDS;
+            }
+            default:
+                throw new UnsupportedOperationException("Unknown table " + uri);
+        }
+    }
+
+    private String getSortOrder(Uri uri, String aRequested) {
+        if (!TextUtils.isEmpty(aRequested))
+            return aRequested;
+
+        final int match = URI_MATCHER.match(uri);
+        switch (match) {
+            case DELETED_PASSWORDS:
+                return DEFAULT_DELETED_PASSWORDS_SORT_ORDER;
+            case PASSWORDS: {
+                return DEFAULT_PASSWORDS_SORT_ORDER;
+            }
+            default:
+                throw new UnsupportedOperationException("Unknown delete URI " + uri);
+        }
+    }
+
+    private Uri getAuthUri(Uri uri) {
+        final int match = URI_MATCHER.match(uri);
+        switch (match) {
+            case DELETED_PASSWORDS:
+                return BrowserContract.DELETED_PASSWORDS_AUTHORITY_URI;
+            case PASSWORDS: {
+                return BrowserContract.PASSWORDS_AUTHORITY_URI;
+            }
+            default:
+                throw new UnsupportedOperationException("Unknown delete URI " + uri);
+        }
+    }
+
+    private void setupDefaults(Uri uri, ContentValues values)
+            throws IllegalArgumentException {
+        int match = URI_MATCHER.match(uri);
+        long now = System.currentTimeMillis();
+        switch (match) {
+            case DELETED_PASSWORDS:
+                values.put(DeletedPasswords.TIME_DELETED, now);
+
+                // Deleted passwords must contain a guid
+                if (!values.containsKey(Passwords.GUID)) {
+                    throw new IllegalArgumentException("Must provide a GUID for a deleted password");
+                }
+                break;
+            case PASSWORDS: {
+                values.put(Passwords.TIME_CREATED, now);
+
+                // Generate GUID for new password. Don't override specified GUIDs.
+                if (!values.containsKey(Passwords.GUID)) {
+                    String guid = Utils.generateGuid();
+                    values.put(Passwords.GUID, guid);
+                }
+                String nowString = new Long(now).toString();
+                DBUtils.replaceKey(values, CommonColumns._ID, Passwords.ID, "");
+                DBUtils.replaceKey(values, SyncColumns.DATE_CREATED, Passwords.TIME_CREATED, nowString);
+                DBUtils.replaceKey(values, SyncColumns.DATE_MODIFIED, Passwords.TIME_PASSWORD_CHANGED, nowString);
+                DBUtils.replaceKey(values, null, Passwords.HOSTNAME, "");
+                DBUtils.replaceKey(values, null, Passwords.HTTP_REALM, "");
+                DBUtils.replaceKey(values, null, Passwords.FORM_SUBMIT_URL, "");
+                DBUtils.replaceKey(values, null, Passwords.USERNAME_FIELD, "");
+                DBUtils.replaceKey(values, null, Passwords.PASSWORD_FIELD, "");
+                DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_USERNAME, "");
+                DBUtils.replaceKey(values, null, Passwords.ENCRYPTED_PASSWORD, "");
+                DBUtils.replaceKey(values, null, Passwords.ENC_TYPE, "0");
+                DBUtils.replaceKey(values, null, Passwords.TIME_LAST_USED, nowString);
+                DBUtils.replaceKey(values, null, Passwords.TIME_PASSWORD_CHANGED, nowString);
+                DBUtils.replaceKey(values, null, Passwords.TIMES_USED, "0");
+                break;
+            }
+
+            default:
+                throw new UnsupportedOperationException("Unknown insert URI " + uri);
+        }
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        int deleted = 0;
+        final SQLiteBridge db = getDatabase(uri);
+        if (db == null)
+            return deleted;
+
+        String table = getTable(uri);
+        if (table.equals(""))
+            return deleted;
+
+        if (selection != null) {
+            for (String item : mSyncColumns)
+                selection = selection.replace(item, translateColumn(item));
+        }
+
+        try {
+            deleted = db.delete(table, selection, selectionArgs);
+        } catch (SQLiteBridgeException ex) {
+            Log.e(LOGTAG, "Error deleting record", ex);
+        }
+
+        return deleted;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        long id = -1;
+        final SQLiteBridge db = getDatabase(uri);
+        if (db == null)
+            return null;
+
+        String table = getTable(uri);
+        if (table.equals(""))
+            return null;
+
+        try {
+            setupDefaults(uri, values);
+        } catch(Exception ex) {
+            Log.e(LOGTAG, "Error setting up defaults", ex);
+            return null;
+        }
+
+        try {
+            id = db.insert(table, "", values);
+        } catch(SQLiteBridgeException ex) {
+            Log.e(LOGTAG, "Error inserting in db", ex);
+        }
+
+        return ContentUris.withAppendedId(uri, id);
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        int updated = 0;
+        final SQLiteBridge db = getDatabase(uri);
+        if (db == null)
+            return updated;
+
+        String table = getTable(uri);
+        if (TextUtils.isEmpty(table))
+            return updated;
+
+        if (selection != null) {
+            for (String item : mSyncColumns)
+                selection = selection.replace(item, translateColumn(item));
+        }
+
+        try {
+            return db.update(table, values, selection, selectionArgs);
+        } catch(SQLiteBridgeException ex) {
+            Log.e(LOGTAG, "Error updating table", ex);
+        }
+        return 0;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        Cursor cursor = null;
+        final SQLiteBridge db = getDatabase(uri);
+        if (db == null)
+            return cursor;
+
+        String table = getTable(uri);
+        if (table.equals(""))
+            return cursor;
+
+        sortOrder = getSortOrder(uri, sortOrder);
+        if (TextUtils.isEmpty(sortOrder))
+            return cursor;
+
+        try {
+            cursor = db.query(table, projection, selection, selectionArgs, null, null, sortOrder, null);
+            cursor.setNotificationUri(getContext().getContentResolver(), getAuthUri(uri));
+        } catch (SQLiteBridgeException ex) {
+            Log.e(LOGTAG, "Error querying database", ex);
+        }
+
+        return cursor;
+    }
+
+    private String translateColumn(String column) {
+        if (column.equals(SyncColumns.DATE_CREATED))
+            return Passwords.TIME_CREATED;
+        else if (column.equals(SyncColumns.DATE_MODIFIED))
+            return Passwords.TIME_PASSWORD_CHANGED;
+        else if (column.equals(CommonColumns._ID))
+            return Passwords.ID;
+        else
+            return column;
+    }
+}
--- a/mobile/android/base/sqlite/SQLiteBridge.java
+++ b/mobile/android/base/sqlite/SQLiteBridge.java
@@ -1,53 +1,28 @@
-/* -*- 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 ***** */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sqlite;
 
 import org.mozilla.gecko.sqlite.SQLiteBridgeException;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
 
 /*
  * 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";
@@ -63,16 +38,227 @@ public class SQLiteBridge {
                                           ArrayList<Object[]> aRes)
         throws SQLiteBridgeException;
 
     // Takes the path to the database we want to access.
     public SQLiteBridge(String aDb) throws SQLiteBridgeException {
         mDb = aDb;
     }
 
+    // Executes a simple line of sql.
+    public void execSQL(String sql)
+                throws SQLiteBridgeException {
+        try {
+            query(sql, null);
+        } catch(SQLiteBridgeException ex) {
+            throw ex;
+        }
+    }
+
+    // Executes a simple line of sql. Allow you to bind arguments
+    public void execSQL(String sql, String[] bindArgs)
+                throws SQLiteBridgeException {
+        try {
+            query(sql, bindArgs);
+        } catch(SQLiteBridgeException ex) {
+            throw ex;
+        }
+    }
+
+    // Executes a DELETE statement on the database
+    public int delete(String table, String whereClause, String[] whereArgs)
+               throws SQLiteBridgeException {
+        StringBuilder sb = new StringBuilder("DELETE from ");
+        sb.append(table);
+        if (whereClause != null) {
+            sb.append(" WHERE " + whereClause);
+        }
+
+        try {
+            return getIntResult(sb.toString(), whereArgs, 1);
+        } catch(SQLiteBridgeException ex) {
+            throw ex;
+        }
+    }
+
+    public Cursor query(String table,
+                        String[] columns,
+                        String selection,
+                        String[] selectionArgs,
+                        String groupBy,
+                        String having,
+                        String orderBy,
+                        String limit)
+               throws SQLiteBridgeException {
+        StringBuilder sb = new StringBuilder("SELECT ");
+        if (columns != null)
+            sb.append(TextUtils.join(", ", columns));
+        else
+            sb.append(" * ");
+
+        sb.append(" FROM ");
+        sb.append(table);
+
+        if (selection != null) {
+            sb.append(" WHERE " + selection);
+        }
+
+        if (groupBy != null) {
+            sb.append(" GROUP BY " + groupBy);
+        }
+
+        if (having != null) {
+            sb.append(" HAVING " + having);
+        }
+
+        if (orderBy != null) {
+            sb.append(" ORDER BY " + orderBy);
+        }
+
+        if (limit != null) {
+            sb.append(" " + limit);
+        }
+
+        ArrayList<Object[]> results;
+        try {
+            mColumns = null;
+
+            results = query(sb.toString(), selectionArgs);
+
+        } catch(SQLiteBridgeException ex) {
+            throw ex;
+        }
+
+        MatrixCursor cursor = new MatrixCursor(mColumns.toArray(new String[0]));
+        try {
+            for (Object resultRow: results) {
+                Object[] resultColumns = (Object[])resultRow;
+                if (resultColumns.length == mColumns.size())
+                    cursor.addRow(resultColumns);
+            }
+        } catch(IllegalArgumentException ex) {
+            Log.e(LOGTAG, "Error getting rows", ex);
+        }
+
+        return cursor;
+    }
+
+    public long insert(String table, String nullColumnHack, ContentValues values)
+               throws SQLiteBridgeException {
+        Set<Entry<String, Object>> valueSet = values.valueSet();
+        Iterator<Entry<String, Object>> valueIterator = valueSet.iterator();
+        ArrayList<String> valueNames = new ArrayList<String>();
+        ArrayList<String> valueBinds = new ArrayList<String>();
+        ArrayList<String> keyNames = new ArrayList<String>();
+
+        while(valueIterator.hasNext()) {
+            Entry<String, Object> value = valueIterator.next();
+            keyNames.add(value.getKey());
+            valueNames.add("?");
+            valueBinds.add(value.getValue().toString());
+        }
+
+        StringBuilder sb = new StringBuilder("INSERT into ");
+        sb.append(table);
+
+        sb.append(" (");
+        sb.append(TextUtils.join(", ", keyNames));
+        sb.append(")");
+
+        // XXX - Do we need to bind these values?
+        sb.append(" VALUES (");
+        sb.append(TextUtils.join(", ", valueNames));
+        sb.append(") ");
+
+        String[] binds = new String[valueBinds.size()];
+        valueBinds.toArray(binds);
+        try {
+            return getIntResult(sb.toString(), binds, 0);
+        } catch (SQLiteBridgeException ex) {
+            throw ex;
+        }
+    }
+
+    public int update(String table, ContentValues values, String whereClause, String[] whereArgs)
+               throws SQLiteBridgeException {
+        Set<Entry<String, Object>> valueSet = values.valueSet();
+        Iterator<Entry<String, Object>> valueIterator = valueSet.iterator();
+        ArrayList<String> valueNames = new ArrayList<String>();
+
+        StringBuilder sb = new StringBuilder("UPDATE ");
+        sb.append(table);
+
+        sb.append(" SET ");
+        while(valueIterator.hasNext()) {
+            Entry<String, Object> value = valueIterator.next();
+            sb.append(value.getKey() + " = ?");
+            valueNames.add(value.getValue().toString());
+            if (valueIterator.hasNext())
+                sb.append(", ");
+            else
+                sb.append(" ");
+        }
+
+        if (whereClause != null) {
+            sb.append(" WHERE ");
+            sb.append(whereClause);
+            for (int i = 0; i < whereArgs.length; i++) {
+                valueNames.add(whereArgs[i]);
+            }
+        }
+
+        String[] binds = new String[valueNames.size()];
+        valueNames.toArray(binds);
+        try {
+            return getIntResult(sb.toString(), binds, 1);
+        } catch (SQLiteBridgeException ex) {
+            throw ex;
+        }
+    }
+
+    private int getIntResult(String query, String[] params, int resultIndex)
+               throws SQLiteBridgeException {
+        ArrayList<Object[]> results = null;
+        try {
+            mColumns = null;
+            results = query(query, params);
+        } catch(SQLiteBridgeException ex) {
+            throw ex;
+        }
+
+        if (results != null) {
+            for (Object resultRow: results) {
+                Object[] resultColumns = (Object[])resultRow;
+                return ((Number)resultColumns[resultIndex]).intValue();
+            }
+        }
+        return -1;
+    }
+
+    public int getVersion()
+               throws SQLiteBridgeException {
+        ArrayList<Object[]> results = null;
+        try {
+            mColumns = null;
+            results = query("PRAGMA user_version");
+        } catch(SQLiteBridgeException ex) {
+            throw ex;
+        }
+        int ret = -1;
+        if (results != null) {
+            for (Object resultRow: results) {
+                Object[] resultColumns = (Object[])resultRow;
+                String version = (String)resultColumns[0];
+                ret = Integer.parseInt(version);
+            }
+        }
+        return ret;
+    }
+
+
     // 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
@@ -90,12 +276,11 @@ public class SQLiteBridge {
     }
 
     // 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.
-    }
+    // nop, provided for API compatibility with SQLiteDatabase.
+    public void close() { }
 }
\ No newline at end of file
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -216,16 +216,17 @@ var BrowserApp = {
     Services.obs.addObserver(this, "Preferences:Get", false);
     Services.obs.addObserver(this, "Preferences:Set", false);
     Services.obs.addObserver(this, "ScrollTo:FocusedInput", false);
     Services.obs.addObserver(this, "Sanitize:ClearAll", false);
     Services.obs.addObserver(this, "PanZoom:PanZoom", false);
     Services.obs.addObserver(this, "FullScreen:Exit", false);
     Services.obs.addObserver(this, "Viewport:Change", false);
     Services.obs.addObserver(this, "SearchEngines:Get", false);
+    Services.obs.addObserver(this, "Passwords:Init", false);
 
     function showFullScreenWarning() {
       NativeWindow.toast.show(Strings.browser.GetStringFromName("alertFullScreenToast"), "short");
     }
 
     window.addEventListener("fullscreen", function() {
       sendMessageToJava({
         gecko: {
@@ -965,16 +966,22 @@ var BrowserApp = {
       Sanitizer.sanitize();
     } else if (aTopic == "FullScreen:Exit") {
       browser.contentDocument.mozCancelFullScreen();
     } else if (aTopic == "Viewport:Change") {
       this.selectedTab.viewport = JSON.parse(aData);
       ViewportHandler.onResize();
     } else if (aTopic == "SearchEngines:Get") {
       this.getSearchEngines();
+    } else if (aTopic == "Passwords:Init") {
+      var storage = Components.classes["@mozilla.org/login-manager/storage/mozStorage;1"].  
+        getService(Components.interfaces.nsILoginManagerStorage);
+      storage.init();
+
+      sendMessageToJava({gecko: { type: "Passwords:Init:Return" }});
     }
   },
 
   get defaultBrowserWidth() {
     delete this.defaultBrowserWidth;
     let width = Services.prefs.getIntPref("browser.viewport.desktopWidth");
     return this.defaultBrowserWidth = width;
   }
--- a/mozglue/android/SQLiteBridge.cpp
+++ b/mozglue/android/SQLiteBridge.cpp
@@ -30,16 +30,17 @@
  * 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 <stdio.h>
 #include <jni.h>
 #include <android/log.h>
 #include "dlfcn.h"
 #include "APKOpen.h"
 #ifndef MOZ_OLD_LINKER
 #include "ElfLoader.h"
 #endif
 #include "SQLiteBridge.h"
@@ -61,16 +62,18 @@ 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)
+SQLITE_WRAPPER_INT(sqlite3_changes)
+SQLITE_WRAPPER_INT(sqlite3_last_insert_rowid)
 
 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);
@@ -79,16 +82,18 @@ void setup_sqlite_functions(void *sqlite
   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);
+  GETFUNC(sqlite3_changes);
+  GETFUNC(sqlite3_last_insert_rowid);
 #undef GETFUNC
 }
 
 static bool initialized = false;
 static jclass stringClass;
 static jclass objectClass;
 static jclass byteBufferClass;
 static jclass arrayListClass;
@@ -152,102 +157,134 @@ Java_org_mozilla_gecko_sqlite_SQLiteBrid
                                                       jstring jDb,
                                                       jstring jQuery,
                                                       jobjectArray jParams,
                                                       jobject jColumns,
                                                       jobject jArrayList)
 {
     JNI_Setup(jenv);
 
+    char* errorMsg;
+    jsize numPars = 0;
+
     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));
+        asprintf(&errorMsg, "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));
+        asprintf(&errorMsg, "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);
+    if (jParams != NULL) {
+        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",
+        asprintf(&errorMsg, "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;
+
+    if (jParams != NULL) {
+        // 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) {
+                    asprintf(&errorMsg, "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) {
+                    asprintf(&errorMsg, "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));
+        asprintf(&errorMsg, "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);
     }
 
+    // if the statement doesn't return any results, instead return the id and number of changed rows
+    if (rc == SQLITE_DONE) {
+        jclass integerClass = jenv->FindClass("java/lang/Integer");
+        jmethodID intConstructor = jenv->GetMethodID(integerClass, "<init>", "(I)V");
+        
+        jobjectArray jRow = jenv->NewObjectArray(2, objectClass, NULL);
+        if (jRow == NULL) {
+            asprintf(&errorMsg, "Can't allocate jRow Object[]\n");
+            goto error_close;
+        }
+
+        int id = f_sqlite3_last_insert_rowid(db);
+        jobject jId = jenv->NewObject(integerClass, intConstructor, id);
+        jenv->SetObjectArrayElement(jRow, 0, jId);
+        jenv->DeleteLocalRef(jId);
+
+        int changed = f_sqlite3_changes(db);
+        jobject jChanged = jenv->NewObject(integerClass, intConstructor, changed);
+        jenv->SetObjectArrayElement(jRow, 1, jChanged);
+        jenv->DeleteLocalRef(jChanged);
+
+        jenv->CallBooleanMethod(jArrayList, jArrayListAdd, jRow);
+        jenv->DeleteLocalRef(jRow);
+    }
+
     // For each row, add an Object[] to the passed ArrayList,
     // with that containing either String or ByteArray objects
     // containing the columns
     while (rc != SQLITE_DONE) {
         // Process row
         // Construct Object[]
         jobjectArray jRow = jenv->NewObjectArray(cols,
                                                  objectClass,
                                                  NULL);
         if (jRow == NULL) {
-            LOG("Can't allocate jRow Object[]\n");
+            asprintf(&errorMsg, "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);
@@ -260,17 +297,17 @@ Java_org_mozilla_gecko_sqlite_SQLiteBrid
                                                  colLen);
                 if (jByteBuffer == NULL) {
                     goto error_close;
                 }
 
                 // Get its backing array
                 void* bufferArray = jenv->GetDirectBufferAddress(jByteBuffer);
                 if (bufferArray == NULL) {
-                    LOG("Failure calling GetDirectBufferAddress\n");
+                    asprintf(&errorMsg, "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);
@@ -289,27 +326,29 @@ Java_org_mozilla_gecko_sqlite_SQLiteBrid
 
         // 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));
+            asprintf(&errorMsg, "Can't re-step statement:(%d) %s\n", rc, f_sqlite3_errmsg(db));
             goto error_close;
         }
     }
 
     rc = f_sqlite3_finalize(ppStmt);
     if (rc != SQLITE_OK) {
-        LOG("Can't finalize statement: %s\n", f_sqlite3_errmsg(db));
+        asprintf(&errorMsg, "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");
+    LOG("Error in SQLiteBridge: %s\n", errorMsg);
+    JNI_Throw(jenv, "org/mozilla/gecko/sqlite/SQLiteBridgeException", errorMsg);
+    free(errorMsg);
     return;
 }
--- a/mozglue/android/SQLiteBridge.h
+++ b/mozglue/android/SQLiteBridge.h
@@ -54,10 +54,12 @@ SQLITE_WRAPPER(sqlite3_step, int, sqlite
 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)
+SQLITE_WRAPPER(sqlite3_changes, int, sqlite3*)
+SQLITE_WRAPPER(sqlite3_last_insert_rowid, sqlite3_int64, sqlite3*)
 
 #endif /* SQLiteBridge_h */