A bunch of updates to BrowserProvider2.

The images are now shared between the history
and bookmarks tables so updates to one are
reflected in the other.

Added a parameter for specifying a limit when
calling query().

Added a combined view of history and bookmarks.

Added a way to get a distinct list of the
accounts providing bookmarks.

Added the ability to find the server unique
IDs for parent and insert_after when doing
a query for a row.

Change-Id: I9afa15bcf7ca68468793c49fbec701e516e4540e
diff --git a/src/com/android/browser/Bookmarks.java b/src/com/android/browser/Bookmarks.java
index 3ead203..532d7c0 100644
--- a/src/com/android/browser/Bookmarks.java
+++ b/src/com/android/browser/Bookmarks.java
@@ -25,6 +25,8 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.provider.BrowserContract;
+import android.provider.BrowserContract.Combined;
+import android.provider.BrowserContract.Images;
 import android.util.Log;
 import android.webkit.WebIconDatabase;
 import android.widget.Toast;
@@ -153,13 +155,12 @@
     }
 
     static final String QUERY_BOOKMARKS_WHERE =
-            BrowserContract.Bookmarks.IS_FOLDER + " == 0 AND (" + 
-            BrowserContract.Bookmarks.URL + " == ? OR " +
-            BrowserContract.Bookmarks.URL + " == ? OR " +
-            BrowserContract.Bookmarks.URL + " LIKE ? || '%' OR " +
-            BrowserContract.Bookmarks.URL + " LIKE ? || '%')";
+            Combined.URL + " == ? OR " +
+            Combined.URL + " == ? OR " +
+            Combined.URL + " LIKE ? || '%' OR " +
+            Combined.URL + " LIKE ? || '%'";
 
-    /* package */ static Cursor queryBookmarksForUrl(ContentResolver cr,
+    /* package */ static Cursor queryCombinedForUrl(ContentResolver cr,
             String originalUrl, String url) {
         if (cr == null || url == null) {
             return null;
@@ -172,8 +173,8 @@
     
         // Look for both the original url and the actual url. This takes in to
         // account redirects.
-        String originalUrlNoQuery = Bookmarks.removeQuery(originalUrl);
-        String urlNoQuery = Bookmarks.removeQuery(url);
+        String originalUrlNoQuery = removeQuery(originalUrl);
+        String urlNoQuery = removeQuery(url);
         originalUrl = originalUrlNoQuery + '?';
         url = urlNoQuery + '?';
     
@@ -182,11 +183,9 @@
         // Use url to match the base url with other queries (i.e. if the url is
         // http://www.google.com/m, search for
         // http://www.google.com/m?some_query)
-        final String[] selArgs = new String[] {
-                originalUrlNoQuery, urlNoQuery, originalUrl, url };
-        final String[] projection = new String[] { BrowserContract.Bookmarks._ID };
-        return cr.query(BrowserContract.Bookmarks.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE,
-                selArgs, null);
+        final String[] selArgs = new String[] { originalUrlNoQuery, urlNoQuery, originalUrl, url };
+        final String[] projection = new String[] { Combined.URL };
+        return cr.query(Combined.CONTENT_URI, projection, QUERY_BOOKMARKS_WHERE, selArgs, null);
     }
 
     // Strip the query from the given url.
@@ -210,30 +209,28 @@
      * @param url The current url.
      * @param favicon The favicon bitmap to write to the db.
      */
-    /* package */ static void updateBookmarkFavicon(final ContentResolver cr,
+    /* package */ static void updateFavicon(final ContentResolver cr,
             final String originalUrl, final String url, final Bitmap favicon) {
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... unused) {
-                final Cursor c =
-                        Bookmarks.queryBookmarksForUrl(cr, originalUrl, url);
-                if (c == null) {
-                    return null;
+                Cursor cursor = queryCombinedForUrl(cr, originalUrl, url);
+                try {
+                    if (cursor.moveToFirst()) {
+                        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+                        favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+                        ContentValues values = new ContentValues();
+                        values.put(Images.FAVICON, os.toByteArray());
+                        values.put(Images.URL, cursor.getString(0));
+
+                        do {
+                            cr.update(Images.CONTENT_URI, values, null, null);
+                        } while (cursor.moveToNext());
+                    }
+                } finally {
+                    if (cursor != null) cursor.close();
                 }
-                if (c.moveToFirst()) {
-                    ContentValues values = new ContentValues();
-                    final ByteArrayOutputStream os =
-                            new ByteArrayOutputStream();
-                    favicon.compress(Bitmap.CompressFormat.PNG, 100, os);
-                    values.put(BrowserContract.Bookmarks.FAVICON,
-                            os.toByteArray());
-                    do {
-                        cr.update(ContentUris.withAppendedId(
-                                BrowserContract.Bookmarks.CONTENT_URI, c.getLong(0)),
-                                values, null, null);
-                    } while (c.moveToNext());
-                }
-                c.close();
                 return null;
             }
         }.execute();
diff --git a/src/com/android/browser/BookmarksLoader.java b/src/com/android/browser/BookmarksLoader.java
index 9d5e3ed..0947184 100644
--- a/src/com/android/browser/BookmarksLoader.java
+++ b/src/com/android/browser/BookmarksLoader.java
@@ -18,10 +18,13 @@
 
 import android.content.Context;
 import android.content.CursorLoader;
+import android.net.Uri;
 import android.provider.BrowserContract.Bookmarks;
+import android.text.TextUtils;
 
 public class BookmarksLoader extends CursorLoader {
-    public static final String ARG_ROOT_FOLDER = "root";
+    public static final String ARG_ACCOUNT_TYPE = "acct_type";
+    public static final String ARG_ACCOUNT_NAME = "acct_name";
 
     public static final int COLUMN_INDEX_ID = 0;
     public static final int COLUMN_INDEX_URL = 1;
@@ -42,7 +45,26 @@
         Bookmarks.POSITION, // 7
     };
 
-    public BookmarksLoader(Context context, int rootFolder) {
-        super(context, Bookmarks.CONTENT_URI_DEFAULT_FOLDER, PROJECTION, null, null, null);
+    private String mAccountType;
+    private String mAccountName;
+
+    public BookmarksLoader(Context context, String accountType, String accountName) {
+        super(context, addAccount(Bookmarks.CONTENT_URI_DEFAULT_FOLDER, accountType, accountName),
+                PROJECTION, null, null, null);
+        mAccountType = accountType;
+        mAccountName = accountName;
+    }
+
+    @Override
+    public void setUri(Uri uri) {
+        super.setUri(addAccount(uri, mAccountType, mAccountName));
+    }
+
+    private static Uri addAccount(Uri uri, String accountType, String accountName) {
+        if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+            return uri.buildUpon().appendQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE, accountType).
+                    appendQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME, accountName).build();
+        }
+        return uri;
     }
 }
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index e1e1e18..9dd801d 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -69,6 +69,7 @@
 import android.provider.Browser;
 import android.provider.BrowserContract;
 import android.provider.ContactsContract;
+import android.provider.BrowserContract.Images;
 import android.provider.ContactsContract.Intents.Insert;
 import android.provider.Downloads;
 import android.provider.MediaStore;
@@ -2449,29 +2450,25 @@
         new AsyncTask<Void, Void, Void>() {
             @Override
             protected Void doInBackground(Void... unused) {
-                Cursor c = null;
+                Cursor cursor = null;
                 try {
-                    c = Bookmarks.queryBookmarksForUrl(
-                            cr, originalUrl, url);
-                    if (c != null) {
-                        if (c.moveToFirst()) {
-                            ContentValues values = new ContentValues();
-                            final ByteArrayOutputStream os
-                                    = new ByteArrayOutputStream();
-                            bm.compress(Bitmap.CompressFormat.PNG, 100, os);
-                            values.put(BrowserContract.Bookmarks.THUMBNAIL,
-                                    os.toByteArray());
-                            do {
-                                cr.update(ContentUris.withAppendedId(
-                                        BrowserContract.Bookmarks.CONTENT_URI, c.getLong(0)),
-                                        values, null, null);
-                            } while (c.moveToNext());
-                        }
+                    cursor = Bookmarks.queryCombinedForUrl(cr, originalUrl, url);
+                    if (cursor != null && cursor.moveToFirst()) {
+                        final ByteArrayOutputStream os = new ByteArrayOutputStream();
+                        bm.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+                        ContentValues values = new ContentValues();
+                        values.put(Images.THUMBNAIL, os.toByteArray());
+                        values.put(Images.URL, cursor.getString(0));
+
+                        do {
+                            cr.update(Images.CONTENT_URI, values, null, null);
+                        } while (cursor.moveToNext());
                     }
                 } catch (IllegalStateException e) {
                     // Ignore
                 } finally {
-                    if (c != null) c.close();
+                    if (cursor != null) cursor.close();
                 }
                 return null;
             }
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 91785d5..152d4cf 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -22,6 +22,7 @@
 import android.app.LoaderManager;
 import android.content.ClipData;
 import android.content.ClipboardManager;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -33,23 +34,22 @@
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.Browser;
 import android.provider.BrowserContract;
 import android.util.Pair;
 import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.View.OnClickListener;
 import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View.OnClickListener;
 import android.webkit.WebIconDatabase.IconListener;
 import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
 import android.widget.Button;
 import android.widget.GridView;
 import android.widget.Toast;
+import android.widget.AdapterView.OnItemClickListener;
 
 import java.util.Stack;
 
@@ -83,11 +83,13 @@
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         switch (id) {
             case LOADER_BOOKMARKS: {
-                int rootFolder = 0;
+                String accountType = null;
+                String accountName = null;
                 if (args != null) {
-                    args.getInt(BookmarksLoader.ARG_ROOT_FOLDER, 0);
+                    accountType = args.getString(BookmarksLoader.ARG_ACCOUNT_TYPE);
+                    accountName = args.getString(BookmarksLoader.ARG_ACCOUNT_NAME);
                 }
-                return new BookmarksLoader(getActivity(), rootFolder);
+                return new BookmarksLoader(getActivity(), accountType, accountName);
             }
         }
         throw new UnsupportedOperationException("Unknown loader id " + id);
@@ -106,8 +108,9 @@
 
         // Fill in the "up" button if needed
         BookmarksLoader bl = (BookmarksLoader) loader;
+        String path = bl.getUri().getPath();
         boolean rootFolder =
-                (BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER.equals(bl.getUri()));
+                BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER.getPath().equals(path);
         if (rootFolder) {
             mUpButton.setText(R.string.defaultBookmarksUpButton);
             mUpButton.setEnabled(false);
@@ -389,7 +392,6 @@
 
     /**
      *  Update a row in the database with new information.
-     *  Requeries the database if the information has changed.
      *  @param map  Bundle storing id, title and url of new information
      */
     public void updateRow(Bundle map) {
@@ -428,7 +430,6 @@
                     ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id),
                     values, null, null);
         }
-        updateView();
     }
 
     private void displayRemoveBookmarkDialog(final int position) {
@@ -436,15 +437,19 @@
         // delete the bookmark
         Cursor cursor = (Cursor) mAdapter.getItem(position);
         Context context = getActivity();
+        final ContentResolver resolver = context.getContentResolver();
+        final Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI,
+                cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID));
+
         new AlertDialog.Builder(context)
                 .setTitle(R.string.delete_bookmark)
                 .setIcon(android.R.drawable.ic_dialog_alert)
-                .setMessage(context.getText(R.string.delete_bookmark_warning).toString().replace(
-                        "%s", cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
+                .setMessage(context.getString(R.string.delete_bookmark_warning,
+                        cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE)))
                 .setPositiveButton(R.string.ok,
                         new DialogInterface.OnClickListener() {
                             public void onClick(DialogInterface dialog, int whichButton) {
-                                deleteBookmark(position);
+                                resolver.delete(uri, null, null);
                             }
                         })
                 .setNegativeButton(R.string.cancel, null)
@@ -467,16 +472,8 @@
      */
     public void deleteBookmark(int position) {
         Cursor cursor = (Cursor) mAdapter.getItem(position);
-        String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
-        String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
-        Bookmarks.removeFromBookmarks(null, getActivity().getContentResolver(), url, title);
-        updateView();
+        long id = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID);
+        Uri uri = ContentUris.withAppendedId(BrowserContract.Bookmarks.CONTENT_URI, id);
+        getActivity().getContentResolver().delete(uri, null, null);
     }
-
-    private void updateView() {
-        BookmarksLoader loader =
-            (BookmarksLoader) (Loader)(getLoaderManager().getLoader(LOADER_BOOKMARKS));
-        loader.forceLoad();
-    }
-
 }
diff --git a/src/com/android/browser/DownloadTouchIcon.java b/src/com/android/browser/DownloadTouchIcon.java
index 1442683..7bb93dc 100644
--- a/src/com/android/browser/DownloadTouchIcon.java
+++ b/src/com/android/browser/DownloadTouchIcon.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.os.Message;
 import android.provider.BrowserContract;
+import android.provider.BrowserContract.Images;
 import android.webkit.WebView;
 
 import java.io.ByteArrayOutputStream;
@@ -101,15 +102,15 @@
     @Override
     public Void doInBackground(String... values) {
         if (mContentResolver != null) {
-            mCursor = Bookmarks.queryBookmarksForUrl(mContentResolver,
+            mCursor = Bookmarks.queryCombinedForUrl(mContentResolver,
                     mOriginalUrl, mUrl);
         }
 
-        boolean inBookmarksDatabase = mCursor != null && mCursor.getCount() > 0;
+        boolean inDatabase = mCursor != null && mCursor.getCount() > 0;
 
         String url = values[0];
 
-        if (inBookmarksDatabase || mMessage != null) {
+        if (inDatabase || mMessage != null) {
             AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
             HttpHost httpHost = Proxy.getPreferredHttpHost(mActivity, url);
             if (httpHost != null) {
@@ -130,7 +131,7 @@
                         if (content != null) {
                             Bitmap icon = BitmapFactory.decodeStream(
                                     content, null, null);
-                            if (inBookmarksDatabase) {
+                            if (inDatabase) {
                                 storeIcon(icon);
                             } else if (mMessage != null) {
                                 Bundle b = mMessage.getData();
@@ -177,17 +178,16 @@
             return;
         }
 
-        final ByteArrayOutputStream os = new ByteArrayOutputStream();
-        icon.compress(Bitmap.CompressFormat.PNG, 100, os);
-        ContentValues values = new ContentValues();
-        values.put(BrowserContract.Bookmarks.TOUCH_ICON,
-                os.toByteArray());
-
         if (mCursor.moveToFirst()) {
+            final ByteArrayOutputStream os = new ByteArrayOutputStream();
+            icon.compress(Bitmap.CompressFormat.PNG, 100, os);
+
+            ContentValues values = new ContentValues();
+            values.put(Images.TOUCH_ICON, os.toByteArray());
+            values.put(Images.URL, mCursor.getString(0));
+
             do {
-                mContentResolver.update(ContentUris.withAppendedId(
-                        BrowserContract.Bookmarks.CONTENT_URI, mCursor.getLong(0)),
-                        values, null, null);
+                mContentResolver.update(Images.CONTENT_URI, values, null, null);
             } while (mCursor.moveToNext());
         }
     }
diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java
index 54f3088..dc42428 100644
--- a/src/com/android/browser/Tab.java
+++ b/src/com/android/browser/Tab.java
@@ -497,7 +497,7 @@
 
             // update the bookmark database for favicon
             if (favicon != null) {
-                Bookmarks.updateBookmarkFavicon(mActivity
+                Bookmarks.updateFavicon(mActivity
                         .getContentResolver(), null, url, favicon);
             }
 
@@ -1035,7 +1035,7 @@
         @Override
         public void onReceivedIcon(WebView view, Bitmap icon) {
             if (icon != null) {
-                Bookmarks.updateBookmarkFavicon(mActivity
+                Bookmarks.updateFavicon(mActivity
                         .getContentResolver(), view.getOriginalUrl(), view
                         .getUrl(), icon);
             }
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index 28eed79..a633941 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -31,25 +31,36 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.provider.BrowserContract;
-import android.provider.SyncStateContract;
+import android.provider.BrowserContract.Accounts;
 import android.provider.BrowserContract.Bookmarks;
 import android.provider.BrowserContract.ChromeSyncColumns;
+import android.provider.BrowserContract.Combined;
 import android.provider.BrowserContract.History;
+import android.provider.BrowserContract.Images;
 import android.provider.BrowserContract.Searches;
 import android.provider.BrowserContract.SyncState;
 import android.provider.ContactsContract.RawContacts;
+import android.provider.SyncStateContract;
 import android.text.TextUtils;
 
 import java.util.HashMap;
 
 public class BrowserProvider2 extends SQLiteContentProvider {
 
-    static final Uri LEGACY_BROWSER_AUTHORITY_URI = Uri.parse("browser");
+    static final String LEGACY_AUTHORITY = "browser";
+    static final Uri LEGACY_AUTHORITY_URI = new Uri.Builder().authority(LEGACY_AUTHORITY).build();
 
     static final String TABLE_BOOKMARKS = "bookmarks";
     static final String TABLE_HISTORY = "history";
+    static final String TABLE_IMAGES = "images";
     static final String TABLE_SEARCHES = "searches";
     static final String TABLE_SYNC_STATE = "syncstate";
+    static final String VIEW_COMBINED = "combined";
+
+    static final String TABLE_BOOKMARKS_JOIN_IMAGES = "bookmarks LEFT OUTER JOIN images " +
+            "ON bookmarks.url = images." + Images.URL;
+    static final String TABLE_HISTORY_JOIN_IMAGES = "history LEFT OUTER JOIN images " +
+            "ON history.url = images." + Images.URL;
 
     static final String DEFAULT_SORT_HISTORY = History.DATE_LAST_VISITED + " DESC";
 
@@ -67,6 +78,13 @@
     static final int SYNCSTATE = 4000;
     static final int SYNCSTATE_ID = 4001;
 
+    static final int IMAGES = 5000;
+
+    static final int COMBINED = 6000;
+    static final int COMBINED_ID = 6001;
+
+    static final int ACCOUNTS = 7000;
+
     static final long FIXED_ID_CHROME_ROOT = 1;
     static final long FIXED_ID_BOOKMARKS = 2;
     static final long FIXED_ID_BOOKMARKS_BAR = 3;
@@ -74,30 +92,45 @@
 
     static final String DEFAULT_BOOKMARKS_SORT_ORDER = "position ASC, _id ASC";
 
-    
     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 
+    static final HashMap<String, String> ACCOUNTS_PROJECTION_MAP = new HashMap<String, String>();
     static final HashMap<String, String> BOOKMARKS_PROJECTION_MAP = new HashMap<String, String>();
     static final HashMap<String, String> OTHER_BOOKMARKS_PROJECTION_MAP = 
             new HashMap<String, String>();
     static final HashMap<String, String> HISTORY_PROJECTION_MAP = new HashMap<String, String>();
     static final HashMap<String, String> SYNC_STATE_PROJECTION_MAP = new HashMap<String, String>();
+    static final HashMap<String, String> IMAGES_PROJECTION_MAP = new HashMap<String, String>();
+    static final HashMap<String, String> COMBINED_PROJECTION_MAP = new HashMap<String, String>();
 
     static {
         final UriMatcher matcher = URI_MATCHER;
-        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks", BOOKMARKS);
-        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/#", BOOKMARKS_ID);
-        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder", BOOKMARKS_FOLDER);
-        matcher.addURI(BrowserContract.AUTHORITY, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
-        matcher.addURI(BrowserContract.AUTHORITY, "history", HISTORY);
-        matcher.addURI(BrowserContract.AUTHORITY, "history/#", HISTORY_ID);
-        matcher.addURI(BrowserContract.AUTHORITY, "searches", SEARCHES);
-        matcher.addURI(BrowserContract.AUTHORITY, "searches/#", SEARCHES_ID);
-        matcher.addURI(BrowserContract.AUTHORITY, "syncstate", SYNCSTATE);
-        matcher.addURI(BrowserContract.AUTHORITY, "syncstate/#", SYNCSTATE_ID);
+        final String authority = BrowserContract.AUTHORITY;
+        matcher.addURI(authority, "accounts", ACCOUNTS);
+        matcher.addURI(authority, "bookmarks", BOOKMARKS);
+        matcher.addURI(authority, "bookmarks/#", BOOKMARKS_ID);
+        matcher.addURI(authority, "bookmarks/folder", BOOKMARKS_FOLDER);
+        matcher.addURI(authority, "bookmarks/folder/#", BOOKMARKS_FOLDER_ID);
+        matcher.addURI(authority, "history", HISTORY);
+        matcher.addURI(authority, "history/#", HISTORY_ID);
+        matcher.addURI(authority, "searches", SEARCHES);
+        matcher.addURI(authority, "searches/#", SEARCHES_ID);
+        matcher.addURI(authority, "syncstate", SYNCSTATE);
+        matcher.addURI(authority, "syncstate/#", SYNCSTATE_ID);
+        matcher.addURI(authority, "images", IMAGES);
+        matcher.addURI(authority, "combined", COMBINED);
+        matcher.addURI(authority, "combined/#", COMBINED_ID);
+
+        // Projection maps
+        HashMap<String, String> map;
+
+        // Accounts
+        map = ACCOUNTS_PROJECTION_MAP;
+        map.put(Accounts.ACCOUNT_TYPE, Accounts.ACCOUNT_TYPE);
+        map.put(Accounts.ACCOUNT_NAME, Accounts.ACCOUNT_NAME);
 
         // Bookmarks
-        HashMap<String, String> map = BOOKMARKS_PROJECTION_MAP;
+        map = BOOKMARKS_PROJECTION_MAP;
         map.put(Bookmarks._ID, qualifyColumn(TABLE_BOOKMARKS, Bookmarks._ID));
         map.put(Bookmarks.TITLE, Bookmarks.TITLE);
         map.put(Bookmarks.URL, Bookmarks.URL);
@@ -121,6 +154,14 @@
         map.put(Bookmarks.SYNC3, Bookmarks.SYNC3);
         map.put(Bookmarks.SYNC4, Bookmarks.SYNC4);
         map.put(Bookmarks.SYNC5, Bookmarks.SYNC5);
+        map.put(Bookmarks.PARENT_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
+                " FROM " + TABLE_BOOKMARKS + " A WHERE " +
+                "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.PARENT +
+                ") AS " + Bookmarks.PARENT_SOURCE_ID);
+        map.put(Bookmarks.INSERT_AFTER_SOURCE_ID, "(SELECT " + Bookmarks.SOURCE_ID +
+                " FROM " + TABLE_BOOKMARKS + " A WHERE " +
+                "A." + Bookmarks._ID + "=" + TABLE_BOOKMARKS + "." + Bookmarks.INSERT_AFTER +
+                ") AS " + Bookmarks.INSERT_AFTER_SOURCE_ID);
 
         // Other bookmarks
         OTHER_BOOKMARKS_PROJECTION_MAP.putAll(BOOKMARKS_PROJECTION_MAP);
@@ -130,11 +171,11 @@
         // History
         map = HISTORY_PROJECTION_MAP;
         map.put(History._ID, qualifyColumn(TABLE_HISTORY, History._ID));
-        map.put(History.TITLE, Bookmarks.TITLE);
-        map.put(History.URL, Bookmarks.URL);
-        map.put(History.FAVICON, Bookmarks.FAVICON);
-        map.put(History.THUMBNAIL, Bookmarks.THUMBNAIL);
-        map.put(History.TOUCH_ICON, Bookmarks.TOUCH_ICON);
+        map.put(History.TITLE, History.TITLE);
+        map.put(History.URL, History.URL);
+        map.put(History.FAVICON, History.FAVICON);
+        map.put(History.THUMBNAIL, History.THUMBNAIL);
+        map.put(History.TOUCH_ICON, History.TOUCH_ICON);
         map.put(History.DATE_CREATED, History.DATE_CREATED);
         map.put(History.DATE_LAST_VISITED, History.DATE_LAST_VISITED);
         map.put(History.VISITS, History.VISITS);
@@ -146,6 +187,32 @@
         map.put(SyncState.ACCOUNT_NAME, SyncState.ACCOUNT_NAME);
         map.put(SyncState.ACCOUNT_TYPE, SyncState.ACCOUNT_TYPE);
         map.put(SyncState.DATA, SyncState.DATA);
+
+        // Images
+        map = IMAGES_PROJECTION_MAP;
+        map.put(Images.URL, Images.URL);
+        map.put(Images.FAVICON, Images.FAVICON);
+        map.put(Images.THUMBNAIL, Images.THUMBNAIL);
+        map.put(Images.TOUCH_ICON, Images.TOUCH_ICON);
+
+        // Combined history half
+        map = COMBINED_PROJECTION_MAP;
+        map.put(Combined._ID, Combined._ID);
+        map.put(Combined.TITLE, Combined.TITLE);
+        map.put(Combined.URL, Combined.URL);
+        map.put(Combined.DATE_CREATED, Combined.DATE_CREATED);
+        map.put(Combined.DATE_LAST_VISITED, Combined.DATE_LAST_VISITED);
+        map.put(Combined.IS_BOOKMARK, Combined.IS_BOOKMARK);
+        map.put(Combined.VISITS, Combined.VISITS);
+        map.put(Combined.FAVICON, Combined.FAVICON);
+        map.put(Combined.THUMBNAIL, Combined.THUMBNAIL);
+        map.put(Combined.TOUCH_ICON, Combined.TOUCH_ICON);
+        map.put(Combined.USER_ENTERED, Combined.USER_ENTERED);
+    }
+
+    static final String bookmarkOrHistoryColumn(String column) {
+        return "CASE WHEN bookmarks." + column + " IS NOT NULL THEN " +
+                "bookmarks." + column + " ELSE history." + column + " END AS " + column;
     }
 
     static final String qualifyColumn(String table, String column) {
@@ -157,7 +224,7 @@
 
     final class DatabaseHelper extends SQLiteOpenHelper {
         static final String DATABASE_NAME = "browser2.db";
-        static final int DATABASE_VERSION = 16;
+        static final int DATABASE_VERSION = 22;
         public DatabaseHelper(Context context) {
             super(context, DATABASE_NAME, null, DATABASE_VERSION);
         }
@@ -168,11 +235,8 @@
                     Bookmarks._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                     Bookmarks.TITLE + " TEXT," +
                     Bookmarks.URL + " TEXT," +
-                    Bookmarks.FAVICON + " BLOB," +
-                    Bookmarks.THUMBNAIL + " BLOB," +
-                    Bookmarks.TOUCH_ICON + " BLOB," +
                     Bookmarks.IS_FOLDER + " INTEGER NOT NULL DEFAULT 0," +
-                    Bookmarks.PARENT + " INTEGER NOT NULL DEFAULT 0," +
+                    Bookmarks.PARENT + " INTEGER," +
                     Bookmarks.POSITION + " INTEGER NOT NULL," +
                     Bookmarks.INSERT_AFTER + " INTEGER," +
                     Bookmarks.IS_DELETED + " INTEGER NOT NULL DEFAULT 0," +
@@ -198,21 +262,58 @@
                     History._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                     History.TITLE + " TEXT," +
                     History.URL + " TEXT NOT NULL," +
-                    History.FAVICON + " BLOB," +
-                    History.THUMBNAIL + " BLOB," +
-                    History.TOUCH_ICON + " BLOB," +
                     History.DATE_CREATED + " INTEGER," +
                     History.DATE_LAST_VISITED + " INTEGER," +
                     History.VISITS + " INTEGER NOT NULL DEFAULT 0," +
                     History.USER_ENTERED + " INTEGER" +
                     ");");
 
+            db.execSQL("CREATE TABLE " + TABLE_IMAGES + " (" +
+                    Images.URL + " TEXT UNIQUE NOT NULL," +
+                    Images.FAVICON + " BLOB," +
+                    Images.THUMBNAIL + " BLOB," +
+                    Images.TOUCH_ICON + " BLOB" +
+                    ");");
+            db.execSQL("CREATE INDEX imagesUrlIndex ON " + TABLE_IMAGES +
+                    "(" + Images.URL + ")");
+
             db.execSQL("CREATE TABLE " + TABLE_SEARCHES + " (" +
                     Searches._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
                     Searches.SEARCH + " TEXT," +
                     Searches.DATE + " LONG" +
                     ");");
 
+            db.execSQL("CREATE VIEW " + VIEW_COMBINED + " AS " +
+                "SELECT " +
+                    bookmarkOrHistoryColumn(Combined._ID) + ", " +
+                    bookmarkOrHistoryColumn(Combined.TITLE) + ", " +
+                    qualifyColumn(TABLE_HISTORY, Combined.URL) + ", " +
+                    qualifyColumn(TABLE_HISTORY, Combined.DATE_CREATED) + ", " +
+                    Combined.DATE_LAST_VISITED + ", " +
+                    "CASE WHEN bookmarks._id IS NOT NULL THEN 1 ELSE 0 END AS " + Combined.IS_BOOKMARK + ", " +
+                    Combined.VISITS + ", " +
+                    Combined.FAVICON + ", " +
+                    Combined.THUMBNAIL + ", " +
+                    Combined.TOUCH_ICON + ", " +
+                    "NULL AS " + Combined.USER_ENTERED + " "+
+                "FROM history LEFT OUTER JOIN bookmarks ON history.url = bookmarks.url LEFT OUTER JOIN images ON history.url = images.url_key " +
+
+                "UNION ALL " +
+
+                "SELECT " +
+                    Combined._ID + ", " +
+                    Combined.TITLE + ", " +
+                    Combined.URL + ", " +
+                    Combined.DATE_CREATED + ", " +
+                    "NULL AS " + Combined.DATE_LAST_VISITED + ", "+
+                    "1 AS " + Combined.IS_BOOKMARK + ", " +
+                    "0 AS " + Combined.VISITS + ", "+
+                    Combined.FAVICON + ", " +
+                    Combined.THUMBNAIL + ", " +
+                    Combined.TOUCH_ICON + ", " +
+                    "NULL AS " + Combined.USER_ENTERED + " "+
+                "FROM bookmarks LEFT OUTER JOIN images ON bookmarks.url = images.url_key WHERE url NOT IN (SELECT url FROM history)");
+
             mSyncHelper.createDatabase(db);
         }
 
@@ -222,6 +323,8 @@
             db.execSQL("DROP TABLE IF EXISTS " + TABLE_BOOKMARKS);
             db.execSQL("DROP TABLE IF EXISTS " + TABLE_HISTORY);
             db.execSQL("DROP TABLE IF EXISTS " + TABLE_SEARCHES);
+            db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
+            db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
             onCreate(db);
         }
 
@@ -302,6 +405,8 @@
                     R.array.bookmarks);
             int size = bookmarks.length;
             try {
+                String parent = Long.toString(parentId);
+                String now = Long.toString(System.currentTimeMillis());
                 for (int i = 0; i < size; i = i + 2) {
                     CharSequence bookmarkDestination = replaceSystemPropertyInString(getContext(),
                             bookmarks[i + 1]);
@@ -310,13 +415,15 @@
                             Bookmarks.URL + ", " +
                             Bookmarks.IS_FOLDER + "," +
                             Bookmarks.PARENT + "," +
-                            Bookmarks.POSITION +
+                            Bookmarks.POSITION + "," +
+                            Bookmarks.DATE_CREATED +
                         ") VALUES (" +
                             "'" + bookmarks[i] + "', " +
                             "'" + bookmarkDestination + "', " +
                             "0," +
-                            Long.toString(parentId) + "," +
-                            Integer.toString(i) +
+                            parent + "," +
+                            Integer.toString(i) + "," +
+                            now +
                             ");");
                 }
             } catch (ArrayIndexOutOfBoundsException e) {
@@ -399,7 +506,7 @@
     public void notifyChange(boolean callerIsSyncAdapter) {
         ContentResolver resolver = getContext().getContentResolver();
         resolver.notifyChange(BrowserContract.AUTHORITY_URI, null, !callerIsSyncAdapter);
-        resolver.notifyChange(LEGACY_BROWSER_AUTHORITY_URI, null, !callerIsSyncAdapter);
+        resolver.notifyChange(LEGACY_AUTHORITY_URI, null, !callerIsSyncAdapter);
     }
 
     @Override
@@ -430,7 +537,15 @@
         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
         final int match = URI_MATCHER.match(uri);
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+        String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
         switch (match) {
+            case ACCOUNTS: {
+                qb.setTables(TABLE_BOOKMARKS);
+                qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
+                qb.setDistinct(true);
+                break;
+            }
+
             case BOOKMARKS_FOLDER_ID:
             case BOOKMARKS_ID:
             case BOOKMARKS: {
@@ -442,24 +557,35 @@
 
                 if (match == BOOKMARKS_ID) {
                     // Tack on the ID of the specific bookmark requested
-                    selection = DatabaseUtils.concatenateWhere(
-                            TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?", selection);
+                    selection = DatabaseUtils.concatenateWhere(selection,
+                            TABLE_BOOKMARKS + "." + Bookmarks._ID + "=?");
                     selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                             new String[] { Long.toString(ContentUris.parseId(uri)) });
                 } else if (match == BOOKMARKS_FOLDER_ID) {
                     // Tack on the ID of the specific folder requested
-                    selection = DatabaseUtils.concatenateWhere(
-                            TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?", selection);
+                    selection = DatabaseUtils.concatenateWhere(selection,
+                            TABLE_BOOKMARKS + "." + Bookmarks.PARENT + "=?");
                     selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                             new String[] { Long.toString(ContentUris.parseId(uri)) });
                 }
 
+                // Look for account info
+                String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+                String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+                if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+                    selection = DatabaseUtils.concatenateWhere(selection,
+                            Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? ");
+                    selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+                            new String[] { accountType, accountName });
+                }
+
+                // Set a default sort order if one isn't specified
                 if (TextUtils.isEmpty(sortOrder)) {
                     sortOrder = DEFAULT_BOOKMARKS_SORT_ORDER;
                 }
 
                 qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
-                qb.setTables(TABLE_BOOKMARKS);
+                qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
                 break;
             }
 
@@ -470,29 +596,63 @@
                             "selections aren't supported on this URI");
                 }
 
-                qb.setTables(TABLE_BOOKMARKS);
-                qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
-                String bookmarksBarQuery = qb.buildQuery(projection,
-                        Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0",
-                        null, null, null, null, null);
+                // Look for an account
+                boolean useAccount = false;
+                String accountType = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_TYPE);
+                String accountName = uri.getQueryParameter(Bookmarks.PARAM_ACCOUNT_NAME);
+                if (!TextUtils.isEmpty(accountType) && !TextUtils.isEmpty(accountName)) {
+                    useAccount = true;
+                }
 
-                qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
-                String otherBookmarksQuery = qb.buildQuery(projection,
-                        Bookmarks._ID + "=?",
-                        null, null, null, null, null);
+                qb.setTables(TABLE_BOOKMARKS_JOIN_IMAGES);
+                String bookmarksBarQuery;
+                String otherBookmarksQuery;
+                String[] args;
+                if (!useAccount) {
+                    qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+                    bookmarksBarQuery = qb.buildQuery(projection,
+                            Bookmarks.PARENT + "=? AND " + Bookmarks.IS_DELETED + "=0",
+                            null, null, null, null, null);
+
+                    qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
+                    otherBookmarksQuery = qb.buildQuery(projection,
+                            Bookmarks._ID + "=?",
+                            null, null, null, null, null);
+
+                    args = new String[] { Long.toString(FIXED_ID_BOOKMARKS_BAR),
+                            Long.toString(FIXED_ID_OTHER_BOOKMARKS) };
+                } else {
+                    qb.setProjectionMap(BOOKMARKS_PROJECTION_MAP);
+                    bookmarksBarQuery = qb.buildQuery(projection,
+                            Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=? " +
+                                    "AND parent = " +
+                                        "(SELECT _id FROM " + TABLE_BOOKMARKS + " WHERE " +
+                                        ChromeSyncColumns.SERVER_UNIQUE + "=" +
+                                        "'" + ChromeSyncColumns.FOLDER_NAME_BOOKMARKS_BAR + "' " +
+                                        "AND account_type = ? AND account_name = ?) " +
+                                    "AND " + Bookmarks.IS_DELETED + "=0",
+                            null, null, null, null, null);
+
+                    qb.setProjectionMap(OTHER_BOOKMARKS_PROJECTION_MAP);
+                    otherBookmarksQuery = qb.buildQuery(projection,
+                            Bookmarks.ACCOUNT_TYPE + "=? AND " + Bookmarks.ACCOUNT_NAME + "=?" +
+                                    " AND " + ChromeSyncColumns.SERVER_UNIQUE + "=?",
+                            null, null, null, null, null);
+
+                    args = new String[] {
+                            accountType, accountName, accountType, accountName,
+                            accountType, accountName, ChromeSyncColumns.FOLDER_NAME_OTHER_BOOKMARKS,
+                            };
+                }
 
                 String query = qb.buildUnionQuery(
                         new String[] { bookmarksBarQuery, otherBookmarksQuery },
-                        DEFAULT_BOOKMARKS_SORT_ORDER, null);
-
-                return db.rawQuery(query, new String[] {
-                        Long.toString(FIXED_ID_BOOKMARKS_BAR),
-                        Long.toString(FIXED_ID_OTHER_BOOKMARKS)});
+                        DEFAULT_BOOKMARKS_SORT_ORDER, limit);
+                return db.rawQuery(query, args);
             }
 
             case HISTORY_ID: {
-                selection = DatabaseUtils.concatenateWhere(
-                        TABLE_HISTORY + "._id=?", selection);
+                selection = DatabaseUtils.concatenateWhere(selection, TABLE_HISTORY + "._id=?");
                 selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
                         new String[] { Long.toString(ContentUris.parseId(uri)) });
                 // fall through
@@ -502,7 +662,7 @@
                     sortOrder = DEFAULT_SORT_HISTORY;
                 }
                 qb.setProjectionMap(HISTORY_PROJECTION_MAP);
-                qb.setTables(TABLE_HISTORY);
+                qb.setTables(TABLE_HISTORY_JOIN_IMAGES);
                 break;
             }
 
@@ -518,12 +678,31 @@
                 return mSyncHelper.query(db, projection, selectionWithId, selectionArgs, sortOrder);
             }
 
+            case IMAGES: {
+                qb.setTables(TABLE_IMAGES);
+                qb.setProjectionMap(IMAGES_PROJECTION_MAP);
+                break;
+            }
+
+            case COMBINED_ID: {
+                selection = DatabaseUtils.concatenateWhere(selection, VIEW_COMBINED + "._id=?");
+                selectionArgs = DatabaseUtils.appendSelectionArgs(selectionArgs,
+                        new String[] { Long.toString(ContentUris.parseId(uri)) });
+                // fall through
+            }
+            case COMBINED: {
+                qb.setTables(VIEW_COMBINED);
+                qb.setProjectionMap(COMBINED_PROJECTION_MAP);
+                break;
+            }
+
             default: {
                 throw new UnsupportedOperationException("Unknown URL " + uri.toString());
             }
         }
 
-        Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);
+        Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder,
+                limit);
         cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.AUTHORITY_URI);
         return cursor;
     }
@@ -603,11 +782,11 @@
                     values.put(Bookmarks.DATE_CREATED, now);
                     values.put(Bookmarks.DATE_MODIFIED, now);
                     values.put(Bookmarks.DIRTY, 1);
-                }
 
-                // If no parent is set default to the "Bookmarks Bar" folder
-                if (!values.containsKey(Bookmarks.PARENT)) {
-                    values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS_BAR);
+                    // If no parent is set default to the "Bookmarks Bar" folder
+                    if (!values.containsKey(Bookmarks.PARENT)) {
+                        values.put(Bookmarks.PARENT, FIXED_ID_BOOKMARKS_BAR);
+                    }
                 }
 
                 // If no position is requested put the bookmark at the beginning of the list
@@ -615,11 +794,31 @@
                     values.put(Bookmarks.POSITION, Long.toString(Long.MIN_VALUE));
                 }
 
+                // Extract out the image values so they can be inserted into the images table
+                String url = values.getAsString(Bookmarks.URL);
+                ContentValues imageValues = extractImageValues(values, url);
+                boolean isFolder = values.getAsBoolean(Bookmarks.IS_FOLDER);
+                if (!isFolder && imageValues != null && !TextUtils.isEmpty(url)) {
+                    db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
+                }
+
                 id = db.insertOrThrow(TABLE_BOOKMARKS, Bookmarks.DIRTY, values);
                 break;
             }
 
             case HISTORY: {
+                // If no created time is specified set it to now
+                if (!values.containsKey(History.DATE_CREATED)) {
+                    values.put(History.DATE_CREATED, System.currentTimeMillis());
+                }
+
+                // Extract out the image values so they can be inserted into the images table
+                ContentValues imageValues = extractImageValues(values,
+                        values.getAsString(History.URL));
+                if (imageValues != null) {
+                    db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, imageValues);
+                }
+
                 id = db.insertOrThrow(TABLE_HISTORY, History.VISITS, values);
                 break;
             }
@@ -696,7 +895,7 @@
                 // fall through
             }
             case HISTORY: {
-                return db.update(TABLE_HISTORY, values, selection, selectionArgs);
+                return updateHistoryInTransaction(values, selection, selectionArgs);
             }
 
             case SYNCSTATE: {
@@ -712,6 +911,20 @@
                 return mSyncHelper.update(mDb, values,
                         selectionWithId, selectionArgs);
             }
+
+            case IMAGES: {
+                String url = values.getAsString(Images.URL);
+                if (TextUtils.isEmpty(url)) {
+                    throw new IllegalArgumentException("Images.URL is required");
+                }
+                int count = db.update(TABLE_IMAGES, values, Images.URL + "=?",
+                        new String[] { url });
+                if (count == 0) {
+                    db.insertOrThrow(TABLE_IMAGES, Images.FAVICON, values);
+                    count = 1;
+                }
+                return count;
+            }
         }
         throw new UnsupportedOperationException("Unknown update URI " + uri);
     }
@@ -719,21 +932,28 @@
     /**
      * Does a query to find the matching bookmarks and updates each one with the provided values.
      */
-    private int updateBookmarksInTransaction(ContentValues values, String selection,
+    int updateBookmarksInTransaction(ContentValues values, String selection,
             String[] selectionArgs, boolean callerIsSyncAdapter) {
         int count = 0;
         final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor cursor = query(Bookmarks.CONTENT_URI,
-                new String[] { Bookmarks._ID, Bookmarks.VERSION },
+                new String[] { Bookmarks._ID, Bookmarks.VERSION, Bookmarks.URL },
                 selection, selectionArgs, null);
         try {
             String[] args = new String[1];
             // Mark the bookmark dirty if the caller isn't a sync adapter
             if (!callerIsSyncAdapter) {
-                values = new ContentValues(values);
                 values.put(Bookmarks.DATE_MODIFIED, System.currentTimeMillis());
                 values.put(Bookmarks.DIRTY, 1);
             }
+
+            boolean updatingUrl = values.containsKey(Bookmarks.URL);
+            String url = null;
+            if (updatingUrl) {
+                url = values.getAsString(Bookmarks.URL);
+            }
+            ContentValues imageValues = extractImageValues(values, url);
+
             while (cursor.moveToNext()) {
                 args[0] = cursor.getString(0);
                 if (!callerIsSyncAdapter) {
@@ -741,6 +961,21 @@
                     values.put(Bookmarks.VERSION, cursor.getLong(1) + 1);
                 }
                 count += db.update(TABLE_BOOKMARKS, values, "_id=?", args);
+
+                // Update the images over in their table
+                if (imageValues != null) {
+                    if (!updatingUrl) {
+                        url = cursor.getString(2);
+                        imageValues.put(Images.URL, url);
+                    }
+
+                    if (!TextUtils.isEmpty(url)) {
+                        args[0] = url;
+                        if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
+                            db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
+                        }
+                    }
+                }
             }
         } finally {
             if (cursor != null) cursor.close();
@@ -748,7 +983,48 @@
         return count;
     }
 
-    private String appendAccountToSelection(Uri uri, String selection) {
+    /**
+     * Does a query to find the matching bookmarks and updates each one with the provided values.
+     */
+    int updateHistoryInTransaction(ContentValues values, String selection, String[] selectionArgs) {
+        int count = 0;
+        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        Cursor cursor = query(History.CONTENT_URI,
+                new String[] { History._ID, History.URL },
+                selection, selectionArgs, null);
+        try {
+            String[] args = new String[1];
+
+            boolean updatingUrl = values.containsKey(History.URL);
+            String url = null;
+            if (updatingUrl) {
+                url = values.getAsString(History.URL);
+            }
+            ContentValues imageValues = extractImageValues(values, url);
+
+            while (cursor.moveToNext()) {
+                args[0] = cursor.getString(0);
+                count += db.update(TABLE_HISTORY, values, "_id=?", args);
+
+                // Update the images over in their table
+                if (imageValues != null) {
+                    if (!updatingUrl) {
+                        url = cursor.getString(1);
+                        imageValues.put(Images.URL, url);
+                    }
+                    args[0] = url;
+                    if (db.update(TABLE_IMAGES, imageValues, Images.URL + "=?", args) == 0) {
+                        db.insert(TABLE_IMAGES, Images.FAVICON, imageValues);
+                    }
+                }
+            }
+        } finally {
+            if (cursor != null) cursor.close();
+        }
+        return count;
+    }
+
+    String appendAccountToSelection(Uri uri, String selection) {
         final String accountName = uri.getQueryParameter(RawContacts.ACCOUNT_NAME);
         final String accountType = uri.getQueryParameter(RawContacts.ACCOUNT_TYPE);
 
@@ -777,4 +1053,37 @@
             return selection;
         }
     }
+
+    ContentValues extractImageValues(ContentValues values, String url) {
+        ContentValues imageValues = null;
+        // favicon
+        if (values.containsKey(Bookmarks.FAVICON)) {
+            imageValues = new ContentValues();
+            imageValues.put(Images.FAVICON, values.getAsByteArray(Bookmarks.FAVICON));
+            values.remove(Bookmarks.FAVICON);
+        }
+
+        // thumbnail
+        if (values.containsKey(Bookmarks.THUMBNAIL)) {
+            if (imageValues == null) {
+                imageValues = new ContentValues();
+            }
+            imageValues.put(Images.THUMBNAIL, values.getAsByteArray(Bookmarks.THUMBNAIL));
+            values.remove(Bookmarks.THUMBNAIL);
+        }
+
+        // touch icon
+        if (values.containsKey(Bookmarks.TOUCH_ICON)) {
+            if (imageValues == null) {
+                imageValues = new ContentValues();
+            }
+            imageValues.put(Images.TOUCH_ICON, values.getAsByteArray(Bookmarks.TOUCH_ICON));
+            values.remove(Bookmarks.TOUCH_ICON);
+        }
+
+        if (imageValues != null) {
+            imageValues.put(Images.URL,  url);
+        }
+        return imageValues;
+    }
 }