Bug 883300: Make BookmarksListView a class of its own. [r=margaret]
authorSriram Ramasubramanian <sriram@mozilla.com>
Fri, 14 Jun 2013 12:00:59 -0700
changeset 143343 6cd11ae108b510c079347d326444ea0f984da7a9
parent 143342 21426026164f1af92898e64c88fb8dc05d42270b
child 143344 202981fc6c93a92e4ec42dd4b13f50055f7a2a7d
push id25130
push userlrocha@mozilla.com
push dateWed, 21 Aug 2013 09:41:27 +0000
treeherdermozilla-central@b2486721572e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret
bugs883300
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 883300: Make BookmarksListView a class of its own. [r=margaret]
mobile/android/base/GeckoViewsFactory.java
mobile/android/base/Makefile.in
mobile/android/base/home/BookmarksListView.java
mobile/android/base/home/BookmarksPage.java
mobile/android/base/home/HomeListView.java
mobile/android/base/resources/layout/home_bookmarks_page.xml
--- a/mobile/android/base/GeckoViewsFactory.java
+++ b/mobile/android/base/GeckoViewsFactory.java
@@ -1,16 +1,17 @@
 /* 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;
 
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.home.BookmarkFolderView;
+import org.mozilla.gecko.home.BookmarksListView;
 import org.mozilla.gecko.home.FadedTextView;
 import org.mozilla.gecko.home.TwoLinePageRow;
 import org.mozilla.gecko.menu.MenuItemDefault;
 import org.mozilla.gecko.widget.AboutHomeView;
 import org.mozilla.gecko.widget.AddonsSection;
 import org.mozilla.gecko.widget.FaviconView;
 import org.mozilla.gecko.widget.IconTabWidget;
 import org.mozilla.gecko.widget.LastTabsSection;
@@ -84,16 +85,17 @@ public final class GeckoViewsFactory imp
             mFactoryMap.put("ImageButton", GeckoImageButton.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("ImageView", GeckoImageView.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("LinearLayout", GeckoLinearLayout.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("RelativeLayout", GeckoRelativeLayout.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("TextSwitcher", GeckoTextSwitcher.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("TextView", GeckoTextView.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("FaviconView", FaviconView.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("home.BookmarkFolderView", BookmarkFolderView.class.getConstructor(arg1Class, arg2Class));
+            mFactoryMap.put("home.BookmarksListView", BookmarksListView.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("home.FadedTextView", FadedTextView.class.getConstructor(arg1Class, arg2Class));
             mFactoryMap.put("home.TwoLinePageRow", TwoLinePageRow.class.getConstructor(arg1Class, arg2Class));
         } catch (NoSuchMethodException nsme) {
             Log.e(LOGTAG, "Unable to initialize views factory", nsme);
         }
     }
 
     // Making this a singleton class.
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -208,16 +208,17 @@ FENNEC_JAVA_FILES = \
   gfx/SubdocumentScrollHelper.java \
   gfx/TextLayer.java \
   gfx/TextureGenerator.java \
   gfx/TextureReaper.java \
   gfx/TileLayer.java \
   gfx/TouchEventHandler.java \
   gfx/ViewTransform.java \
   gfx/VirtualLayer.java \
+  home/BookmarksListView.java \
   home/BookmarksPage.java \
   home/BookmarkFolderView.java \
   home/HomeFragment.java \
   home/HomeListView.java \
   home/HomePager.java \
   home/HomePagerTabStrip.java \
   home/FadedTextView.java \
   home/TwoLinePageRow.java \
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/BookmarksListView.java
@@ -0,0 +1,334 @@
+/* -*- 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.R;
+import org.mozilla.gecko.db.BrowserContract.Bookmarks;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.BrowserDB.URLColumns;
+import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.util.GamepadUtils;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+
+import java.util.LinkedList;
+
+public class BookmarksListView extends HomeListView
+                               implements AdapterView.OnItemClickListener{
+    
+    public static final String LOGTAG = "GeckoBookmarksListView";
+
+    private int mFolderId = Bookmarks.FIXED_ROOT_ID;
+    private String mFolderTitle = "";
+
+    private BookmarksListAdapter mCursorAdapter = null;
+    private BookmarksQueryTask mQueryTask = null;
+
+    // Folder title for the currently shown list of bookmarks.
+    private BookmarkFolderView mFolderView;
+
+    public BookmarksListView(Context context) {
+        this(context, null);
+    }
+
+    public BookmarksListView(Context context, AttributeSet attrs) {
+        this(context, attrs, android.R.attr.listViewStyle);
+    }
+
+    public BookmarksListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        // Folder title view, is always in open state.
+        mFolderView = (BookmarkFolderView) LayoutInflater.from(context).inflate(R.layout.bookmark_folder_row, null);
+        mFolderView.open();
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        // Intialize the adapter.
+        mCursorAdapter = new BookmarksListAdapter(getContext());
+
+        // We need to add the header before we set the adapter, hence make it null
+        refreshListWithCursor(null);
+
+        setOnItemClickListener(this);
+        setOnKeyListener(GamepadUtils.getListItemClickDispatcher());
+
+        mQueryTask = new BookmarksQueryTask();
+        mQueryTask.execute();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        // Can't use getters for adapter. It will create one if null.
+        if (mCursorAdapter != null) {
+            final Cursor cursor = mCursorAdapter.getCursor();
+            mCursorAdapter = null;
+
+            // Gingerbread locks the DB when closing a cursor, so do it in the background.
+            ThreadUtils.postToBackgroundThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (cursor != null && !cursor.isClosed())
+                        cursor.close();
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        final ListView list = (ListView) parent;
+        final int headerCount = list.getHeaderViewsCount();
+
+        // If we tap on the header view, move back to parent folder.
+        if (headerCount == 1 && position == 0) {
+            mCursorAdapter.moveToParentFolder();
+            return;
+        }
+
+        final Cursor cursor = mCursorAdapter.getCursor();
+        if (cursor == null) {
+            return;
+        }
+
+        // The header view takes up a spot in the list
+        if (headerCount == 1) {
+            position--;
+        }
+
+        cursor.moveToPosition(position);
+
+        int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
+        if (type == Bookmarks.TYPE_FOLDER) {
+            // If we're clicking on a folder, update adapter to move to that folder
+            final int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
+            final String folderTitle = mCursorAdapter.getFolderTitle(position);
+            mCursorAdapter.moveToChildFolder(folderId, folderTitle);
+        } else {
+            // Otherwise, just open the URL
+            final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
+            OnUrlOpenListener listener = getOnUrlOpenListener();
+            if (listener != null) {
+                listener.onUrlOpen(url);
+            }
+        }
+    }
+
+    private void refreshListWithCursor(Cursor cursor) {
+        // We need to add the header before we set the adapter, hence making it null.
+        setAdapter(null);
+
+        // Add a header view based on the root folder.
+        if (mFolderId == Bookmarks.FIXED_ROOT_ID) {
+            if (getHeaderViewsCount() == 1) {
+                removeHeaderView(mFolderView);
+            }
+        } else {
+            if (getHeaderViewsCount() == 0) {
+                addHeaderView(mFolderView, null, true);
+            }
+
+            mFolderView.setText(mFolderTitle);
+        }
+
+        // This will update the cursorAdapter to use the new one if it already exists.
+        mCursorAdapter.changeCursor(cursor);
+        setAdapter(mCursorAdapter);
+
+        // Reset the task.
+        mQueryTask = null;
+    }
+
+    /**
+     * Adapter to back the ListView with a list of bookmarks.
+     */
+    private class BookmarksListAdapter extends SimpleCursorAdapter {
+        private static final int VIEW_TYPE_ITEM = 0;
+        private static final int VIEW_TYPE_FOLDER = 1;
+
+        private static final int VIEW_TYPE_COUNT = 2;
+
+        // mParentStack holds folder id/title pairs that allow us to navigate
+        // back up the folder heirarchy.
+        private LinkedList<Pair<Integer, String>> mParentStack;
+
+        public BookmarksListAdapter(Context context) {
+            // Initializing with a null cursor.
+            super(context, -1, null, new String[] {}, new int[] {});
+
+            mParentStack = new LinkedList<Pair<Integer, String>>();
+
+            // Add the root folder to the stack
+            Pair<Integer, String> rootFolder = new Pair<Integer, String>(mFolderId, mFolderTitle);
+            mParentStack.addFirst(rootFolder);
+        }
+
+        // Refresh the current folder by executing a new task.
+        private void refreshCurrentFolder() {
+            // Cancel any pre-existing async refresh tasks
+            if (mQueryTask != null) {
+                mQueryTask.cancel(false);
+            }
+
+            Pair<Integer, String> folderPair = mParentStack.getFirst();
+            mFolderId = folderPair.first;
+            mFolderTitle = folderPair.second;
+
+            mQueryTask = new BookmarksQueryTask();
+            mQueryTask.execute(new Integer(mFolderId));
+        }
+
+        /**
+         * Moves to parent folder, if one exists.
+         */
+        public void moveToParentFolder() {
+            // If we're already at the root, we can't move to a parent folder
+            if (mParentStack.size() != 1) {
+                mParentStack.removeFirst();
+                refreshCurrentFolder();
+            }
+        }
+
+        /**
+         * Moves to child folder, given a folderId.
+         *
+         * @param folderId The id of the folder to show.
+         * @param folderTitle The title of the folder to show.
+         */
+        public void moveToChildFolder(int folderId, String folderTitle) {
+            Pair<Integer, String> folderPair = new Pair<Integer, String>(folderId, folderTitle);
+            mParentStack.addFirst(folderPair);
+            refreshCurrentFolder();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getItemViewType(int position) {
+            Cursor c = getCursor();
+
+            if (c.moveToPosition(position) &&
+                c.getInt(c.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) {
+                return VIEW_TYPE_FOLDER;
+            }
+
+            // Default to retuning normal item type
+            return VIEW_TYPE_ITEM;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getViewTypeCount() {
+            return VIEW_TYPE_COUNT;
+        }
+
+        /**
+         * Get the title of the folder for a given position.
+         *
+         * @param position The position of the view.
+         * @return The title of the folder at the position.
+         */
+        public String getFolderTitle(int position) {
+            Cursor c = getCursor();
+            if (!c.moveToPosition(position)) {
+                return "";
+            }
+
+            String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID));
+
+            // If we don't have a special GUID, just return the folder title from the DB.
+            if (guid == null || guid.length() == 12) {
+                return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
+            }
+
+            // Use localized strings for special folder names.
+            if (guid.equals(Bookmarks.FAKE_DESKTOP_FOLDER_GUID)) {
+                return getResources().getString(R.string.bookmarks_folder_desktop);
+            } else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) {
+                return getResources().getString(R.string.bookmarks_folder_menu);
+            } else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) {
+                return getResources().getString(R.string.bookmarks_folder_toolbar);
+            } else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) {
+                return getResources().getString(R.string.bookmarks_folder_unfiled);
+            }
+
+            // If for some reason we have a folder with a special GUID, but it's not one of
+            // the special folders we expect in the UI, just return the title from the DB.
+            return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final int viewType = getItemViewType(position);
+
+            if (convertView == null) {
+                if (viewType == VIEW_TYPE_ITEM) {
+                    convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.home_item_row, null);
+                } else {
+                    convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_folder_row, null);
+                }
+            }
+
+            Cursor cursor = getCursor();
+            if (!cursor.moveToPosition(position)) {
+                throw new IllegalStateException("Couldn't move cursor to position " + position);
+            }
+
+            if (viewType == VIEW_TYPE_ITEM) {
+                TwoLinePageRow row = (TwoLinePageRow) convertView;
+                row.updateFromCursor(cursor);
+            } else {
+                BookmarkFolderView row = (BookmarkFolderView) convertView;
+                row.setText(getFolderTitle(position));
+            }
+
+            return convertView;
+        }
+    }
+
+    /**
+     * AsyncTask to query the DB for bookmarks.
+     */
+    private class BookmarksQueryTask extends AsyncTask<Integer, Void, Cursor> {
+        @Override
+        protected Cursor doInBackground(Integer... folderIds) {
+            int folderId = Bookmarks.FIXED_ROOT_ID;
+
+            if (folderIds.length != 0) {
+                folderId = folderIds[0].intValue();
+            }
+
+            return BrowserDB.getBookmarksInFolder(getContext().getContentResolver(), folderId);
+        }
+
+        @Override
+        protected void onPostExecute(final Cursor cursor) {
+            refreshListWithCursor(cursor);
+        }
+    }
+}
--- a/mobile/android/base/home/BookmarksPage.java
+++ b/mobile/android/base/home/BookmarksPage.java
@@ -1,57 +1,33 @@
 /* -*- 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.R;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserDB.URLColumns;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.util.GamepadUtils;
-import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
-import android.content.Context;
 import android.content.res.Configuration;
-import android.database.Cursor;
-import android.os.AsyncTask;
 import android.os.Bundle;
-import android.util.Pair;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ListView;
-import android.widget.SimpleCursorAdapter;
-
-import java.util.LinkedList;
 
 /**
  * A page in about:home that displays a ListView of bookmarks.
  */
-public class BookmarksPage extends HomeFragment implements AdapterView.OnItemClickListener {
+public class BookmarksPage extends HomeFragment {
     public static final String LOGTAG = "GeckoBookmarksPage";
 
-    private int mFolderId = Bookmarks.FIXED_ROOT_ID;
-    private String mFolderTitle = "";
-
-    private BookmarksListAdapter mCursorAdapter = null;
-    private BookmarksQueryTask mQueryTask = null;
-
     // The view shown by the fragment.
-    private HomeListView mList;
-
-    // Folder title for the currently shown list of bookmarks.
-    private BookmarkFolderView mFolderView;
+    private BookmarksListView mList;
 
     // On URL open listener
     private OnUrlOpenListener mUrlOpenListener;
 
     public BookmarksPage() {
         mUrlOpenListener = null;
     }
 
@@ -60,75 +36,44 @@ public class BookmarksPage extends HomeF
         super.onAttach(activity);
 
         try {
             mUrlOpenListener = (OnUrlOpenListener) activity;
         } catch (ClassCastException e) {
             throw new ClassCastException(activity.toString()
                     + " must implement HomePager.OnUrlOpenListener");
         }
-
-        // Intialize the adapter.
-        mCursorAdapter = new BookmarksListAdapter(getActivity());
     }
 
     @Override
     public void onDetach() {
         super.onDetach();
 
         mUrlOpenListener = null;
-
-        // Can't use getters for adapter. It will create one if null.
-        if (mCursorAdapter != null) {
-            final Cursor cursor = mCursorAdapter.getCursor();
-            mCursorAdapter = null;
-
-            // Gingerbread locks the DB when closing a cursor, so do it in the background.
-            ThreadUtils.postToBackgroundThread(new Runnable() {
-                @Override
-                public void run() {
-                    if (cursor != null && !cursor.isClosed())
-                        cursor.close();
-                }
-            });
-        }
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         super.onCreateView(inflater, container, savedInstanceState);
         return inflater.inflate(R.layout.home_bookmarks_page, null);
     }
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
 
-        mList = (HomeListView) view.findViewById(R.id.bookmarks_list);
-
-        // Folder title view, is always in open state.
-        mFolderView = (BookmarkFolderView) LayoutInflater.from(getActivity()).inflate(R.layout.bookmark_folder_row, null);
-        mFolderView.open();
-
-        // We need to add the header before we set the adapter, hence make it null
-        refreshListWithCursor(null);
-
-        mList.setOnItemClickListener(this);
-        mList.setOnKeyListener(GamepadUtils.getListItemClickDispatcher());
+        mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
+        mList.setOnUrlOpenListener(mUrlOpenListener);
 
         registerForContextMenu(mList);
-
-        mQueryTask = new BookmarksQueryTask();
-        mQueryTask.execute();
     }
 
     @Override
     public void onDestroyView() {
         mList = null;
-        mFolderView = null;
         super.onDestroyView();
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
 
         // Reattach the fragment, forcing a reinflation of its view.
@@ -141,246 +86,9 @@ public class BookmarksPage extends HomeF
         // onResume().
         if (isVisible()) {
             getFragmentManager().beginTransaction()
                                 .detach(this)
                                 .attach(this)
                                 .commitAllowingStateLoss();
         }
     }
-
-    @Override
-    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        final ListView list = (ListView) parent;
-        final int headerCount = list.getHeaderViewsCount();
-
-        // If we tap on the header view, move back to parent folder.
-        if (headerCount == 1 && position == 0) {
-            mCursorAdapter.moveToParentFolder();
-            return;
-        }
-
-        final Cursor cursor = mCursorAdapter.getCursor();
-        if (cursor == null) {
-            return;
-        }
-
-        // The header view takes up a spot in the list
-        if (headerCount == 1) {
-            position--;
-        }
-
-        cursor.moveToPosition(position);
-
-        int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
-        if (type == Bookmarks.TYPE_FOLDER) {
-            // If we're clicking on a folder, update adapter to move to that folder
-            final int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
-            final String folderTitle = mCursorAdapter.getFolderTitle(position);
-            mCursorAdapter.moveToChildFolder(folderId, folderTitle);
-        } else {
-            // Otherwise, just open the URL
-            final String url = cursor.getString(cursor.getColumnIndexOrThrow(URLColumns.URL));
-            if (mUrlOpenListener != null) {
-                mUrlOpenListener.onUrlOpen(url);
-            }
-        }
-    }
-
-    private void refreshListWithCursor(Cursor cursor) {
-        // We need to add the header before we set the adapter, hence making it null.
-        mList.setAdapter(null);
-
-        // Add a header view based on the root folder.
-        if (mFolderId == Bookmarks.FIXED_ROOT_ID) {
-            if (mList.getHeaderViewsCount() == 1) {
-                mList.removeHeaderView(mFolderView);
-            }
-        } else {
-            if (mList.getHeaderViewsCount() == 0) {
-                mList.addHeaderView(mFolderView, null, true);
-            }
-
-            mFolderView.setText(mFolderTitle);
-        }
-
-        // This will update the cursorAdapter to use the new one if it already exists.
-        mCursorAdapter.changeCursor(cursor);
-        mList.setAdapter(mCursorAdapter);
-
-        // Reset the task.
-        mQueryTask = null;
-    }
-
-    /**
-     * Adapter to back the ListView with a list of bookmarks.
-     */
-    private class BookmarksListAdapter extends SimpleCursorAdapter {
-        private static final int VIEW_TYPE_ITEM = 0;
-        private static final int VIEW_TYPE_FOLDER = 1;
-
-        private static final int VIEW_TYPE_COUNT = 2;
-
-        // mParentStack holds folder id/title pairs that allow us to navigate
-        // back up the folder heirarchy.
-        private LinkedList<Pair<Integer, String>> mParentStack;
-
-        public BookmarksListAdapter(Context context) {
-            // Initializing with a null cursor.
-            super(context, -1, null, new String[] {}, new int[] {});
-
-            mParentStack = new LinkedList<Pair<Integer, String>>();
-
-            // Add the root folder to the stack
-            Pair<Integer, String> rootFolder = new Pair<Integer, String>(mFolderId, mFolderTitle);
-            mParentStack.addFirst(rootFolder);
-        }
-
-        // Refresh the current folder by executing a new task.
-        private void refreshCurrentFolder() {
-            // Cancel any pre-existing async refresh tasks
-            if (mQueryTask != null) {
-                mQueryTask.cancel(false);
-            }
-
-            Pair<Integer, String> folderPair = mParentStack.getFirst();
-            mFolderId = folderPair.first;
-            mFolderTitle = folderPair.second;
-
-            mQueryTask = new BookmarksQueryTask();
-            mQueryTask.execute(new Integer(mFolderId));
-        }
-
-        /**
-         * Moves to parent folder, if one exists.
-         */
-        public void moveToParentFolder() {
-            // If we're already at the root, we can't move to a parent folder
-            if (mParentStack.size() != 1) {
-                mParentStack.removeFirst();
-                refreshCurrentFolder();
-            }
-        }
-
-        /**
-         * Moves to child folder, given a folderId.
-         *
-         * @param folderId The id of the folder to show.
-         * @param folderTitle The title of the folder to show.
-         */
-        public void moveToChildFolder(int folderId, String folderTitle) {
-            Pair<Integer, String> folderPair = new Pair<Integer, String>(folderId, folderTitle);
-            mParentStack.addFirst(folderPair);
-            refreshCurrentFolder();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getItemViewType(int position) {
-            Cursor c = getCursor();
-
-            if (c.moveToPosition(position) &&
-                c.getInt(c.getColumnIndexOrThrow(Bookmarks.TYPE)) == Bookmarks.TYPE_FOLDER) {
-                return VIEW_TYPE_FOLDER;
-            }
-
-            // Default to retuning normal item type
-            return VIEW_TYPE_ITEM;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getViewTypeCount() {
-            return VIEW_TYPE_COUNT;
-        }
-
-        /**
-         * Get the title of the folder for a given position.
-         *
-         * @param position The position of the view.
-         * @return The title of the folder at the position.
-         */
-        public String getFolderTitle(int position) {
-            Cursor c = getCursor();
-            if (!c.moveToPosition(position)) {
-                return "";
-            }
-
-            String guid = c.getString(c.getColumnIndexOrThrow(Bookmarks.GUID));
-
-            // If we don't have a special GUID, just return the folder title from the DB.
-            if (guid == null || guid.length() == 12) {
-                return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
-            }
-
-            // Use localized strings for special folder names.
-            if (guid.equals(Bookmarks.FAKE_DESKTOP_FOLDER_GUID)) {
-                return getResources().getString(R.string.bookmarks_folder_desktop);
-            } else if (guid.equals(Bookmarks.MENU_FOLDER_GUID)) {
-                return getResources().getString(R.string.bookmarks_folder_menu);
-            } else if (guid.equals(Bookmarks.TOOLBAR_FOLDER_GUID)) {
-                return getResources().getString(R.string.bookmarks_folder_toolbar);
-            } else if (guid.equals(Bookmarks.UNFILED_FOLDER_GUID)) {
-                return getResources().getString(R.string.bookmarks_folder_unfiled);
-            }
-
-            // If for some reason we have a folder with a special GUID, but it's not one of
-            // the special folders we expect in the UI, just return the title from the DB.
-            return c.getString(c.getColumnIndexOrThrow(Bookmarks.TITLE));
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final int viewType = getItemViewType(position);
-
-            if (convertView == null) {
-                if (viewType == VIEW_TYPE_ITEM) {
-                    convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.home_item_row, null);
-                } else {
-                    convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_folder_row, null);
-                }
-            }
-
-            Cursor cursor = getCursor();
-            if (!cursor.moveToPosition(position)) {
-                throw new IllegalStateException("Couldn't move cursor to position " + position);
-            }
-
-            if (viewType == VIEW_TYPE_ITEM) {
-                TwoLinePageRow row = (TwoLinePageRow) convertView;
-                row.updateFromCursor(cursor);
-            } else {
-                BookmarkFolderView row = (BookmarkFolderView) convertView;
-                row.setText(getFolderTitle(position));
-            }
-
-            return convertView;
-        }
-    }
-
-    /**
-     * AsyncTask to query the DB for bookmarks.
-     */
-    private class BookmarksQueryTask extends AsyncTask<Integer, Void, Cursor> {
-        @Override
-        protected Cursor doInBackground(Integer... folderIds) {
-            int folderId = Bookmarks.FIXED_ROOT_ID;
-
-            if (folderIds.length != 0) {
-                folderId = folderIds[0].intValue();
-            }
-
-            return BrowserDB.getBookmarksInFolder(getActivity().getContentResolver(), folderId);
-        }
-
-        @Override
-        protected void onPostExecute(final Cursor cursor) {
-            refreshListWithCursor(cursor);
-        }
-    }
 }
--- a/mobile/android/base/home/HomeListView.java
+++ b/mobile/android/base/home/HomeListView.java
@@ -3,16 +3,17 @@
  * 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.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserDB.URLColumns;
+import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 
 import android.content.Context;
 import android.database.Cursor;
 import android.util.AttributeSet;
 import android.view.ContextMenu.ContextMenuInfo;
 import android.view.MotionEvent;
 import android.view.View;
 
@@ -25,16 +26,19 @@ import android.widget.ListView;
  * when any of its rows is long pressed.
  */
 public class HomeListView extends ListView
                           implements OnItemLongClickListener {
 
     // ContextMenuInfo associated with the currently long pressed list item.
     private HomeContextMenuInfo mContextMenuInfo;
 
+    // On URL open listener
+    private OnUrlOpenListener mUrlOpenListener;
+
     public HomeListView(Context context) {
         this(context, null);
     }
 
     public HomeListView(Context context, AttributeSet attrs) {
         this(context, attrs, android.R.attr.listViewStyle);
     }
 
@@ -61,16 +65,24 @@ public class HomeListView extends ListVi
         return showContextMenuForChild(HomeListView.this);
     }
 
     @Override
     public ContextMenuInfo getContextMenuInfo() {
         return mContextMenuInfo;
     }
 
+    public OnUrlOpenListener getOnUrlOpenListener() {
+        return mUrlOpenListener;
+    }
+
+    public void setOnUrlOpenListener(OnUrlOpenListener listener) {
+        mUrlOpenListener = listener;
+    }
+
     /**
      * A ContextMenuInfo for HomeListView that adds details from the cursor.
      */
     public static class HomeContextMenuInfo extends AdapterContextMenuInfo {
         public int rowId;
         public String url;
         public byte[] favicon;
         public String title;
--- a/mobile/android/base/resources/layout/home_bookmarks_page.xml
+++ b/mobile/android/base/resources/layout/home_bookmarks_page.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
-<org.mozilla.gecko.home.HomeListView xmlns:android="http://schemas.android.com/apk/res/android"
-                                     android:id="@+id/bookmarks_list"
-                                     android:layout_width="fill_parent"
-                                     android:layout_height="fill_parent"/>
+<org.mozilla.gecko.home.BookmarksListView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/bookmarks_list"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"/>