Bug 1329131 - Part 2: Implement selecting bookmark folder page. r?Grisha,ahunt draft
authorJing-wei Wu <topwu.tw@gmail.com>
Mon, 24 Apr 2017 00:41:46 +0800
changeset 572743 7a058d7ff4dbbdd6e118fed0a7a7a195b07d194e
parent 572742 7befe17e1d4e18fc1a308b0bc79b1172df77c3cc
child 572745 eb505e2858246cfe79e5a1424dd59d65b364f71a
push id57174
push userbmo:topwu.tw@gmail.com
push dateThu, 04 May 2017 17:39:17 +0000
reviewersGrisha, ahunt
bugs1329131
milestone55.0a1
Bug 1329131 - Part 2: Implement selecting bookmark folder page. r?Grisha,ahunt MozReview-Commit-ID: 6uEC9iauvZj
mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
mobile/android/base/java/org/mozilla/gecko/bookmarks/SelectFolderCallback.java
mobile/android/base/java/org/mozilla/gecko/bookmarks/SelectFolderFragment.java
mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/moz.build
mobile/android/base/resources/drawable-hdpi/orange_check.png
mobile/android/base/resources/drawable-xhdpi/orange_check.png
mobile/android/base/resources/drawable-xxhdpi/orange_check.png
mobile/android/base/resources/layout/bookmark_folder_item.xml
mobile/android/base/resources/layout/bookmark_folder_select.xml
mobile/android/base/resources/values/colors.xml
mobile/android/base/resources/values/dimens.xml
mobile/android/base/strings.xml.in
--- a/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
@@ -33,17 +33,17 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserDB;
 
 import java.lang.ref.WeakReference;
 
 /**
  * A dialog fragment that allows editing bookmark's url, title and changing the parent."
  */
-public class BookmarkEditFragment extends DialogFragment {
+public class BookmarkEditFragment extends DialogFragment implements SelectFolderCallback {
 
     private static final String ARG_ID = "id";
     private static final String ARG_URL = "url";
     private static final String ARG_BOOKMARK = "bookmark";
 
     private long bookmarkId;
     private String url;
     private Bookmark bookmark;
@@ -156,16 +156,29 @@ public class BookmarkEditFragment extend
         });
         toolbar.setNavigationOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 dismiss();
             }
         });
 
+        folderText.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (bookmark == null) {
+                    return;
+                }
+
+                final SelectFolderFragment dialog = SelectFolderFragment.newInstance(bookmark.parentId, bookmark.id);
+                dialog.setTargetFragment(BookmarkEditFragment.this, 0);
+                dialog.show(getActivity().getSupportFragmentManager(), "select-bookmark-folder");
+            }
+        });
+
         return view;
     }
 
     @Override
     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
         if (savedInstanceState != null) {
             final Bookmark bookmark = savedInstanceState.getParcelable(ARG_BOOKMARK);
             if (bookmark != null) {
@@ -191,16 +204,28 @@ public class BookmarkEditFragment extend
             bookmark.title = nameText.getText().toString();
             bookmark.folder = folderText.getText().toString();
             outState.putParcelable(ARG_BOOKMARK, bookmark);
         }
 
         super.onSaveInstanceState(outState);
     }
 
+    @Override
+    public void onFolderChanged(long parentId, String title) {
+        if (bookmark == null) {
+            // Don't update view if bookmark isn't initialized yet.
+            return;
+        }
+
+        bookmark.parentId = parentId;
+        bookmark.folder = title;
+        invalidateView(bookmark);
+    }
+
     private void invalidateView(Bookmark bookmark) {
         this.bookmark = bookmark;
 
         nameText.setText(bookmark.title);
 
         if (bookmark.type == Bookmarks.TYPE_FOLDER) {
             locationLayout.setVisibility(View.GONE);
         } else {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/SelectFolderCallback.java
@@ -0,0 +1,10 @@
+/* -*- 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.bookmarks;
+
+interface SelectFolderCallback {
+    void onFolderChanged(long folderId, String title);
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/SelectFolderFragment.java
@@ -0,0 +1,434 @@
+/* -*- 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.bookmarks;
+
+import android.annotation.SuppressLint;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.AsyncTaskLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.widget.FadedSingleColorTextView;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A dialog fragment that allows selecting a bookmark folder.
+ */
+public class SelectFolderFragment extends DialogFragment {
+
+    private static final String ARG_PARENT_ID = "parentId";
+    private static final String ARG_BOOKMARK_ID = "bookmarkId";
+
+    private static final int LOADER_ID_FOLDERS = 0;
+
+    private long parentId;
+    private long bookmarkId;
+
+    private Toolbar toolbar;
+    private RecyclerView folderView;
+    private FolderListAdapter foldersAdapter;
+
+    private SelectFolderCallback callback;
+
+    public static SelectFolderFragment newInstance(long parentId, long bookmarkId) {
+        final Bundle args = new Bundle();
+        args.putLong(ARG_PARENT_ID, parentId);
+        args.putLong(ARG_BOOKMARK_ID, bookmarkId);
+
+        final SelectFolderFragment fragment = new SelectFolderFragment();
+        fragment.setArguments(args);
+
+        return fragment;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+
+        final Fragment fragment = getTargetFragment();
+        if (fragment != null && fragment instanceof SelectFolderCallback) {
+            callback = (SelectFolderCallback) fragment;
+        }
+    }
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setStyle(DialogFragment.STYLE_NO_TITLE, R.style.Bookmark_Gecko);
+
+        final Bundle args = getArguments();
+        if (args != null) {
+            parentId = args.getLong(ARG_PARENT_ID);
+            bookmarkId = args.getLong(ARG_BOOKMARK_ID);
+        }
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+                             @Nullable Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.bookmark_folder_select, container);
+        toolbar = (Toolbar) view.findViewById(R.id.toolbar);
+        folderView = (RecyclerView) view.findViewById(R.id.folder_recycler_view);
+
+        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                dismiss();
+            }
+        });
+
+        foldersAdapter = new FolderListAdapter();
+        folderView.setAdapter(foldersAdapter);
+
+        folderView.setHasFixedSize(true);
+
+        final LinearLayoutManager llm = new LinearLayoutManager(getContext());
+        folderView.setLayoutManager(llm);
+
+        return view;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        getLoaderManager().initLoader(LOADER_ID_FOLDERS, null, new FoldersLoaderCallbacks());
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+
+        getLoaderManager().destroyLoader(LOADER_ID_FOLDERS);
+    }
+
+    public void onFolderSelected(final Folder folder) {
+        // Ignore fake folders.
+        if (folder.id == BrowserContract.Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
+            return;
+        }
+
+        final String title;
+        if (BrowserContract.Bookmarks.MOBILE_FOLDER_GUID.equals(folder.guid)) {
+            title = getString(R.string.bookmarks_folder_mobile);
+        } else {
+            title = folder.title;
+        }
+
+        if (callback != null) {
+            callback.onFolderChanged(folder.id, title);
+        }
+        dismiss();
+    }
+
+    /**
+     * A private struct to make it easier to pass bookmark data across threads
+     */
+    private static class Folder {
+        private final long id;
+        private final String title;
+        private final String guid;
+
+        int paddingLevel = 0;
+
+        private Folder(long id, String title, String guid) {
+            this.id = id;
+            this.title = title;
+            this.guid = guid;
+        }
+    }
+
+    private class FoldersLoaderCallbacks implements LoaderManager.LoaderCallbacks<List<Folder>> {
+        @Override
+        public Loader<List<Folder>> onCreateLoader(int id, Bundle args) {
+            return new FoldersLoader(getContext(), bookmarkId);
+        }
+
+        @Override
+        public void onLoadFinished(Loader<List<Folder>> loader, final List<Folder> folders) {
+            if (folders == null) {
+                return;
+            }
+            foldersAdapter.addAll(folders);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<List<Folder>> loader) {
+        }
+    }
+
+    /**
+     * An AsyncTaskLoader to load {@link Folder} from a cursor.
+     */
+    private static class FoldersLoader extends AsyncTaskLoader<List<Folder>> {
+        private final ContentResolver cr;
+        private final BrowserDB db;
+        private long bookmarkId;
+        private List<Folder> folders;
+
+        private FoldersLoader(Context context, long bookmarkId) {
+            super(context);
+
+            cr = context.getContentResolver();
+            db = BrowserDB.from(context);
+            this.bookmarkId = bookmarkId;
+        }
+
+        @Override
+        public List<Folder> loadInBackground() {
+            // The data order in cursor is 'ASC TYPE, ASC POSITION, ASC ID'.
+            final Cursor cursor = db.getAllBookmarkFolders(cr);
+            if (cursor == null) {
+                return null;
+            }
+
+            final int capacity = cursor.getCount();
+
+            @SuppressLint("UseSparseArrays") final Map<Long, List<Folder>> folderMap = new HashMap<>(capacity);
+            try {
+                while (cursor.moveToNext()) {
+                    final long id = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID));
+                    final String title = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.TITLE));
+                    final String guid = cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.GUID));
+
+                    final long parentId;
+                    switch (guid) {
+                        case BrowserContract.Bookmarks.TOOLBAR_FOLDER_GUID:
+                        case BrowserContract.Bookmarks.UNFILED_FOLDER_GUID:
+                        case BrowserContract.Bookmarks.MENU_FOLDER_GUID:
+                            parentId = BrowserContract.Bookmarks.FAKE_DESKTOP_FOLDER_ID;
+                            break;
+
+                        case BrowserContract.Bookmarks.MOBILE_FOLDER_GUID:
+                        case BrowserContract.Bookmarks.FAKE_DESKTOP_FOLDER_GUID:
+                            parentId = BrowserContract.Bookmarks.FIXED_ROOT_ID;
+                            break;
+
+                        default:
+                            parentId = cursor.getLong(cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.PARENT));
+                            break;
+                    }
+
+                    List<Folder> childFolders = folderMap.get(parentId);
+                    if (childFolders == null) {
+                        childFolders = new ArrayList<>();
+                    }
+                    childFolders.add(new Folder(id, title, guid));
+
+                    folderMap.put(parentId, childFolders);
+                }
+            } finally {
+                cursor.close();
+            }
+
+            folders = flattenChildFolders(folderMap, capacity);
+            return folders;
+        }
+
+        private List<Folder> flattenChildFolders(Map<Long, List<Folder>> map, int capacity) {
+            final List<Folder> result = new ArrayList<>(capacity);
+
+            // Here we use ArrayDeque as a stack because it's considered faster than java.util.Stack.
+            // https://developer.android.com/reference/java/util/ArrayDeque.html
+            final ArrayDeque<Folder> folderQueue = new ArrayDeque<>(capacity);
+
+            // Add "mobile bookmarks" and "desktop bookmarks" into arrayDeque.
+            final List<Folder> first2Children = map.get((long) BrowserContract.Bookmarks.FIXED_ROOT_ID);
+            for (Folder child : first2Children) {
+                if (!BrowserContract.Bookmarks.MOBILE_FOLDER_GUID.equals(child.guid) &&
+                    !BrowserContract.Bookmarks.FAKE_DESKTOP_FOLDER_GUID.equals(child.guid)) {
+                    continue;
+                }
+                folderQueue.add(child);
+            }
+
+            while (!folderQueue.isEmpty()) {
+                final Folder folder = folderQueue.pop();
+                result.add(folder);
+
+                final List<Folder> children = map.get(folder.id);
+                if (children == null || children.size() == 0) {
+                    continue;
+                }
+                for (int i = children.size() - 1; i >= 0; --i) {
+                    final Folder child = children.get(i);
+
+                    // Ignore itself and descendant folders because we don't allow to select itself or its descendants.
+                    if (child.id == bookmarkId) {
+                        continue;
+                    }
+
+                    child.paddingLevel = folder.paddingLevel + 1;
+                    folderQueue.push(child);
+                }
+            }
+            return result;
+        }
+
+        @Override
+        public void deliverResult(List<Folder> folderList) {
+            if (isReset()) {
+                folders = null;
+                return;
+            }
+
+            folders = folderList;
+
+            if (isStarted()) {
+                super.deliverResult(folderList);
+            }
+        }
+
+        @Override
+        protected void onStartLoading() {
+            if (folders != null) {
+                deliverResult(folders);
+            }
+
+            if (takeContentChanged() || folders == null) {
+                forceLoad();
+            }
+        }
+
+        @Override
+        protected void onStopLoading() {
+            cancelLoad();
+        }
+
+        @Override
+        public void onCanceled(List<Folder> folderList) {
+            folders = null;
+        }
+
+        @Override
+        protected void onReset() {
+            super.onReset();
+
+            // Ensure the loader is stopped.
+            onStopLoading();
+
+            folders = null;
+        }
+    }
+
+    private class FolderListAdapter extends RecyclerView.Adapter<FolderListAdapter.ViewHolder> {
+        private final int firstStartPadding;
+        private final int startPadding;
+        private final List<Folder> folders;
+
+        private FolderListAdapter() {
+            final Resources res = getResources();
+            firstStartPadding = (int) res.getDimension(R.dimen.bookmark_folder_first_child_padding);
+            startPadding = (int) res.getDimension(R.dimen.bookmark_folder_child_padding);
+
+            folders = new ArrayList<>();
+        }
+
+        private void addAll(List<Folder> folderList) {
+            folders.clear();
+            folders.addAll(folderList);
+
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            final View itemView = LayoutInflater.from(parent.getContext())
+                                          .inflate(R.layout.bookmark_folder_item, parent, false);
+            return new ViewHolder(itemView);
+        }
+
+        @Override
+        public void onBindViewHolder(ViewHolder holder, int position) {
+            final Folder folder = folders.get(position);
+
+            if (BrowserContract.Bookmarks.MOBILE_FOLDER_GUID.equals(folder.guid)) {
+                holder.titleView.setText(R.string.bookmarks_folder_mobile);
+            } else if (folder.id == BrowserContract.Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
+                holder.titleView.setText(R.string.bookmarks_folder_desktop);
+            } else {
+                holder.titleView.setText(folder.title);
+            }
+
+            final int startPadding;
+            if (folder.paddingLevel == 0) {
+                startPadding = 0;
+            } else {
+                startPadding = firstStartPadding + (folder.paddingLevel - 1) * this.startPadding;
+            }
+
+            ViewCompat.setPaddingRelative(holder.container,
+                                          startPadding,
+                                          holder.container.getPaddingTop(),
+                                          ViewCompat.getPaddingEnd(holder.container),
+                                          holder.container.getPaddingBottom());
+
+            final boolean isSelected = (folder.id == parentId);
+            holder.selectView.setVisibility(isSelected ? View.VISIBLE : View.GONE);
+
+            if (folder.paddingLevel == 0) {
+                holder.divider.setVisibility(View.VISIBLE);
+                holder.itemView.setBackgroundResource(R.color.about_page_header_grey);
+            } else {
+                holder.divider.setVisibility(View.GONE);
+                holder.itemView.setBackgroundResource(R.color.bookmark_folder_bg_color);
+            }
+        }
+
+        @Override
+        public int getItemCount() {
+            return folders.size();
+        }
+
+        class ViewHolder extends RecyclerView.ViewHolder {
+            private final View divider;
+            private final View container;
+            private final FadedSingleColorTextView titleView;
+            private final ImageView selectView;
+
+            public ViewHolder(View itemView) {
+                super(itemView);
+
+                divider = itemView.findViewById(R.id.divider);
+                container = itemView.findViewById(R.id.container);
+                titleView = (FadedSingleColorTextView) itemView.findViewById(R.id.title);
+                selectView = (ImageView) itemView.findViewById(R.id.select);
+
+                itemView.setOnClickListener(new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        final Folder folder = folders.get(getAdapterPosition());
+                        onFolderSelected(folder);
+                    }
+                });
+            }
+        }
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BookmarksPanel.java
@@ -100,25 +100,31 @@ public class BookmarksPanel extends Home
         final View view = inflater.inflate(R.layout.home_bookmarks_panel, container, false);
 
         mList = (BookmarksListView) view.findViewById(R.id.bookmarks_list);
 
         mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
-                if (type == Bookmarks.TYPE_FOLDER) {
-                    // We don't show a context menu for folders
+                final boolean enableFullBookmarkManagement = BookmarkUtils.isEnabled(getContext());
+                if (!enableFullBookmarkManagement && type == Bookmarks.TYPE_FOLDER) {
+                    // We don't show a context menu for folders if full bookmark management isn't enabled.
                     return null;
                 }
                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
                 info.url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
                 info.title = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.TITLE));
                 info.bookmarkId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
                 info.itemType = RemoveItemType.BOOKMARKS;
+
+                if (type == Bookmarks.TYPE_FOLDER) {
+                    info.isFolder = true;
+                    info.url = "";
+                }
                 return info;
             }
         });
 
         return view;
     }
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeFragment.java
@@ -12,16 +12,17 @@ import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.bookmarks.BookmarkUtils;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.distribution.PartnerBookmarksProviderProxy;
 import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenInBackgroundListener;
 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
 import org.mozilla.gecko.home.TopSitesGridView.TopSitesGridContextMenuInfo;
 import org.mozilla.gecko.preferences.GeckoPreferences;
@@ -159,18 +160,19 @@ public abstract class HomeFragment exten
     @Override
     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
         if (!(menuInfo instanceof HomeContextMenuInfo)) {
             return;
         }
 
         HomeContextMenuInfo info = (HomeContextMenuInfo) menuInfo;
 
-        // Don't show the context menu for folders.
-        if (info.isFolder) {
+        // Don't show the context menu for folders if full bookmark management isn't enabled.
+        final boolean enableFullBookmarkManagement = BookmarkUtils.isEnabled(getContext());
+        if (info.isFolder && !enableFullBookmarkManagement) {
             return;
         }
 
         MenuInflater inflater = new MenuInflater(view.getContext());
         inflater.inflate(R.menu.home_contextmenu, menu);
 
         menu.setHeaderTitle(info.getDisplayTitle());
 
@@ -194,16 +196,26 @@ public abstract class HomeFragment exten
             menu.findItem(R.id.home_share).setVisible(false);
         }
 
         if (!Restrictions.isAllowed(view.getContext(), Restrictable.PRIVATE_BROWSING)) {
             menu.findItem(R.id.home_open_private_tab).setVisible(false);
         }
         final boolean distSetAsHomepage = GeckoSharedPrefs.forProfile(view.getContext()).getBoolean(GeckoPreferences.PREFS_SET_AS_HOMEPAGE, false);
         menu.findItem(R.id.home_set_as_homepage).setVisible(distSetAsHomepage);
+
+        // Hide unused menu items for bookmark folder.
+        if (info.isFolder) {
+            menu.findItem(R.id.home_open_new_tab).setVisible(false);
+            menu.findItem(R.id.home_open_private_tab).setVisible(false);
+            menu.findItem(R.id.home_copyurl).setVisible(false);
+            menu.findItem(R.id.home_share).setVisible(false);
+            menu.findItem(R.id.home_add_to_launcher).setVisible(false);
+            menu.findItem(R.id.home_set_as_homepage).setVisible(false);
+        }
     }
 
     @Override
     public boolean onContextItemSelected(MenuItem item) {
         // onContextItemSelected() is first dispatched to the activity and
         // then dispatched to its fragments. Since fragments cannot "override"
         // menu item selection handling, it's better to avoid menu id collisions
         // between the activity and its fragments.
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -542,16 +542,17 @@
      any page is removed frome about:home. This includes pages that are in history,
      bookmarks, or reading list. -->
 <!ENTITY page_removed "Page removed">
 
 <!ENTITY bookmark_edit_title "Edit Bookmark">
 <!ENTITY bookmark_edit_name "Name">
 <!ENTITY bookmark_edit_location "Location">
 <!ENTITY bookmark_edit_keyword "Keyword">
+<!ENTITY bookmark_select_folder "Select folder">
 
 <!-- Localization note (site_settings_*) : These strings are used in the "Site Settings"
      dialog that appears after selecting the "Edit Site Settings" context menu item. -->
 <!ENTITY site_settings_title3       "Site Settings">
 <!ENTITY site_settings_cancel       "Cancel">
 <!ENTITY site_settings_clear        "Clear">
 
 <!-- Localization note : These strings are used as alternate text for accessibility.
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -498,16 +498,18 @@ gbjar.sources += ['java/org/mozilla/geck
     'animation/HeightChangeAnimation.java',
     'animation/PropertyAnimator.java',
     'animation/Rotate3DAnimation.java',
     'animation/ViewHelper.java',
     'ANRReporter.java',
     'bookmarks/BookmarkEditFragment.java',
     'bookmarks/BookmarkUtils.java',
     'bookmarks/EditBookmarkTask.java',
+    'bookmarks/SelectFolderCallback.java',
+    'bookmarks/SelectFolderFragment.java',
     'BootReceiver.java',
     'BrowserApp.java',
     'BrowserLocaleManager.java',
     'cleanup/FileCleanupController.java',
     'cleanup/FileCleanupService.java',
     'CustomEditText.java',
     'customtabs/ActionBarPresenter.java',
     'customtabs/CustomTabsActivity.java',
new file mode 100644
index 0000000000000000000000000000000000000000..17b0b3c745be2ea9f7858f1a0483b31887749507
GIT binary patch
literal 20431
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
new file mode 100644
index 0000000000000000000000000000000000000000..2973a97beb4876edebe1a0a6bb6b5bef3239e6e7
GIT binary patch
literal 20509
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
HaK--s2d~Th
new file mode 100644
index 0000000000000000000000000000000000000000..a3cf4f75b296831838434be0af6ae5e495eff15a
GIT binary patch
literal 20637
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
S&GP^AVrS!MecW<I<o^J~PW9aY
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/bookmark_folder_item.xml
@@ -0,0 +1,71 @@
+<?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/. -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                xmlns:gecko="http://schemas.android.com/apk/res-auto"
+                xmlns:tools="http://schemas.android.com/tools"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+    <View
+        android:id="@+id/divider"
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="@color/toolbar_divider_grey"
+        android:visibility="gone"/>
+
+    <LinearLayout
+        android:id="@+id/container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:background="?attr/selectableItemBackground"
+        android:minHeight="@dimen/page_row_height"
+        android:orientation="horizontal">
+
+        <View
+            android:id="@+id/padding_start"
+            android:layout_width="@dimen/bookmark_folder_child_padding"
+            android:layout_height="1dp"/>
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:src="@drawable/folder_closed"/>
+
+        <org.mozilla.gecko.widget.FadedSingleColorTextView
+            android:id="@+id/title"
+            style="@style/Widget.FolderTitle.OneLine"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:layout_weight="1"
+            android:clickable="false"
+            android:maxLines="1"
+            android:paddingLeft="16dp"
+            android:paddingRight="16dp"
+            android:singleLine="true"
+            android:textColor="@color/text_and_tabs_tray_grey"
+            android:textSize="18sp"
+            gecko:fadeWidth="90dp"
+            tools:text="This is a long test title"/>
+
+        <ImageView
+            android:id="@+id/select"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:src="@drawable/orange_check"
+            android:visibility="gone"
+            tools:visibility="visible"/>
+
+        <View
+            android:id="@+id/padding_end"
+            android:layout_width="@dimen/bookmark_folder_child_padding"
+            android:layout_height="1dp"/>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/bookmark_folder_select.xml
@@ -0,0 +1,30 @@
+<?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/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:background="@android:color/white">
+
+    <android.support.v7.widget.Toolbar
+        android:id="@+id/toolbar"
+        android:layout_width="match_parent"
+        android:layout_height="?actionBarSize"
+        android:background="@color/text_and_tabs_tray_grey"
+        app:navigationIcon="@drawable/abc_ic_ab_back_mtrl_am_alpha"
+        app:subtitleTextColor="@android:color/white"
+        app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+        app:title="@string/bookmark_select_folder" />
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/folder_recycler_view"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        tools:listitem="@layout/bookmark_folder_item"/>
+</LinearLayout>
\ No newline at end of file
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -142,9 +142,10 @@
 
   <color name="activity_stream_divider">#FFD2D2D2</color>
   <color name="activity_stream_subtitle">#FF919191</color>
   <color name="activity_stream_timestamp">#FFD3D3D3</color>
   <color name="activity_stream_icon">#FF919191</color>
 
   <color name="tablet_tab_strip_divider_color">#555555</color>
 
+  <color name="bookmark_folder_bg_color">#FCFCFC</color>
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -230,9 +230,13 @@
     <item name="activity_stream_top_sites_text_height" type="dimen">30dp</item>
 
     <!-- Default touch target size for buttons/imageviews that might be of small size -->
     <item name="touch_target_size" type="dimen">48dp</item>
 
     <!-- Custom tabs -->
     <dimen name="custom_tab_action_button_size">56dp</dimen>
     <dimen name="custom_tab_action_button_padding">16dp</dimen>
+
+    <!-- Full bookmark management -->
+    <dimen name="bookmark_folder_first_child_padding">40dp</dimen>
+    <dimen name="bookmark_folder_child_padding">16dp</dimen>
 </resources>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -430,16 +430,17 @@
   <string name="pref_compact_tabs_summary">&pref_compact_tabs_summary2;</string>
 
   <string name="page_removed">&page_removed;</string>
 
   <string name="bookmark_edit_title">&bookmark_edit_title;</string>
   <string name="bookmark_edit_name">&bookmark_edit_name;</string>
   <string name="bookmark_edit_location">&bookmark_edit_location;</string>
   <string name="bookmark_edit_keyword">&bookmark_edit_keyword;</string>
+  <string name="bookmark_select_folder">&bookmark_select_folder;</string>
 
   <string name="pref_use_master_password">&pref_use_master_password;</string>
   <string name="masterpassword_create_title">&masterpassword_create_title;</string>
   <string name="masterpassword_remove_title">&masterpassword_remove_title;</string>
   <string name="masterpassword_password">&masterpassword_password;</string>
   <string name="masterpassword_confirm">&masterpassword_confirm;</string>
 
   <string name="button_ok">&button_ok;</string>