Initial pass at collapsable bookmarks

 Change bookmark view to show all bookmarks
 Group by account_name

Change-Id: I2c252c9f0e8d4df4271649dbe9bf3db336f921df
diff --git a/src/com/android/browser/AddBookmarkPage.java b/src/com/android/browser/AddBookmarkPage.java
index 08f9d39..3659041 100644
--- a/src/com/android/browser/AddBookmarkPage.java
+++ b/src/com/android/browser/AddBookmarkPage.java
@@ -156,7 +156,7 @@
     }
 
     @Override
-    public void onTop(int level, Object data) {
+    public void onTop(BreadCrumbView view, int level, Object data) {
         if (null == data) return;
         Folder folderData = (Folder) data;
         long folder = folderData.Id;
diff --git a/src/com/android/browser/BookmarksLoader.java b/src/com/android/browser/BookmarksLoader.java
index 7722392..bc06497 100644
--- a/src/com/android/browser/BookmarksLoader.java
+++ b/src/com/android/browser/BookmarksLoader.java
@@ -49,8 +49,8 @@
         ChromeSyncColumns.SERVER_UNIQUE, // 9
     };
 
-    private String mAccountType;
-    private String mAccountName;
+    String mAccountType;
+    String mAccountName;
 
     public BookmarksLoader(Context context, String accountType, String accountName) {
         super(context, addAccount(Bookmarks.CONTENT_URI_DEFAULT_FOLDER, accountType, accountName),
diff --git a/src/com/android/browser/BreadCrumbView.java b/src/com/android/browser/BreadCrumbView.java
index 9aaecd7..6706deb 100644
--- a/src/com/android/browser/BreadCrumbView.java
+++ b/src/com/android/browser/BreadCrumbView.java
@@ -41,8 +41,8 @@
 public class BreadCrumbView extends LinearLayout implements OnClickListener {
     private static final int DIVIDER_PADDING = 12; // dips
 
-    interface Controller {
-        public void onTop(int level, Object data);
+    public interface Controller {
+        public void onTop(BreadCrumbView view, int level, Object data);
     }
 
     private ImageButton mBackButton;
@@ -52,6 +52,7 @@
     private Drawable mSeparatorDrawable;
     private float mDividerPadding;
     private int mMaxVisible = -1;
+    private Context mContext;
 
     /**
      * @param context
@@ -81,12 +82,14 @@
     }
 
     private void init(Context ctx) {
+        mContext = ctx;
+        setFocusable(true);
         mUseBackButton = false;
         mCrumbs = new ArrayList<Crumb>();
-        TypedArray a = ctx.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
+        TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
         mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
         a.recycle();
-        mDividerPadding = DIVIDER_PADDING * ctx.getResources().getDisplayMetrics().density;
+        mDividerPadding = DIVIDER_PADDING * mContext.getResources().getDisplayMetrics().density;
         addBackButton();
     }
 
@@ -134,9 +137,9 @@
     public void notifyController() {
         if (mController != null) {
             if (mCrumbs.size() > 0) {
-                mController.onTop(mCrumbs.size(), getTopCrumb().data);
+                mController.onTop(this, mCrumbs.size(), getTopCrumb().data);
             } else {
-                mController.onTop(0, null);
+                mController.onTop(this, 0, null);
             }
         }
     }
diff --git a/src/com/android/browser/BrowserBookmarksAdapter.java b/src/com/android/browser/BrowserBookmarksAdapter.java
index 201079c..7543528 100644
--- a/src/com/android/browser/BrowserBookmarksAdapter.java
+++ b/src/com/android/browser/BrowserBookmarksAdapter.java
@@ -27,7 +27,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-class BrowserBookmarksAdapter extends CursorAdapter {
+public class BrowserBookmarksAdapter extends CursorAdapter {
     LayoutInflater mInflater;
     int mCurrentView;
 
diff --git a/src/com/android/browser/BrowserBookmarksPage.java b/src/com/android/browser/BrowserBookmarksPage.java
index 1dc2612..46197e9 100644
--- a/src/com/android/browser/BrowserBookmarksPage.java
+++ b/src/com/android/browser/BrowserBookmarksPage.java
@@ -16,7 +16,8 @@
 
 package com.android.browser;
 
-import com.android.browser.BreadCrumbView.Crumb;
+import com.android.browser.view.BookmarkExpandableGridView;
+import com.android.browser.view.BookmarkExpandableGridView.BookmarkContextMenuInfo;
 
 import android.app.Activity;
 import android.app.Fragment;
@@ -25,11 +26,11 @@
 import android.content.ClipboardManager;
 import android.content.ContentUris;
 import android.content.Context;
+import android.content.CursorLoader;
 import android.content.Intent;
 import android.content.Loader;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -40,6 +41,7 @@
 import android.os.Bundle;
 import android.preference.PreferenceManager;
 import android.provider.BrowserContract;
+import android.provider.BrowserContract.Accounts;
 import android.provider.BrowserContract.ChromeSyncColumns;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -50,116 +52,114 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.webkit.WebIconDatabase.IconListener;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.GridView;
+import android.widget.ExpandableListView;
+import android.widget.ExpandableListView.OnChildClickListener;
 import android.widget.ListView;
 import android.widget.PopupMenu.OnMenuItemClickListener;
 import android.widget.Toast;
 
+import java.util.HashMap;
+
 interface BookmarksPageCallbacks {
     // Return true if handled
     boolean onBookmarkSelected(Cursor c, boolean isFolder);
     // Return true if handled
     boolean onOpenInNewWindow(Cursor c);
-    void onFolderChanged(int level, Uri uri);
 }
 
 /**
  *  View showing the user's bookmarks in the browser.
  */
 public class BrowserBookmarksPage extends Fragment implements View.OnCreateContextMenuListener,
-        LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener, IconListener,
-        BreadCrumbView.Controller, OnMenuItemClickListener, OnSharedPreferenceChangeListener {
+        LoaderManager.LoaderCallbacks<Cursor>, IconListener,
+        BreadCrumbView.Controller, OnMenuItemClickListener, OnChildClickListener {
 
     static final String LOGTAG = "browser";
 
-    static final int LOADER_BOOKMARKS = 1;
+    static final int LOADER_ACCOUNTS = 1;
+    static final int LOADER_BOOKMARKS = 100;
 
     static final String EXTRA_DISABLE_WINDOW = "disable_new_window";
 
-    static final String ACCOUNT_NAME_UNSYNCED = "Unsynced";
-
     public static final String PREF_ACCOUNT_TYPE = "acct_type";
     public static final String PREF_ACCOUNT_NAME = "acct_name";
 
+    static final String ACCOUNT_TYPE = "account_type";
+    static final String ACCOUNT_NAME = "account_name";
+
     static final int VIEW_THUMBNAILS = 1;
     static final int VIEW_LIST = 2;
     static final String PREF_SELECTED_VIEW = "bookmarks_view";
 
     BookmarksPageCallbacks mCallbacks;
     View mRoot;
-    GridView mGrid;
+    BookmarkExpandableGridView mGrid;
     ListView mList;
-    BrowserBookmarksAdapter mAdapter;
     boolean mDisableNewWindow;
-    boolean mCanceled = false;
     boolean mEnableContextMenu = true;
-    boolean mShowRootFolder = false;
     View mEmptyView;
     int mCurrentView;
     View mHeader;
-    ViewGroup mHeaderContainer;
-    BreadCrumbView mCrumbs;
-    int mCrumbVisibility = View.VISIBLE;
-    int mCrumbMaxVisible = -1;
-    boolean mCrumbBackButton = false;
+    HashMap<Integer, BrowserBookmarksAdapter> mBookmarkAdapters = new HashMap<Integer, BrowserBookmarksAdapter>();
 
     static BrowserBookmarksPage newInstance(BookmarksPageCallbacks cb,
             Bundle args, ViewGroup headerContainer) {
         BrowserBookmarksPage bbp = new BrowserBookmarksPage();
         bbp.mCallbacks = cb;
-        bbp.mHeaderContainer = headerContainer;
         bbp.setArguments(args);
         return bbp;
     }
 
     @Override
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-        switch (id) {
-            case LOADER_BOOKMARKS: {
-                SharedPreferences prefs = PreferenceManager
-                        .getDefaultSharedPreferences(getActivity());
-                String accountType = prefs.getString(PREF_ACCOUNT_TYPE, null);
-                String accountName = prefs.getString(PREF_ACCOUNT_NAME, null);
-                BookmarksLoader bl = new BookmarksLoader(getActivity(),
-                        accountType, accountName);
-                if (mCrumbs != null) {
-                    Uri uri = (Uri) mCrumbs.getTopData();
-                    if (uri != null) {
-                        bl.setUri(uri);
-                    }
-                }
-                return bl;
-            }
+        if (id == LOADER_ACCOUNTS) {
+            return new AccountsLoader(getActivity());
+        } else if (id >= LOADER_BOOKMARKS) {
+            String accountType = args.getString(ACCOUNT_TYPE);
+            String accountName = args.getString(ACCOUNT_NAME);
+            BookmarksLoader bl = new BookmarksLoader(getActivity(),
+                    accountType, accountName);
+            return bl;
+        } else {
+            throw new UnsupportedOperationException("Unknown loader id " + id);
         }
-        throw new UnsupportedOperationException("Unknown loader id " + id);
     }
 
     @Override
     public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
-        switch (loader.getId()) {
-            case LOADER_BOOKMARKS: {
-                // Set the visibility of the empty vs. content views
-                if (cursor == null || cursor.getCount() == 0) {
-                    mEmptyView.setVisibility(View.VISIBLE);
-                    mGrid.setVisibility(View.GONE);
-                    mList.setVisibility(View.GONE);
-                } else {
-                    mEmptyView.setVisibility(View.GONE);
-                    setupBookmarkView();
-                }
-
-                // Give the new data to the adapter
-                mAdapter.changeCursor(cursor);
-                break;
+        if (loader.getId() == LOADER_ACCOUNTS) {
+            LoaderManager lm = getLoaderManager();
+            int id = LOADER_BOOKMARKS;
+            while (cursor.moveToNext()) {
+                String accountName = cursor.getString(0);
+                String accountType = cursor.getString(1);
+                Bundle args = new Bundle();
+                args.putString(ACCOUNT_NAME, accountName);
+                args.putString(ACCOUNT_TYPE, accountType);
+                BrowserBookmarksAdapter adapter = new BrowserBookmarksAdapter(
+                        getActivity(), mCurrentView);
+                mBookmarkAdapters.put(id, adapter);
+                mGrid.addAccount(accountName, adapter);
+                lm.restartLoader(id, args, this);
+                id++;
             }
+            // TODO: Figure out what a reload of these means
+            // Currently, a reload is triggered whenever bookmarks change
+            // This is less than ideal
+            // It also causes UI flickering as a new adapter is created
+            // instead of re-using an existing one when the account_name is the
+            // same.
+            // For now, this is a one-shot load
+            getLoaderManager().destroyLoader(LOADER_ACCOUNTS);
+        } else if (loader.getId() >= LOADER_BOOKMARKS) {
+            BrowserBookmarksAdapter adapter = mBookmarkAdapters.get(loader.getId());
+            adapter.changeCursor(cursor);
         }
     }
 
     @Override
     public void onLoaderReset(Loader<Cursor> loader) {
-        onLoadFinished(loader, null);
+        // TODO: Figure out what to do here (if anything?)
     }
 
     long getFolderId() {
@@ -181,37 +181,32 @@
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         final Activity activity = getActivity();
-        // It is possible that the view has been canceled when we get to
-        // this point as back has a higher priority
-        if (mCanceled) {
-            return false;
-        }
-        AdapterView.AdapterContextMenuInfo i =
-            (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
+        BookmarkContextMenuInfo i = (BookmarkContextMenuInfo)item.getMenuInfo();
         // If we have no menu info, we can't tell which item was selected.
         if (i == null) {
             return false;
         }
+        BrowserBookmarksAdapter adapter = getChildAdapter(i.groupPosition);
 
         switch (item.getItemId()) {
         case R.id.open_context_menu_id:
-            loadUrl(i.position);
+            loadUrl(adapter, i.childPosition);
             break;
         case R.id.edit_context_menu_id:
-            editBookmark(i.position);
+            editBookmark(adapter, i.childPosition);
             break;
         case R.id.shortcut_context_menu_id:
-            Cursor c = mAdapter.getItem(i.position);
+            Cursor c = adapter.getItem(i.childPosition);
             activity.sendBroadcast(createShortcutIntent(getActivity(), c));
             break;
         case R.id.delete_context_menu_id:
-            displayRemoveBookmarkDialog(i.position);
+            displayRemoveBookmarkDialog(adapter, i.childPosition);
             break;
         case R.id.new_window_context_menu_id:
-            openInNewWindow(i.position);
+            openInNewWindow(adapter, i.childPosition);
             break;
         case R.id.share_link_context_menu_id: {
-            Cursor cursor = mAdapter.getItem(i.position);
+            Cursor cursor = adapter.getItem(i.childPosition);
             Controller.sharePage(activity,
                     cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE),
                     cursor.getString(BookmarksLoader.COLUMN_INDEX_URL),
@@ -220,16 +215,16 @@
             break;
         }
         case R.id.copy_url_context_menu_id:
-            copy(getUrl(i.position));
+            copy(getUrl(adapter, i.childPosition));
             break;
         case R.id.homepage_context_menu_id: {
-            BrowserSettings.getInstance().setHomePage(getUrl(i.position));
+            BrowserSettings.getInstance().setHomePage(getUrl(adapter, i.childPosition));
             Toast.makeText(activity, R.string.homepage_set, Toast.LENGTH_LONG).show();
             break;
         }
         // Only for the Most visited page
         case R.id.save_to_bookmarks_menu_id: {
-            Cursor cursor = mAdapter.getItem(i.position);
+            Cursor cursor = adapter.getItem(i.childPosition);
             String name = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
             String url = cursor.getString(BookmarksLoader.COLUMN_INDEX_URL);
             // If the site is bookmarked, the item becomes remove from
@@ -270,8 +265,9 @@
 
     @Override
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
-        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
-        Cursor cursor = mAdapter.getItem(info.position);
+        BookmarkContextMenuInfo info = (BookmarkContextMenuInfo) menuInfo;
+        BrowserBookmarksAdapter adapter = getChildAdapter(info.groupPosition);
+        Cursor cursor = adapter.getItem(info.childPosition);
         if (!canEdit(cursor)) {
             return;
         }
@@ -330,11 +326,11 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-
         SharedPreferences prefs = PreferenceManager
-                .getDefaultSharedPreferences(getActivity());
-        prefs.registerOnSharedPreferenceChangeListener(this);
+            .getDefaultSharedPreferences(getActivity());
         mCurrentView = prefs.getInt(PREF_SELECTED_VIEW, getDefaultView());
+        // TODO: Support list view
+        mCurrentView = VIEW_THUMBNAILS;
 
         Bundle args = getArguments();
         mDisableNewWindow = args == null ? false : args.getBoolean(EXTRA_DISABLE_WINDOW, false);
@@ -345,40 +341,20 @@
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
-        Context context = getActivity();
-
         mRoot = inflater.inflate(R.layout.bookmarks, container, false);
         mEmptyView = mRoot.findViewById(android.R.id.empty);
 
-        mGrid = (GridView) mRoot.findViewById(R.id.grid);
-        mGrid.setOnItemClickListener(this);
-        mGrid.setColumnWidth(Controller.getDesiredThumbnailWidth(getActivity()));
+        mGrid = (BookmarkExpandableGridView) mRoot.findViewById(R.id.grid);
+        mGrid.setOnChildClickListener(this);
+        mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
+        mGrid.setBreadcrumbController(this);
         mList = (ListView) mRoot.findViewById(R.id.list);
-        mList.setOnItemClickListener(this);
+        // TODO: mList.setOnItemClickListener(this);
         setEnableContextMenu(mEnableContextMenu);
 
-        // Prep the header
-        ViewGroup hc = mHeaderContainer;
-        if (hc == null) {
-            hc = (ViewGroup) mRoot.findViewById(R.id.header_container);
-            hc.setVisibility(View.VISIBLE);
-        }
-        mHeader = inflater.inflate(R.layout.bookmarks_header, hc, false);
-        hc.addView(mHeader);
-        mCrumbs = (BreadCrumbView) mHeader.findViewById(R.id.crumbs);
-        mCrumbs.setController(this);
-        mCrumbs.setUseBackButton(mCrumbBackButton);
-        mCrumbs.setMaxVisible(mCrumbMaxVisible);
-        mCrumbs.setVisibility(mCrumbVisibility);
-        String name = getString(R.string.bookmarks);
-        mCrumbs.pushView(name, false, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
-        if (mCallbacks != null) {
-            mCallbacks.onFolderChanged(1, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
-        }
         // Start the loaders
         LoaderManager lm = getLoaderManager();
-        mAdapter = new BrowserBookmarksAdapter(getActivity(), mCurrentView);
-        lm.restartLoader(LOADER_BOOKMARKS, null, this);
+        lm.restartLoader(LOADER_ACCOUNTS, null, this);
 
         // Add our own listener in case there are favicons that have yet to be loaded.
         CombinedBookmarkHistoryView.getIconListenerSet().addListener(this);
@@ -386,7 +362,7 @@
         return mRoot;
     }
 
-     private int getDefaultView() {
+    private int getDefaultView() {
         if (BrowserActivity.isXlarge(getActivity())) {
             return VIEW_THUMBNAILS;
         }
@@ -396,16 +372,14 @@
     @Override
     public void onDestroyView() {
         super.onDestroyView();
-        SharedPreferences prefs = PreferenceManager
-                .getDefaultSharedPreferences(getActivity());
-        prefs.unregisterOnSharedPreferenceChangeListener(this);
-        if (mHeaderContainer != null) {
-            mHeaderContainer.removeView(mHeader);
+        mGrid.setBreadcrumbController(null);
+        LoaderManager lm = getLoaderManager();
+        lm.destroyLoader(LOADER_ACCOUNTS);
+        for (int id : mBookmarkAdapters.keySet()) {
+            lm.destroyLoader(id);
         }
-        mCrumbs.setController(null);
-        mCrumbs = null;
-        getLoaderManager().destroyLoader(LOADER_BOOKMARKS);
-        mAdapter = null;
+        mBookmarkAdapters.clear();
+
         CombinedBookmarkHistoryView.getIconListenerSet().removeListener(this);
     }
 
@@ -413,35 +387,52 @@
     public void onReceivedIcon(String url, Bitmap icon) {
         // A new favicon has been loaded, so let anything attached to the adapter know about it
         // so new icons will be loaded.
-        mAdapter.notifyDataSetChanged();
+        // TODO: Notify all of data set changed
+        // TODO: Wait, is this even needed? Won't this trigger a DB change anyway?
+    }
+
+    private BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
+        if (mCurrentView == VIEW_THUMBNAILS) {
+            return mGrid.getChildAdapter(groupPosition);
+        } else {
+            // TODO: Support expandable list
+            return null;
+        }
+    }
+
+    private BreadCrumbView getBreadCrumbs(int groupPosition) {
+        if (mCurrentView == VIEW_THUMBNAILS) {
+            return mGrid.getBreadCrumbs(groupPosition);
+        } else {
+            // TODO: Support expandable list
+            return null;
+        }
     }
 
     @Override
-    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
-        // It is possible that the view has been canceled when we get to
-        // this point as back has a higher priority
-        if (mCanceled) {
-            android.util.Log.e(LOGTAG, "item clicked when dismissing");
-            return;
-        }
-
-        Cursor cursor = mAdapter.getItem(position);
+    public boolean onChildClick(ExpandableListView parent, View v,
+            int groupPosition, int childPosition, long id) {
+        BrowserBookmarksAdapter adapter = getChildAdapter(groupPosition);
+        Cursor cursor = adapter.getItem(childPosition);
         boolean isFolder = cursor.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) != 0;
         if (mCallbacks != null &&
                 mCallbacks.onBookmarkSelected(cursor, isFolder)) {
-            return;
+            return true;
         }
 
+        // TODO: Folder stuff
         if (isFolder) {
             String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
             Uri uri = ContentUris.withAppendedId(
                     BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER, id);
-            if (mCrumbs != null) {
+            BreadCrumbView crumbs = getBreadCrumbs(groupPosition);
+            if (crumbs != null) {
                 // update crumbs
-                mCrumbs.pushView(title, uri);
+                crumbs.pushView(title, uri);
             }
-            loadFolder(uri);
+            loadFolder(groupPosition, uri);
         }
+        return true;
     }
 
     /* package */ static Intent createShortcutIntent(Context context, Cursor cursor) {
@@ -452,15 +443,15 @@
         return BookmarkUtils.createAddToHomeIntent(context, url, title, touchIcon, favicon);
     }
 
-    private void loadUrl(int position) {
-        if (mCallbacks != null && mAdapter != null) {
-            mCallbacks.onBookmarkSelected(mAdapter.getItem(position), false);
+    private void loadUrl(BrowserBookmarksAdapter adapter, int position) {
+        if (mCallbacks != null && adapter != null) {
+            mCallbacks.onBookmarkSelected(adapter.getItem(position), false);
         }
     }
 
-    private void openInNewWindow(int position) {
+    private void openInNewWindow(BrowserBookmarksAdapter adapter, int position) {
         if (mCallbacks != null) {
-            Cursor c = mAdapter.getItem(position);
+            Cursor c = adapter.getItem(position);
             boolean isFolder = c.getInt(BookmarksLoader.COLUMN_INDEX_IS_FOLDER) == 1;
             if (isFolder) {
                 long id = c.getLong(BookmarksLoader.COLUMN_INDEX_ID);
@@ -497,9 +488,9 @@
 
     }
 
-    private void editBookmark(int position) {
+    private void editBookmark(BrowserBookmarksAdapter adapter, int position) {
         Intent intent = new Intent(getActivity(), AddBookmarkPage.class);
-        Cursor cursor = mAdapter.getItem(position);
+        Cursor cursor = adapter.getItem(position);
         Bundle item = new Bundle();
         item.putString(BrowserContract.Bookmarks.TITLE,
                 cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE));
@@ -520,18 +511,19 @@
         startActivity(intent);
     }
 
-    private void displayRemoveBookmarkDialog(final int position) {
+    private void displayRemoveBookmarkDialog(BrowserBookmarksAdapter adapter,
+            int position) {
         // Put up a dialog asking if the user really wants to
         // delete the bookmark
-        Cursor cursor = mAdapter.getItem(position);
+        Cursor cursor = adapter.getItem(position);
         long id = cursor.getLong(BookmarksLoader.COLUMN_INDEX_ID);
         String title = cursor.getString(BookmarksLoader.COLUMN_INDEX_TITLE);
         Context context = getActivity();
         BookmarkUtils.displayRemoveBookmarkDialog(id, title, context, null);
     }
 
-    private String getUrl(int position) {
-        return getUrl(mAdapter.getItem(position));
+    private String getUrl(BrowserBookmarksAdapter adapter, int position) {
+        return getUrl(adapter.getItem(position));
     }
 
     /* package */ static String getUrl(Cursor c) {
@@ -563,6 +555,7 @@
         Resources res = getActivity().getResources();
         int horizontalSpacing = (int) res.getDimension(R.dimen.combo_horizontalSpacing);
         mGrid.setHorizontalSpacing(horizontalSpacing);
+        mGrid.setColumnWidthFromLayout(R.layout.bookmark_thumbnail);
         int paddingLeftRight = (int) res.getDimension(R.dimen.combo_paddingLeftRight);
         int paddingTop = (int) res.getDimension(R.dimen.combo_paddingTop);
         mRoot.setPadding(paddingLeftRight, paddingTop,
@@ -577,10 +570,11 @@
     }
 
     void selectView(int view) {
+        // TODO: Support list view
+        view = mCurrentView;
         if (view == mCurrentView) {
             return;
         }
-        mCurrentView = view;
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
         Editor edit = prefs.edit();
         edit.putInt(PREF_SELECTED_VIEW, mCurrentView);
@@ -592,52 +586,53 @@
     }
 
     private void setupBookmarkView() {
-        mAdapter.selectView(mCurrentView);
-        switch (mCurrentView) {
-        case VIEW_THUMBNAILS:
-            mList.setAdapter(null);
-            if (mGrid.getAdapter() != mAdapter) {
-                mGrid.setAdapter(mAdapter);
-            }
-            mGrid.setVisibility(View.VISIBLE);
-            mList.setVisibility(View.GONE);
-            break;
-        case VIEW_LIST:
-            mGrid.setAdapter(null);
-            if (mList.getAdapter() != mAdapter) {
-                mList.setAdapter(mAdapter);
-            }
-            mGrid.setVisibility(View.GONE);
-            mList.setVisibility(View.VISIBLE);
-            break;
-        }
+        // TODO: Support list view
+//        mAdapter.selectView(mCurrentView);
+//        switch (mCurrentView) {
+//        case VIEW_THUMBNAILS:
+//            mList.setAdapter(null);
+//            SharedPreferences prefs = PreferenceManager
+//                    .getDefaultSharedPreferences(getActivity());
+//            String accountName = prefs.getString(PREF_ACCOUNT_NAME, null);
+//            mGrid.addAccount(accountName, mAdapter);
+//            mGrid.setVisibility(View.VISIBLE);
+//            mList.setVisibility(View.GONE);
+//            break;
+//        case VIEW_LIST:
+//            mGrid.clearAccounts();
+//            if (mList.getAdapter() != mAdapter) {
+//                mList.setAdapter(mAdapter);
+//            }
+//            mGrid.setVisibility(View.GONE);
+//            mList.setVisibility(View.VISIBLE);
+//            break;
+//        }
     }
 
     /**
      * BreadCrumb controller callback
      */
     @Override
-    public void onTop(int level, Object data) {
+    public void onTop(BreadCrumbView view, int level, Object data) {
+        int groupPosition = (Integer) view.getTag(R.id.group_position);
         Uri uri = (Uri) data;
         if (uri == null) {
             // top level
             uri = BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER;
         }
-        loadFolder(uri);
+        loadFolder(groupPosition, uri);
     }
 
     /**
      * @param uri
      */
-    private void loadFolder(Uri uri) {
+    private void loadFolder(int groupPosition, Uri uri) {
         LoaderManager manager = getLoaderManager();
-        BookmarksLoader loader =
-                (BookmarksLoader) ((Loader<?>) manager.getLoader(LOADER_BOOKMARKS));
+        // This assumes groups are ordered the same as loaders
+        BookmarksLoader loader = (BookmarksLoader) ((Loader<?>)
+                manager.getLoader(LOADER_BOOKMARKS + groupPosition));
         loader.setUri(uri);
         loader.forceLoad();
-        if (mCallbacks != null) {
-            mCallbacks.onFolderChanged(mCrumbs.getTopLevel(), uri);
-        }
     }
 
     @Override
@@ -654,18 +649,9 @@
     }
 
     public boolean onBackPressed() {
-        if (canGoBack()) {
-            mCrumbs.popView();
-            return true;
-        }
         return false;
     }
 
-    private boolean canGoBack() {
-        Crumb c = mCrumbs.getTopCrumb();
-        return c != null && c.canGoBack;
-    }
-
     public void setCallbackListener(BookmarksPageCallbacks callbackListener) {
         mCallbacks = callbackListener;
     }
@@ -722,41 +708,17 @@
         }
     }
 
-    public void setBreadCrumbVisibility(int visibility) {
-        mCrumbVisibility = visibility;
-        if (mCrumbs != null) {
-            mCrumbs.setVisibility(mCrumbVisibility);
-        }
-    }
+    static class AccountsLoader extends CursorLoader {
 
-    public void setBreadCrumbUseBackButton(boolean use) {
-        mCrumbBackButton = use;
-        if (mCrumbs != null) {
-            mCrumbs.setUseBackButton(mCrumbBackButton);
-        }
-    }
+        static String[] ACCOUNTS_PROJECTION = new String[] {
+            Accounts.ACCOUNT_NAME,
+            Accounts.ACCOUNT_TYPE
+        };
 
-    public void setBreadCrumbMaxVisible(int max) {
-        mCrumbMaxVisible = max;
-        if (mCrumbs != null) {
-            mCrumbs.setMaxVisible(mCrumbMaxVisible);
+        public AccountsLoader(Context context) {
+            super(context, Accounts.CONTENT_URI, ACCOUNTS_PROJECTION, null, null,
+                    Accounts.ACCOUNT_NAME + " ASC");
         }
-    }
 
-    @Override
-    public void onSharedPreferenceChanged(
-            SharedPreferences sharedPreferences, String key) {
-        if (PREF_ACCOUNT_NAME.equals(key) || PREF_ACCOUNT_TYPE.equals(key)) {
-            mCrumbs.setController(null);
-            mCrumbs.clear();
-            LoaderManager lm = getLoaderManager();
-            lm.restartLoader(LOADER_BOOKMARKS, null, this);
-            mCrumbs.setController(this);
-            String name = getString(R.string.bookmarks);
-            mCrumbs.pushView(name, false, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
-            if (mCallbacks != null) {
-                mCallbacks.onFolderChanged(1, BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
-            }
-        }
     }
 }
diff --git a/src/com/android/browser/CombinedBookmarkHistoryView.java b/src/com/android/browser/CombinedBookmarkHistoryView.java
index 793f7a2..af87401 100644
--- a/src/com/android/browser/CombinedBookmarkHistoryView.java
+++ b/src/com/android/browser/CombinedBookmarkHistoryView.java
@@ -204,27 +204,11 @@
             mUiController.onUrlSelected(BrowserBookmarksPage.getUrl(c), false);
             return true;
         }
-
-        @Override
-        public void onFolderChanged(int level, Uri uri) {
-            final int toggleFlags = ActionBar.DISPLAY_SHOW_CUSTOM
-                    | ActionBar.DISPLAY_HOME_AS_UP;
-            // 1 is "bookmarks" root folder
-            if (level <= 1) {
-                mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
-                mActionBar.setDisplayOptions(0, toggleFlags);
-            } else {
-                mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
-                mActionBar.setDisplayOptions(toggleFlags, toggleFlags);
-            }
-        }
     };
 
     private void initFragments(Bundle extras) {
         mBookmarks = BrowserBookmarksPage.newInstance(mBookmarkCallbackWrapper,
                 extras, mBookmarksHeader);
-        mBookmarks.setBreadCrumbMaxVisible(2);
-        mBookmarks.setBreadCrumbUseBackButton(false);
         mHistory = BrowserHistoryPage.newInstance(mUiController, extras);
     }
 
diff --git a/src/com/android/browser/ShortcutActivity.java b/src/com/android/browser/ShortcutActivity.java
index 16a4cbe..af1788d 100644
--- a/src/com/android/browser/ShortcutActivity.java
+++ b/src/com/android/browser/ShortcutActivity.java
@@ -38,8 +38,6 @@
         mBookmarks = (BrowserBookmarksPage) getFragmentManager()
                 .findFragmentById(R.id.bookmarks);
         mBookmarks.setEnableContextMenu(false);
-        mBookmarks.setBreadCrumbMaxVisible(2);
-        mBookmarks.setBreadCrumbUseBackButton(true);
         mBookmarks.setCallbackListener(this);
         View cancel = findViewById(R.id.cancel);
         if (cancel != null) {
@@ -73,10 +71,6 @@
     }
 
     @Override
-    public void onFolderChanged(int level, Uri uri) {
-    }
-
-    @Override
     public void onClick(View v) {
         switch (v.getId()) {
         case R.id.cancel:
diff --git a/src/com/android/browser/provider/BrowserProvider2.java b/src/com/android/browser/provider/BrowserProvider2.java
index d154f20..7d94c7a 100644
--- a/src/com/android/browser/provider/BrowserProvider2.java
+++ b/src/com/android/browser/provider/BrowserProvider2.java
@@ -653,7 +653,7 @@
                 qb.setTables(TABLE_BOOKMARKS);
                 qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
                 qb.setDistinct(true);
-                qb.appendWhere(Bookmarks.ACCOUNT_NAME + " IS NOT NULL");
+                qb.appendWhere(Bookmarks.IS_DELETED + "=0");
                 break;
             }
 
diff --git a/src/com/android/browser/view/BookmarkContainer.java b/src/com/android/browser/view/BookmarkContainer.java
new file mode 100644
index 0000000..260b05e
--- /dev/null
+++ b/src/com/android/browser/view/BookmarkContainer.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.browser.view;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewConfiguration;
+import android.widget.RelativeLayout;
+
+public class BookmarkContainer extends RelativeLayout implements OnClickListener {
+
+    private OnClickListener mClickListener;
+    
+    public BookmarkContainer(Context context) {
+        super(context);
+        init();
+    }
+
+    public BookmarkContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public BookmarkContainer(
+            Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    void init() {
+        setFocusable(true);
+        super.setOnClickListener(this);
+    }
+
+    @Override
+    public void setBackgroundDrawable(Drawable d) {
+        super.setBackgroundDrawable(d);
+    }
+
+    @Override
+    public void setOnClickListener(OnClickListener l) {
+        mClickListener = l;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        updateTransitionDrawable(isPressed());
+    }
+
+    void updateTransitionDrawable(boolean pressed) {
+        final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
+        Drawable selector = getBackground();
+        if (selector != null && selector instanceof StateListDrawable) {
+            Drawable d = ((StateListDrawable)selector).getCurrent();
+            if (d != null && d instanceof TransitionDrawable) {
+                if (pressed && isLongClickable()) {
+                    ((TransitionDrawable) d).startTransition(longPressTimeout);
+                } else {
+                    ((TransitionDrawable) d).resetTransition();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        updateTransitionDrawable(false);
+        if (mClickListener != null) {
+            mClickListener.onClick(view);
+        }
+    }
+}
diff --git a/src/com/android/browser/view/BookmarkExpandableGridView.java b/src/com/android/browser/view/BookmarkExpandableGridView.java
new file mode 100644
index 0000000..1d603cc
--- /dev/null
+++ b/src/com/android/browser/view/BookmarkExpandableGridView.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.browser.view;
+
+import com.android.browser.BreadCrumbView;
+import com.android.browser.BrowserBookmarksAdapter;
+import com.android.browser.R;
+import com.android.internal.view.menu.MenuBuilder;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.provider.BrowserContract;
+import android.util.AttributeSet;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.ExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class BookmarkExpandableGridView extends ExpandableListView
+        implements BreadCrumbView.Controller {
+
+    private BookmarkAccountAdapter mAdapter;
+    private int mColumnWidth;
+    private Context mContext;
+    private OnChildClickListener mOnChildClickListener;
+    private ContextMenuInfo mContextMenuInfo = null;
+    private OnCreateContextMenuListener mOnCreateContextMenuListener;
+    private boolean mLongClickable;
+    private BreadCrumbView.Controller mBreadcrumbController;
+
+    public BookmarkExpandableGridView(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public BookmarkExpandableGridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    public BookmarkExpandableGridView(
+            Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init(context);
+    }
+
+    void init(Context context) {
+        mContext = context;
+        setItemsCanFocus(true);
+        setLongClickable(false);
+        mAdapter = new BookmarkAccountAdapter(mContext);
+        super.setAdapter(mAdapter);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        mAdapter.measureChildren();
+    }
+
+    @Override
+    public void setAdapter(ExpandableListAdapter adapter) {
+        throw new RuntimeException("Not supported");
+    }
+
+    public void setColumnWidthFromLayout(int layout) {
+        LayoutInflater infalter = LayoutInflater.from(mContext);
+        View v = infalter.inflate(layout, this, false);
+        v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+        mColumnWidth = v.getMeasuredWidth();
+    }
+
+    public void setHorizontalSpacing(int horizontalSpacing) {
+        // TODO Auto-generated method stub
+    }
+
+    public void clearAccounts() {
+        mAdapter.clear();
+    }
+
+    public void addAccount(String accountName, BrowserBookmarksAdapter adapter) {
+        // First, check if it already exists
+        int indexOf = mAdapter.mGroups.indexOf(accountName);
+        if (indexOf >= 0) {
+            BrowserBookmarksAdapter existing = mAdapter.mChildren.get(indexOf);
+            if (existing != adapter) {
+                existing.unregisterDataSetObserver(mAdapter.mObserver);
+                // Replace the existing one
+                mAdapter.mChildren.remove(indexOf);
+                mAdapter.mChildren.add(indexOf, adapter);
+                adapter.registerDataSetObserver(mAdapter.mObserver);
+            }
+        } else {
+            mAdapter.mGroups.add(accountName);
+            mAdapter.mChildren.add(adapter);
+            adapter.registerDataSetObserver(mAdapter.mObserver);
+        }
+        mAdapter.notifyDataSetChanged();
+        expandGroup(mAdapter.getGroupCount() - 1);
+    }
+
+    @Override
+    public void setOnChildClickListener(OnChildClickListener onChildClickListener) {
+        mOnChildClickListener = onChildClickListener;
+    }
+
+    @Override
+    public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
+        mOnCreateContextMenuListener = l;
+        if (!mLongClickable) {
+            mLongClickable = true;
+            if (mAdapter != null) {
+                mAdapter.notifyDataSetChanged();
+            }
+        }
+    }
+
+    @Override
+    public void createContextMenu(ContextMenu menu) {
+        // The below is copied from View - we want to bypass the override
+        // in AbsListView
+
+        ContextMenuInfo menuInfo = getContextMenuInfo();
+
+        // Sets the current menu info so all items added to menu will have
+        // my extra info set.
+        ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
+
+        onCreateContextMenu(menu);
+        if (mOnCreateContextMenuListener != null) {
+            mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
+        }
+
+        // Clear the extra information so subsequent items that aren't mine don't
+        // have my extra info.
+        ((MenuBuilder)menu).setCurrentMenuInfo(null);
+
+        if (mParent != null) {
+            mParent.createContextMenu(menu);
+        }
+    }
+
+    @Override
+    public boolean showContextMenuForChild(View originalView) {
+        int groupPosition = (Integer) originalView.getTag(R.id.group_position);
+        int childPosition = (Integer) originalView.getTag(R.id.child_position);
+
+        mContextMenuInfo = new BookmarkContextMenuInfo(originalView,
+                childPosition, groupPosition);
+        if (getParent() != null) {
+            getParent().showContextMenuForChild(this);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onTop(BreadCrumbView view, int level, Object data) {
+        if (mBreadcrumbController != null) {
+            mBreadcrumbController.onTop(view, level, data);
+        }
+    }
+
+    public void setBreadcrumbController(BreadCrumbView.Controller controller) {
+        mBreadcrumbController = controller;
+    }
+
+    @Override
+    protected ContextMenuInfo getContextMenuInfo() {
+        return mContextMenuInfo;
+    }
+
+    public BrowserBookmarksAdapter getChildAdapter(int groupPosition) {
+        return mAdapter.mChildren.get(groupPosition);
+    }
+
+    private OnClickListener mChildClickListener = new OnClickListener() {
+
+        @Override
+        public void onClick(View v) {
+            int groupPosition = (Integer) v.getTag(R.id.group_position);
+            int childPosition = (Integer) v.getTag(R.id.child_position);
+            long id = (Long) v.getTag(R.id.child_id);
+            if (mOnChildClickListener != null) {
+                mOnChildClickListener.onChildClick(BookmarkExpandableGridView.this,
+                        v, groupPosition, childPosition, id);
+            }
+        }
+    };
+
+    private OnClickListener mGroupOnClickListener = new OnClickListener() {
+
+        @Override
+        public void onClick(View v) {
+            int groupPosition = (Integer) v.getTag(R.id.group_position);
+            if (isGroupExpanded(groupPosition)) {
+                collapseGroup(groupPosition);
+            } else {
+                expandGroup(groupPosition);
+            }
+        }
+    };
+
+    public BreadCrumbView getBreadCrumbs(int groupPosition) {
+        return mAdapter.getBreadCrumbView(groupPosition);
+    }
+
+    class BookmarkAccountAdapter extends BaseExpandableListAdapter {
+        ArrayList<BrowserBookmarksAdapter> mChildren;
+        ArrayList<String> mGroups;
+        HashMap<Integer, BreadCrumbView> mBreadcrumbs =
+                new HashMap<Integer, BreadCrumbView>();
+        LayoutInflater mInflater;
+        int mRowCount = 1; // assume at least 1 child fits in a row
+        int mLastViewWidth = -1;
+        int mRowPadding = -1;
+        DataSetObserver mObserver = new DataSetObserver() {
+            @Override
+            public void onChanged() {
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public void onInvalidated() {
+                notifyDataSetChanged();
+            }
+        };
+
+        public BookmarkAccountAdapter(Context context) {
+            mContext = context;
+            mInflater = LayoutInflater.from(mContext);
+            mChildren = new ArrayList<BrowserBookmarksAdapter>();
+            mGroups = new ArrayList<String>();
+        }
+
+        public void clear() {
+            mGroups.clear();
+            mChildren.clear();
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public Object getChild(int groupPosition, int childPosition) {
+            return mChildren.get(groupPosition).getItem(childPosition);
+        }
+
+        @Override
+        public long getChildId(int groupPosition, int childPosition) {
+            return childPosition;
+        }
+
+        @Override
+        public View getChildView(int groupPosition, int childPosition,
+                boolean isLastChild, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false);
+            }
+            LinearLayout row = (LinearLayout) convertView;
+            row.setPadding(
+                    mRowPadding,
+                    row.getPaddingTop(),
+                    mRowPadding,
+                    row.getPaddingBottom());
+            if (row.getChildCount() > mRowCount) {
+                row.removeViews(mRowCount, row.getChildCount() - mRowCount);
+            }
+            for (int i = 0; i < mRowCount; i++) {
+                View cv = null;
+                if (row.getChildCount() > i) {
+                    cv = row.getChildAt(i);
+                }
+                int realChildPosition = (childPosition * mRowCount) + i;
+                BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition);
+                if (realChildPosition < childAdapter.getCount()) {
+                    View v = childAdapter.getView(realChildPosition, cv, row);
+                    v.setTag(R.id.group_position, groupPosition);
+                    v.setTag(R.id.child_position, realChildPosition);
+                    v.setTag(R.id.child_id, childAdapter.getItemId(realChildPosition));
+                    v.setOnClickListener(mChildClickListener);
+                    v.setLongClickable(mLongClickable);
+                    if (cv == null) {
+                        row.addView(v);
+                    } else if (cv != v) {
+                        row.removeViewAt(i);
+                        row.addView(v, i);
+                    } else {
+                        cv.setVisibility(View.VISIBLE);
+                    }
+                } else if (cv != null) {
+                    cv.setVisibility(View.GONE);
+                }
+            }
+            return row;
+        }
+
+        @Override
+        public int getChildrenCount(int groupPosition) {
+            return (int) Math.ceil(
+                    mChildren.get(groupPosition).getCount() / (float)mRowCount);
+        }
+
+        @Override
+        public Object getGroup(int groupPosition) {
+            return mChildren.get(groupPosition);
+        }
+
+        @Override
+        public int getGroupCount() {
+            return mGroups.size();
+        }
+
+        public void measureChildren() {
+            int viewWidth = getMeasuredWidth();
+            if (mLastViewWidth == viewWidth) return;
+
+            ViewGroup parent = (ViewGroup) mInflater.inflate(R.layout.bookmark_grid_row, null);
+            viewWidth -= parent.getPaddingLeft() + parent.getPaddingRight();
+            int rowCount = viewWidth / mColumnWidth;
+            int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2;
+            boolean notify = rowCount != mRowCount || rowPadding != mRowPadding;
+            mRowCount = rowCount;
+            mRowPadding = rowPadding;
+            mLastViewWidth = viewWidth;
+            if (notify) {
+                notifyDataSetChanged();
+            }
+        }
+
+        @Override
+        public long getGroupId(int groupPosition) {
+            return groupPosition;
+        }
+
+        @Override
+        public View getGroupView(int groupPosition, boolean isExpanded,
+                View view, ViewGroup parent) {
+            if (view == null) {
+                view = mInflater.inflate(R.layout.bookmark_group_view, parent, false);
+                view.setOnClickListener(mGroupOnClickListener);
+            }
+            view.setTag(R.id.group_position, groupPosition);
+            FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder);
+            crumbHolder.removeAllViews();
+            BreadCrumbView crumbs = getBreadCrumbView(groupPosition);
+            if (crumbs.getParent() != null) {
+                ((ViewGroup)crumbs.getParent()).removeView(crumbs);
+            }
+            crumbHolder.addView(crumbs);
+            TextView name = (TextView) view.findViewById(R.id.group_name);
+            String groupName = mGroups.get(groupPosition);
+            if (groupName == null) {
+                groupName = mContext.getString(R.string.local_bookmarks);
+            }
+            name.setText(groupName);
+            return view;
+        }
+
+        public BreadCrumbView getBreadCrumbView(int groupPosition) {
+            BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition);
+            if (crumbs == null) {
+                crumbs = (BreadCrumbView)
+                        mInflater.inflate(R.layout.bookmarks_header, null);
+                crumbs.setController(BookmarkExpandableGridView.this);
+                crumbs.setUseBackButton(true);
+                crumbs.setMaxVisible(2);
+                String bookmarks = mContext.getString(R.string.bookmarks);
+                crumbs.pushView(bookmarks, false,
+                        BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER);
+                crumbs.setTag(R.id.group_position, groupPosition);
+                mBreadcrumbs.put(groupPosition, crumbs);
+            }
+            return crumbs;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return false;
+        }
+
+        @Override
+        public boolean isChildSelectable(int groupPosition, int childPosition) {
+            return true;
+        }
+    }
+
+    public static class BookmarkContextMenuInfo implements ContextMenuInfo {
+
+        private BookmarkContextMenuInfo(View targetView, int childPosition,
+                int groupPosition) {
+            this.targetView = targetView;
+            this.childPosition = childPosition;
+            this.groupPosition = groupPosition;
+        }
+
+        public View targetView;
+        public int childPosition;
+        public int groupPosition;
+    }
+
+}