Bug 905685 - Implement incremental favicon loading in about:home. r=sriram, a=lsblakk
authorLucas Rocha <lucasr@mozilla.com>
Wed, 18 Sep 2013 12:58:25 -0400
changeset 160423 59d9eff1d3ed3a8f4168f277e6a46f2a261773c9
parent 160422 fa50f923db953d4ac52a79c1ce08c071feac2df3
child 160424 3fde1bdc6d01658db541466d83ca3888d4fcd682
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssriram, lsblakk
bugs905685
milestone26.0a2
Bug 905685 - Implement incremental favicon loading in about:home. r=sriram, a=lsblakk
mobile/android/base/Makefile.in
mobile/android/base/home/BookmarksPage.java
mobile/android/base/home/BrowserSearch.java
mobile/android/base/home/FaviconsLoader.java
mobile/android/base/home/HomeCursorLoaderCallbacks.java
mobile/android/base/home/LastTabsPage.java
mobile/android/base/home/MostRecentPage.java
mobile/android/base/home/MostVisitedPage.java
mobile/android/base/home/PinBookmarkDialog.java
mobile/android/base/home/ReadingListPage.java
mobile/android/base/home/TwoLinePageRow.java
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -215,24 +215,22 @@ FENNEC_JAVA_FILES = \
   gfx/VirtualLayer.java \
   home/BookmarksListAdapter.java \
   home/BookmarksListView.java \
   home/BookmarksPage.java \
   home/BookmarkFolderView.java \
   home/BookmarkThumbnailView.java \
   home/BrowserSearch.java \
   home/HistoryPage.java \
-  home/HomeCursorLoaderCallbacks.java \
   home/HomeFragment.java \
   home/HomeListView.java \
   home/HomePager.java \
   home/HomePagerTabStrip.java \
   home/HomeBanner.java \
   home/FadedTextView.java \
-  home/FaviconsLoader.java \
   home/LastTabsPage.java \
   home/MostRecentPage.java \
   home/MostVisitedPage.java \
   home/MultiTypeCursorAdapter.java \
   home/PinBookmarkDialog.java \
   home/ReadingListPage.java \
   home/SearchEngine.java \
   home/SearchEngineRow.java \
--- a/mobile/android/base/home/BookmarksPage.java
+++ b/mobile/android/base/home/BookmarksPage.java
@@ -174,17 +174,17 @@ public class BookmarksPage extends HomeF
         });
         mList.setAdapter(mListAdapter);
 
         // Invalidate the cached value that keeps track of whether or
         // not desktop bookmarks (or reading list items) exist.
         BrowserDB.invalidateCachedState();
 
         // Create callbacks before the initial loader is started.
-        mLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
+        mLoaderCallbacks = new CursorLoaderCallbacks();
         mThumbnailsLoaderCallbacks = new ThumbnailsLoaderCallbacks();
         loadIfVisible();
     }
 
     @Override
     public void onDestroyView() {
         mList = null;
         mListAdapter = null;
@@ -448,49 +448,42 @@ public class BookmarksPage extends HomeF
             final int max = getContext().getResources().getInteger(R.integer.number_of_top_sites);
             return BrowserDB.getTopBookmarks(getContext().getContentResolver(), max);
         }
     }
 
     /**
      * Loader callbacks for the LoaderManager of this fragment.
      */
-    private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
-        public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
-            super(context, loaderManager);
-        }
-
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
             switch(id) {
                 case LOADER_ID_BOOKMARKS_LIST: {
                     if (args == null) {
                         return new BookmarksLoader(getActivity());
                     } else {
                         return new BookmarksLoader(getActivity(), args.getInt(BOOKMARKS_FOLDER_KEY));
                     }
                 }
 
                 case LOADER_ID_TOP_BOOKMARKS: {
                     return new TopBookmarksLoader(getActivity());
                 }
+            }
 
-                default: {
-                    return super.onCreateLoader(id, args);
-                }
-            }
+            return null;
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
             final int loaderId = loader.getId();
             switch(loaderId) {
                 case LOADER_ID_BOOKMARKS_LIST: {
                     mListAdapter.swapCursor(c);
-                    loadFavicons(c);
                     mList.setHeaderDividersEnabled(c != null && c.getCount() > 0);
                     break;
                 }
 
                 case LOADER_ID_TOP_BOOKMARKS: {
                     mTopBookmarksAdapter.swapCursor(c);
 
                     // Load the thumbnails.
@@ -504,21 +497,16 @@ public class BookmarksPage extends HomeF
                         if (urls.size() > 0) {
                             Bundle bundle = new Bundle();
                             bundle.putStringArrayList(THUMBNAILS_URLS_KEY, urls);
                             getLoaderManager().restartLoader(LOADER_ID_THUMBNAILS, bundle, mThumbnailsLoaderCallbacks);
                         }
                     }
                     break;
                 }
-
-                default: {
-                    super.onLoadFinished(loader, c);
-                    break;
-                }
             }
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
             final int loaderId = loader.getId();
             switch(loaderId) {
                 case LOADER_ID_BOOKMARKS_LIST: {
@@ -529,28 +517,18 @@ public class BookmarksPage extends HomeF
                 }
 
                 case LOADER_ID_TOP_BOOKMARKS: {
                     if (mTopBookmarks != null) {
                         mTopBookmarksAdapter.swapCursor(null);
                         break;
                     }
                 }
-
-                default: {
-                    super.onLoaderReset(loader);
-                    break;
-                }
             }
         }
-
-        @Override
-        public void onFaviconsLoaded() {
-            mListAdapter.notifyDataSetChanged();
-        }
     }
 
     /**
      * An AsyncTaskLoader to load the thumbnails from a cursor.
      */
     private static class ThumbnailsLoader extends AsyncTaskLoader<Map<String, Thumbnail>> {
         private Map<String, Thumbnail> mThumbnails;
         private ArrayList<String> mUrls;
--- a/mobile/android/base/home/BrowserSearch.java
+++ b/mobile/android/base/home/BrowserSearch.java
@@ -99,17 +99,17 @@ public class BrowserSearch extends HomeF
     private volatile SuggestClient mSuggestClient;
 
     // List of search engines from gecko
     private ArrayList<SearchEngine> mSearchEngines;
 
     // Whether search suggestions are enabled or not
     private boolean mSuggestionsEnabled;
 
-    // Callbacks used for the search and favicon cursor loaders
+    // Callbacks used for the search loader
     private CursorLoaderCallbacks mCursorLoaderCallbacks;
 
     // Callbacks used for the search suggestion loader
     private SuggestionLoaderCallbacks mSuggestionLoaderCallbacks;
 
     // Inflater used by the adapter
     private LayoutInflater mInflater;
 
@@ -281,27 +281,25 @@ public class BrowserSearch extends HomeF
 
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("SearchEngines:Get", null));
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        final Activity activity = getActivity();
-
         // Intialize the search adapter
-        mAdapter = new SearchAdapter(activity);
+        mAdapter = new SearchAdapter(getActivity());
         mList.setAdapter(mAdapter);
 
         // Only create an instance when we need it
         mSuggestionLoaderCallbacks = null;
 
         // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
+        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     @Override
     public void handleMessage(String event, final JSONObject message) {
         if (event.equals("SearchEngines:Data")) {
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
@@ -767,59 +765,36 @@ public class BrowserSearch extends HomeF
                 return -1;
             }
 
             // Return search engine index
             return position - resultCount;
         }
     }
 
-    private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
-        public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
-            super(context, loaderManager);
-        }
-
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            if (id == LOADER_ID_SEARCH) {
-                return SearchLoader.createInstance(getActivity(), args);
-            } else {
-                return super.onCreateLoader(id, args);
-            }
+            return SearchLoader.createInstance(getActivity(), args);
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            if (loader.getId() == LOADER_ID_SEARCH) {
-                mAdapter.swapCursor(c);
+            mAdapter.swapCursor(c);
 
-                // We should handle autocompletion based on the search term
-                // associated with the currently loader that has just provided
-                // the results.
-                SearchCursorLoader searchLoader = (SearchCursorLoader) loader;
-                handleAutocomplete(searchLoader.getSearchTerm(), c);
-
-                loadFavicons(c);
-            } else {
-                super.onLoadFinished(loader, c);
-            }
+            // We should handle autocompletion based on the search term
+            // associated with the currently loader that has just provided
+            // the results.
+            SearchCursorLoader searchLoader = (SearchCursorLoader) loader;
+            handleAutocomplete(searchLoader.getSearchTerm(), c);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
-            if (loader.getId() == LOADER_ID_SEARCH) {
-                mAdapter.swapCursor(null);
-            } else {
-                super.onLoaderReset(loader);
-            }
-        }
-
-        @Override
-        public void onFaviconsLoaded() {
-            mAdapter.notifyDataSetChanged();
+            mAdapter.swapCursor(null);
         }
     }
 
     private class SuggestionLoaderCallbacks implements LoaderCallbacks<ArrayList<String>> {
         @Override
         public Loader<ArrayList<String>> onCreateLoader(int id, Bundle args) {
             // mSuggestClient is set to null in onDestroyView(), so using it
             // safely here relies on the fact that onCreateLoader() is called
deleted file mode 100644
--- a/mobile/android/base/home/FaviconsLoader.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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.home;
-
-import org.mozilla.gecko.favicons.Favicons;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserDB.URLColumns;
-import org.mozilla.gecko.gfx.BitmapUtils;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-
-import java.util.ArrayList;
-
-/**
- * Encapsulates the implementation of the favicons cursorloader.
- */
-class FaviconsLoader {
-    // Argument containing list of urls for the favicons loader
-    private static final String FAVICONS_LOADER_URLS_ARG = "urls";
-
-    private FaviconsLoader() {
-    }
-
-    private static ArrayList<String> getUrlsWithoutFavicon(Cursor c) {
-        ArrayList<String> urls = new ArrayList<String>();
-
-        if (c == null || !c.moveToFirst()) {
-            return urls;
-        }
-
-        do {
-            final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
-
-            // We only want to load favicons from DB if they are not in the
-            // memory cache yet. The url is null for bookmark folders.
-            if (url == null || Favicons.getFaviconFromMemCache(url) != null) {
-                continue;
-            }
-
-            urls.add(url);
-        } while (c.moveToNext());
-
-        return urls;
-    }
-
-    private static void storeFaviconsInMemCache(Cursor c) {
-        if (c == null || !c.moveToFirst()) {
-            return;
-        }
-
-        do {
-            final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
-            final byte[] b = c.getBlob(c.getColumnIndexOrThrow(URLColumns.FAVICON));
-
-            if (b == null) {
-                continue;
-            }
-
-            Bitmap favicon = BitmapUtils.decodeByteArray(b);
-            if (favicon == null) {
-                continue;
-            }
-
-            favicon = Favicons.scaleImage(favicon);
-            Favicons.putFaviconInMemCache(url, favicon);
-        } while (c.moveToNext());
-    }
-
-    public static void restartFromCursor(LoaderManager manager, int loaderId,
-            LoaderCallbacks<Cursor> callbacks, Cursor c) {
-        // If there urls without in-memory favicons, trigger a new loader
-        // to load the images from disk to memory.
-        ArrayList<String> urls = getUrlsWithoutFavicon(c);
-        if (urls.size() > 0) {
-            Bundle args = new Bundle();
-            args.putStringArrayList(FAVICONS_LOADER_URLS_ARG, urls);
-
-            manager.restartLoader(loaderId, args, callbacks);
-        }
-    }
-
-    public static Loader<Cursor> createInstance(Context context, Bundle args) {
-        final ArrayList<String> urls = args.getStringArrayList(FAVICONS_LOADER_URLS_ARG);
-        return new FaviconsCursorLoader(context, urls);
-    }
-
-    private static class FaviconsCursorLoader extends SimpleCursorLoader {
-        private final ArrayList<String> mUrls;
-
-        public FaviconsCursorLoader(Context context, ArrayList<String> urls) {
-            super(context);
-            mUrls = urls;
-        }
-
-        @Override
-        public Cursor loadCursor() {
-            final ContentResolver cr = getContext().getContentResolver();
-
-            Cursor c = BrowserDB.getFaviconsForUrls(cr, mUrls);
-            storeFaviconsInMemCache(c);
-
-            return c;
-        }
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/home/HomeCursorLoaderCallbacks.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-
-/**
- * Cursor loader callbacks that takes care loading favicons into memory.
- */
-abstract class HomeCursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
-
-    // Cursor loader ID for favicons query
-    private static final int LOADER_ID_FAVICONS = 100;
-
-    private final Context mContext;
-    private final LoaderManager mLoaderManager;
-
-    public HomeCursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
-        mContext = context;
-        mLoaderManager = loaderManager;
-    }
-
-    public void loadFavicons(Cursor cursor) {
-        FaviconsLoader.restartFromCursor(mLoaderManager, LOADER_ID_FAVICONS, this, cursor);
-    }
-
-    @Override
-    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-        if (id == LOADER_ID_FAVICONS) {
-            return FaviconsLoader.createInstance(mContext, args);
-        }
-
-        return null;
-    }
-
-    @Override
-    public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-        if (loader.getId() == LOADER_ID_FAVICONS) {
-            onFaviconsLoaded();
-        }
-    }
-
-    @Override
-    public void onLoaderReset(Loader<Cursor> loader) {
-        // Do nothing by default.
-    }
-
-    // Callback for favicons loaded in memory.
-    public abstract void onFaviconsLoaded();
-}
--- a/mobile/android/base/home/LastTabsPage.java
+++ b/mobile/android/base/home/LastTabsPage.java
@@ -137,24 +137,22 @@ public class LastTabsPage extends HomeFr
         mEmptyView = null;
         mRestoreButton = null;
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        final Activity activity = getActivity();
-
         // Intialize adapter
-        mAdapter = new LastTabsAdapter(activity);
+        mAdapter = new LastTabsAdapter(getActivity());
         mList.setAdapter(mAdapter);
 
         // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
+        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     private void updateUiFromCursor(Cursor c) {
         if (c != null && c.getCount() > 0) {
             if (mTitle != null) {
                 mTitle.setVisibility(View.VISIBLE);
             }
@@ -257,48 +255,26 @@ public class LastTabsPage extends HomeFr
         }
 
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             return LayoutInflater.from(context).inflate(R.layout.home_item_row, parent, false);
         }
     }
 
-    private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
-        public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
-            super(context, loaderManager);
-        }
-
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            if (id == LOADER_ID_LAST_TABS) {
-                return new LastTabsCursorLoader(getActivity());
-            } else {
-                return super.onCreateLoader(id, args);
-            }
+            return new LastTabsCursorLoader(getActivity());
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            if (loader.getId() == LOADER_ID_LAST_TABS) {
-                mAdapter.swapCursor(c);
-                updateUiFromCursor(c);
-                loadFavicons(c);
-            } else {
-                super.onLoadFinished(loader, c);
-            }
+            mAdapter.swapCursor(c);
+            updateUiFromCursor(c);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
-            if (loader.getId() == LOADER_ID_LAST_TABS) {
-                mAdapter.swapCursor(null);
-            } else {
-                super.onLoaderReset(loader);
-            }
-        }
-
-        @Override
-        public void onFaviconsLoaded() {
-            mAdapter.notifyDataSetChanged();
+            mAdapter.swapCursor(null);
         }
     }
 }
--- a/mobile/android/base/home/MostRecentPage.java
+++ b/mobile/android/base/home/MostRecentPage.java
@@ -12,16 +12,17 @@ import org.mozilla.gecko.home.HomePager.
 import org.mozilla.gecko.home.TwoLinePageRow;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.support.v4.app.LoaderManager;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewStub;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.ImageView;
@@ -122,24 +123,22 @@ public class MostRecentPage extends Home
         mTitle = null;
         mEmptyView = null;
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        final Activity activity = getActivity();
-
         // Intialize adapter
-        mAdapter = new MostRecentAdapter(activity);
+        mAdapter = new MostRecentAdapter(getActivity());
         mList.setAdapter(mAdapter);
 
         // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
+        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     @Override
     protected void load() {
         getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
     }
 
@@ -355,48 +354,26 @@ public class MostRecentPage extends Home
                 // Reached the last section, no need to continue
                 if (section == MostRecentSection.OLDER) {
                     break;
                 }
             } while (c.moveToNext());
         }
     }
 
-    private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
-        public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
-            super(context, loaderManager);
-        }
-
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            if (id == LOADER_ID_HISTORY) {
-                return new MostRecentCursorLoader(getActivity());
-            } else {
-                return super.onCreateLoader(id, args);
-            }
+            return new MostRecentCursorLoader(getActivity());
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            if (loader.getId() == LOADER_ID_HISTORY) {
-                mAdapter.swapCursor(c);
-                updateUiFromCursor(c);
-                loadFavicons(c);
-            } else {
-                super.onLoadFinished(loader, c);
-            }
+            mAdapter.swapCursor(c);
+            updateUiFromCursor(c);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
-            if (loader.getId() == LOADER_ID_HISTORY) {
-                mAdapter.swapCursor(null);
-            } else {
-                super.onLoaderReset(loader);
-            }
-        }
-
-        @Override
-        public void onFaviconsLoaded() {
-            mAdapter.notifyDataSetChanged();
+            mAdapter.swapCursor(null);
         }
    }
 }
--- a/mobile/android/base/home/MostVisitedPage.java
+++ b/mobile/android/base/home/MostVisitedPage.java
@@ -11,16 +11,17 @@ import org.mozilla.gecko.db.BrowserDB.UR
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.support.v4.app.LoaderManager;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.support.v4.widget.CursorAdapter;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.AdapterView;
 import android.widget.ImageView;
@@ -124,24 +125,22 @@ public class MostVisitedPage extends Hom
         mTitle = null;
         mEmptyView = null;
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        final Activity activity = getActivity();
-
         // Intialize the search adapter
-        mAdapter = new VisitedAdapter(activity, null);
+        mAdapter = new VisitedAdapter(getActivity(), null);
         mList.setAdapter(mAdapter);
 
         // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks(activity, getLoaderManager());
+        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     @Override
     protected void load() {
         getLoaderManager().initLoader(LOADER_ID_FRECENCY, null, mCursorLoaderCallbacks);
     }
 
@@ -201,48 +200,26 @@ public class MostVisitedPage extends Hom
         }
 
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             return LayoutInflater.from(parent.getContext()).inflate(R.layout.home_item_row, parent, false);
         }
     }
 
-    private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
-        public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
-            super(context, loaderManager);
-        }
-
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            if (id == LOADER_ID_FRECENCY) {
-                return new FrecencyCursorLoader(getActivity());
-            } else {
-                return super.onCreateLoader(id, args);
-            }
+            return new FrecencyCursorLoader(getActivity());
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            if (loader.getId() == LOADER_ID_FRECENCY) {
-                mAdapter.swapCursor(c);
-                updateUiFromCursor(c);
-                loadFavicons(c);
-            } else {
-                super.onLoadFinished(loader, c);
-            }
+            mAdapter.swapCursor(c);
+            updateUiFromCursor(c);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
-            if (loader.getId() == LOADER_ID_FRECENCY) {
-                mAdapter.swapCursor(null);
-            } else {
-                super.onLoaderReset(loader);
-            }
-        }
-
-        @Override
-        public void onFaviconsLoaded() {
-            mAdapter.notifyDataSetChanged();
+            mAdapter.swapCursor(null);
         }
     }
 }
--- a/mobile/android/base/home/PinBookmarkDialog.java
+++ b/mobile/android/base/home/PinBookmarkDialog.java
@@ -9,16 +9,17 @@ import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 
 import android.app.Activity;
 import android.content.Context;
 import android.database.Cursor;
 import android.os.Bundle;
 import android.support.v4.app.DialogFragment;
 import android.support.v4.app.LoaderManager;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.support.v4.widget.CursorAdapter;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,32 +34,29 @@ class PinBookmarkDialog extends DialogFr
     // Listener for url selection
     public static interface OnBookmarkSelectedListener {
         public void onBookmarkSelected(String url, String title);
     }
 
     // Cursor loader ID for search query
     private static final int LOADER_ID_SEARCH = 0;
 
-    // Cursor loader ID for favicons query
-    private static final int LOADER_ID_FAVICONS = 1;
-
     // Holds the current search term to use in the query
     private String mSearchTerm;
 
     // Adapter for the list of search results
     private SearchAdapter mAdapter;
 
     // Search entry
     private EditText mSearch;
 
     // Search results
     private ListView mList;
 
-    // Callbacks used for the search and favicon cursor loaders
+    // Callbacks used for the search loader
     private CursorLoaderCallbacks mLoaderCallbacks;
 
     // Bookmark selected listener
     private OnBookmarkSelectedListener mOnBookmarkSelectedListener;
 
     public static PinBookmarkDialog newInstance() {
         return new PinBookmarkDialog();
     }
@@ -120,25 +118,24 @@ class PinBookmarkDialog extends DialogFr
             }
         });
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        final Activity activity = getActivity();
         final LoaderManager manager = getLoaderManager();
 
         // Initialize the search adapter
-        mAdapter = new SearchAdapter(activity);
+        mAdapter = new SearchAdapter(getActivity());
         mList.setAdapter(mAdapter);
 
         // Create callbacks before the initial loader is started
-        mLoaderCallbacks = new CursorLoaderCallbacks(activity, manager);
+        mLoaderCallbacks = new CursorLoaderCallbacks();
 
         // Reconnect to the loader only if present
         manager.initLoader(LOADER_ID_SEARCH, null, mLoaderCallbacks);
 
         // Default filter.
         filter("");
     }
 
@@ -174,47 +171,25 @@ class PinBookmarkDialog extends DialogFr
         }
 
         @Override
         public View newView(Context context, Cursor cursor, ViewGroup parent) {
             return (TwoLinePageRow) mInflater.inflate(R.layout.home_item_row, parent, false);
         }
     }
 
-    private class CursorLoaderCallbacks extends HomeCursorLoaderCallbacks {
-        public CursorLoaderCallbacks(Context context, LoaderManager loaderManager) {
-            super(context, loaderManager);
-        }
-
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            if (id == LOADER_ID_SEARCH) {
-                return SearchLoader.createInstance(getActivity(), args);
-            } else {
-                return super.onCreateLoader(id, args);
-            }
+            return SearchLoader.createInstance(getActivity(), args);
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            if (loader.getId() == LOADER_ID_SEARCH) {
-                mAdapter.swapCursor(c);
-                loadFavicons(c);
-            } else {
-                super.onLoadFinished(loader, c);
-            }
+            mAdapter.swapCursor(c);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
-            if (loader.getId() == LOADER_ID_SEARCH) {
-                mAdapter.swapCursor(null);
-            } else {
-                super.onLoaderReset(loader);
-            }
-        }
-
-        @Override
-        public void onFaviconsLoaded() {
-            mAdapter.notifyDataSetChanged();
+            mAdapter.swapCursor(null);
         }
     }
 }
--- a/mobile/android/base/home/ReadingListPage.java
+++ b/mobile/android/base/home/ReadingListPage.java
@@ -214,38 +214,23 @@ public class ReadingListPage extends Hom
     }
 
     /**
      * LoaderCallbacks implementation that interacts with the LoaderManager.
      */
     private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            switch(id) {
-                case LOADER_ID_READING_LIST:
-                    return new ReadingListLoader(getActivity());
-            }
-            return null;
+            return new ReadingListLoader(getActivity());
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            final int loaderId = loader.getId();
-            switch(loaderId) {
-                case LOADER_ID_READING_LIST:
-                    mAdapter.swapCursor(c);
-                    break;
-           }
-
-           updateUiFromCursor(c);
+            mAdapter.swapCursor(c);
+            updateUiFromCursor(c);
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) {
-            final int loaderId = loader.getId();
-            switch(loaderId) {
-                case LOADER_ID_READING_LIST:
-                    mAdapter.swapCursor(null);
-                    break;
-            }
+            mAdapter.swapCursor(null);
         }
     }
 }
--- a/mobile/android/base/home/TwoLinePageRow.java
+++ b/mobile/android/base/home/TwoLinePageRow.java
@@ -5,24 +5,29 @@
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.favicons.Favicons;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Combined;
+import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.widget.FaviconView;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.Build;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class TwoLinePageRow extends LinearLayout
@@ -35,16 +40,18 @@ public class TwoLinePageRow extends Line
 
     private int mUrlIconId;
     private int mBookmarkIconId;
     private boolean mShowIcons;
 
     // The URL for the page corresponding to this view.
     private String mPageUrl;
 
+    private LoadFaviconTask mLoadFaviconTask;
+
     public TwoLinePageRow(Context context) {
         this(context, null);
     }
 
     public TwoLinePageRow(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         setGravity(Gravity.CENTER_VERTICAL);
@@ -69,16 +76,18 @@ public class TwoLinePageRow extends Line
         // Delay removing the listener to avoid modifying mTabsChangedListeners
         // while notifyListeners is iterating through the array.
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 Tabs.unregisterOnTabsChangedListener(TwoLinePageRow.this);
             }
         });
+
+        cancelLoadFaviconTask();
     }
 
     @Override
     public void onTabChanged(final Tab tab, final Tabs.TabEvents msg, final Object data) {
         switch(msg) {
             case ADDED:
             case CLOSED:
             case LOCATION_CHANGE:
@@ -126,16 +135,26 @@ public class TwoLinePageRow extends Line
      * tab changes or is closed.
      */
     private void updateDisplayedUrl(String url) {
         mPageUrl = url;
         updateDisplayedUrl();
     }
 
     /**
+     * Cancels any pending favicon loading task associated with this view.
+     */
+    private void cancelLoadFaviconTask() {
+        if (mLoadFaviconTask != null) {
+            mLoadFaviconTask.cancel(true);
+            mLoadFaviconTask = null;
+        }
+    }
+
+    /**
      * Replaces the page URL with "Switch to tab" if there is already a tab open with that URL.
      */
     private void updateDisplayedUrl() {
         int tabId = Tabs.getInstance().getTabIdForUrl(mPageUrl);
         if (!mShowIcons || tabId < 0) {
             setUrl(mPageUrl);
             setUrlIcon(NO_ICON);
         } else {
@@ -158,34 +177,54 @@ public class TwoLinePageRow extends Line
 
         int urlIndex = cursor.getColumnIndexOrThrow(URLColumns.URL);
         final String url = cursor.getString(urlIndex);
 
         // Use the URL instead of an empty title for consistency with the normal URL
         // bar view - this is the equivalent of getDisplayTitle() in Tab.java
         setTitle(TextUtils.isEmpty(title) ? url : title);
 
+        // No need to do extra work if the URL associated with this view
+        // hasn't changed.
+        if (TextUtils.equals(mPageUrl, url)) {
+            return;
+        }
+
         updateDisplayedUrl(url);
 
         int faviconIndex = cursor.getColumnIndex(URLColumns.FAVICON);
+        Bitmap favicon = null;
         if (faviconIndex != -1) {
             byte[] b = cursor.getBlob(faviconIndex);
 
-            Bitmap favicon = null;
             if (b != null) {
                 Bitmap bitmap = BitmapUtils.decodeByteArray(b);
                 if (bitmap != null) {
                     favicon = Favicons.scaleImage(bitmap);
                 }
             }
+        } else {
+            // If favicons is not on the cursor, try to fetch it from the memory cache
+            favicon = Favicons.getFaviconFromMemCache(url);
+        }
 
+        cancelLoadFaviconTask();
+
+        if (favicon != null) {
             setFaviconWithUrl(favicon, url);
         } else {
-            // If favicons is not on the cursor, try to fetch it from the memory cache
-            setFaviconWithUrl(Favicons.getFaviconFromMemCache(url), url);
+            mLoadFaviconTask = new LoadFaviconTask(url);
+
+            // Try to use a thread pool instead of serial execution of tasks
+            // to add more throughput to the favicon loading routines.
+            if (Build.VERSION.SDK_INT >= 11) {
+                mLoadFaviconTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+            } else {
+                mLoadFaviconTask.execute();
+            }
         }
 
         // Don't show bookmark/reading list icon, if not needed.
         if (!mShowIcons) {
             return;
         }
 
         final int bookmarkIdIndex = cursor.getColumnIndex(Combined.BOOKMARK_ID);
@@ -208,9 +247,42 @@ public class TwoLinePageRow extends Line
                 setBookmarkIcon(R.drawable.ic_url_bar_reader);
             } else {
                 setBookmarkIcon(R.drawable.ic_url_bar_star);
             }
         } else {
             setBookmarkIcon(NO_ICON);
         }
     }
+
+    private class LoadFaviconTask extends AsyncTask<Void, Void, Bitmap> {
+        private final String mUrl;
+
+        public LoadFaviconTask(String url) {
+            mUrl = url;
+        }
+
+        @Override
+        public Bitmap doInBackground(Void... params) {
+            Bitmap favicon = Favicons.getFaviconFromMemCache(mUrl);
+            if (favicon == null) {
+                final ContentResolver cr = getContext().getContentResolver();
+
+                final Bitmap faviconFromDb = BrowserDB.getFaviconForUrl(cr, mUrl);
+                if (faviconFromDb != null) {
+                    favicon = Favicons.scaleImage(faviconFromDb);
+                    Favicons.putFaviconInMemCache(mUrl, favicon);
+                }
+            }
+
+            return favicon;
+        }
+
+        @Override
+        public void onPostExecute(Bitmap favicon) {
+            if (TextUtils.equals(mPageUrl, mUrl)) {
+                setFaviconWithUrl(favicon, mUrl);
+            }
+
+            mLoadFaviconTask = null;
+        }
+    }
 }