| Index: chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkManager.java
 | 
| diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkManager.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkManager.java
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..62950c4d704c274cf93a86ba98a48b55471e3de3
 | 
| --- /dev/null
 | 
| +++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkManager.java
 | 
| @@ -0,0 +1,648 @@
 | 
| +// Copyright 2015 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +package org.chromium.chrome.browser.enhancedbookmarks;
 | 
| +
 | 
| +import android.annotation.TargetApi;
 | 
| +import android.app.Activity;
 | 
| +import android.app.ActivityOptions;
 | 
| +import android.content.Intent;
 | 
| +import android.os.Build;
 | 
| +import android.preference.PreferenceManager;
 | 
| +import android.support.v4.widget.DrawerLayout;
 | 
| +import android.util.Log;
 | 
| +import android.view.Gravity;
 | 
| +import android.view.View;
 | 
| +import android.view.ViewGroup;
 | 
| +import android.widget.ViewSwitcher;
 | 
| +
 | 
| +import com.google.android.apps.chrome.R;
 | 
| +
 | 
| +import org.chromium.base.ObserverList;
 | 
| +import org.chromium.base.metrics.RecordHistogram;
 | 
| +import org.chromium.chrome.browser.BookmarksBridge.BookmarkItem;
 | 
| +import org.chromium.chrome.browser.BookmarksBridge.BookmarkModelObserver;
 | 
| +import org.chromium.chrome.browser.UrlConstants;
 | 
| +import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksBridge.FiltersObserver;
 | 
| +import org.chromium.chrome.browser.enhanced_bookmarks.EnhancedBookmarksModel;
 | 
| +import org.chromium.chrome.browser.enhanced_bookmarks.LaunchLocation;
 | 
| +import org.chromium.chrome.browser.ntp.NewTabPageUma;
 | 
| +import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksShim;
 | 
| +import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarManageable;
 | 
| +import org.chromium.components.bookmarks.BookmarkId;
 | 
| +import org.chromium.ui.base.DeviceFormFactor;
 | 
| +
 | 
| +import java.io.UnsupportedEncodingException;
 | 
| +import java.net.URLDecoder;
 | 
| +import java.net.URLEncoder;
 | 
| +import java.util.ArrayList;
 | 
| +import java.util.HashSet;
 | 
| +import java.util.List;
 | 
| +import java.util.Set;
 | 
| +import java.util.Stack;
 | 
| +
 | 
| +/**
 | 
| + * The new bookmark manager that is planned to replace the existing bookmark manager. It holds all
 | 
| + * views and shared logics between tablet and phone. For tablet/phone specific logics, see
 | 
| + * {@link EnhancedBookmarkActivity} (phone) and {@link EnhancedBookmarkPage} (tablet).
 | 
| + */
 | 
| +public class EnhancedBookmarkManager implements EnhancedBookmarkDelegate {
 | 
| +    private static final String PREF_LAST_USED_URL = "enhanced_bookmark_last_used_url";
 | 
| +    static final String PREF_WAS_IN_LIST_MODE = "enhanced_bookmark_list_mode_choice";
 | 
| +    // TODO(ianwen): upstream these metrics upstream.
 | 
| +    // UI modes for bookmarks presentation. Default option is grid mode.
 | 
| +    static final int DEFAULT_MODE = 0;
 | 
| +    static final int LIST_MODE = 1;
 | 
| +    static final int GRID_MODE = 2;
 | 
| +
 | 
| +    private Activity mActivity;
 | 
| +    private ViewGroup mMainView;
 | 
| +    private EnhancedBookmarksModel mEnhancedBookmarksModel;
 | 
| +    private EnhancedBookmarkUndoController mUndoController;
 | 
| +    private final ObserverList<EnhancedBookmarkUIObserver> mUIObservers =
 | 
| +            new ObserverList<EnhancedBookmarkUIObserver>();
 | 
| +    private Set<BookmarkId> mSelectedBookmarks = new HashSet<>();
 | 
| +    private boolean mListModeEnabled;
 | 
| +    private EnhancedBookmarkStateChangeListener mUrlChangeListener;
 | 
| +    private EnhancedBookmarkContentView mContentView;
 | 
| +    private EnhancedBookmarkSearchView mSearchView;
 | 
| +    private ViewSwitcher mViewSwitcher;
 | 
| +    private DrawerLayout mDrawer;
 | 
| +    private EnhancedBookmarkDrawerListView mDrawerListView;
 | 
| +    private final Stack<UIState> mStateStack = new Stack<>();
 | 
| +
 | 
| +    private final BookmarkModelObserver mBookmarkModelObserver = new BookmarkModelObserver() {
 | 
| +        @Override
 | 
| +        public void bookmarkNodeRemoved(BookmarkItem parent, int oldIndex, BookmarkItem node) {
 | 
| +            // If the folder is removed in folder mode, show the parent folder or falls back to all
 | 
| +            // bookmarks mode.
 | 
| +            if (getCurrentState() == STATE_FOLDER
 | 
| +                    && node.getId().equals(mStateStack.peek().mFolder)) {
 | 
| +                if (mEnhancedBookmarksModel.getTopLevelFolderIDs(true, true).contains(
 | 
| +                        node.getId())) {
 | 
| +                    openAllBookmarks();
 | 
| +                } else {
 | 
| +                    openFolder(parent.getId());
 | 
| +                }
 | 
| +            }
 | 
| +            clearSelection();
 | 
| +        }
 | 
| +
 | 
| +        @Override
 | 
| +        public void bookmarkNodeMoved(BookmarkItem oldParent, int oldIndex, BookmarkItem newParent,
 | 
| +                int newIndex) {
 | 
| +            clearSelection();
 | 
| +        }
 | 
| +
 | 
| +        @Override
 | 
| +        public void bookmarkModelLoaded() {
 | 
| +            initializeIfBookmarkModelLoaded();
 | 
| +        }
 | 
| +
 | 
| +        @Override
 | 
| +        public void bookmarkModelChanged() {
 | 
| +            // If the folder no longer exists in folder mode, we need to fall back. Relying on the
 | 
| +            // default behavior by setting the folder mode again.
 | 
| +            if (getCurrentState() == STATE_FOLDER) {
 | 
| +                setState(mStateStack.peek());
 | 
| +            }
 | 
| +            clearSelection();
 | 
| +        }
 | 
| +    };
 | 
| +
 | 
| +    private final FiltersObserver mFiltersObserver = new FiltersObserver() {
 | 
| +        @Override
 | 
| +        public void onFiltersChanged() {
 | 
| +            // if the current selected filter was removed, we need to fall back. Relying on the
 | 
| +            // default behavior by setting the filter mode again.
 | 
| +            if (getCurrentState() == STATE_FILTER) {
 | 
| +                setState(mStateStack.peek());
 | 
| +            }
 | 
| +        }
 | 
| +    };
 | 
| +
 | 
| +    /**
 | 
| +     * Creates an instance of {@link EnhancedBookmarkManager}. It also initializes resources,
 | 
| +     * bookmark models and jni bridges.
 | 
| +     * @param activity The activity context to use.
 | 
| +     */
 | 
| +    public EnhancedBookmarkManager(Activity activity) {
 | 
| +        mActivity = activity;
 | 
| +        mEnhancedBookmarksModel = new EnhancedBookmarksModel();
 | 
| +        mMainView = (ViewGroup) mActivity.getLayoutInflater().inflate(R.layout.eb_main, null);
 | 
| +        mDrawer = (DrawerLayout) mMainView.findViewById(R.id.eb_drawer_layout);
 | 
| +        mDrawerListView = (EnhancedBookmarkDrawerListView) mMainView.findViewById(
 | 
| +                R.id.eb_drawer_list);
 | 
| +        mContentView = (EnhancedBookmarkContentView) mMainView.findViewById(R.id.eb_content_view);
 | 
| +        mViewSwitcher = (ViewSwitcher) mMainView.findViewById(R.id.eb_view_switcher);
 | 
| +        mUndoController = new EnhancedBookmarkUndoController(activity, mEnhancedBookmarksModel,
 | 
| +                ((SnackbarManageable) activity).getSnackbarManager());
 | 
| +        mSearchView = (EnhancedBookmarkSearchView) getView().findViewById(R.id.eb_search_view);
 | 
| +        mEnhancedBookmarksModel.addModelObserver(mBookmarkModelObserver);
 | 
| +        initializeIfBookmarkModelLoaded();
 | 
| +
 | 
| +        // Load partner bookmarks explicitly. We load partner bookmarks in the deferred startup
 | 
| +        // code, but that might be executed much later. Especially on L, showing loading
 | 
| +        // progress bar blocks that so it won't be loaded. http://crbug.com/429383
 | 
| +        PartnerBookmarksShim.kickOffReading(activity);
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Destroys and cleans up itself. This must be called after done using this class.
 | 
| +     */
 | 
| +    public void destroy() {
 | 
| +        for (EnhancedBookmarkUIObserver observer : mUIObservers) {
 | 
| +            observer.onDestroy();
 | 
| +        }
 | 
| +        assert mUIObservers.size() == 0;
 | 
| +
 | 
| +        if (mUndoController != null) {
 | 
| +            mUndoController.destroy();
 | 
| +            mUndoController = null;
 | 
| +        }
 | 
| +        mEnhancedBookmarksModel.removeModelObserver(mBookmarkModelObserver);
 | 
| +        mEnhancedBookmarksModel.removeFiltersObserver(mFiltersObserver);
 | 
| +        mEnhancedBookmarksModel.destroy();
 | 
| +        mEnhancedBookmarksModel = null;
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Called when the user presses the back key. This is only going to be called on Phone.
 | 
| +     * @return True if manager handles this event, false if it decides to ignore.
 | 
| +     */
 | 
| +    public boolean onBackPressed() {
 | 
| +        if (doesDrawerExist()) {
 | 
| +            if (mDrawer.isDrawerVisible(Gravity.START)) {
 | 
| +                mDrawer.closeDrawer(Gravity.START);
 | 
| +                return true;
 | 
| +            }
 | 
| +        }
 | 
| +
 | 
| +        if (mContentView.onBackPressed()) return true;
 | 
| +
 | 
| +        if (!mStateStack.empty()) {
 | 
| +            mStateStack.pop();
 | 
| +            if (!mStateStack.empty()) {
 | 
| +                setState(mStateStack.pop());
 | 
| +                return true;
 | 
| +            }
 | 
| +        }
 | 
| +        return false;
 | 
| +    }
 | 
| +
 | 
| +    public View getView() {
 | 
| +        return mMainView;
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Sets the listener that reacts upon the change of the UI state of bookmark manager.
 | 
| +     */
 | 
| +    public void setUrlChangeListener(EnhancedBookmarkStateChangeListener urlListner) {
 | 
| +        mUrlChangeListener = urlListner;
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * @return Current URL representing the UI state of bookmark manager. If no state has been shown
 | 
| +     *         yet in this session, on phone return last used state stored in preference; on tablet
 | 
| +     *         return the url previously set by {@link #updateForUrl(String)}.
 | 
| +     */
 | 
| +    public String getCurrentUrl() {
 | 
| +        if (mStateStack.isEmpty()) return null;
 | 
| +        return mStateStack.peek().mUrl;
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Updates UI based on the new URL on tablet. If the bookmark model is not loaded yet, creates a
 | 
| +     * temporary loading state carrying this url. This method is supposed to align with
 | 
| +     * {@link EnhancedBookmarkPage#updateForUrl(String)}
 | 
| +     * <p>
 | 
| +     * @param url The url to navigate to.
 | 
| +     */
 | 
| +    public void updateForUrl(String url) {
 | 
| +        if (mEnhancedBookmarksModel != null && mEnhancedBookmarksModel.isBookmarkModelLoaded()) {
 | 
| +            setState(UIState.createStateFromUrl(url, mEnhancedBookmarksModel));
 | 
| +        } else {
 | 
| +            // Note this does not guarantee to update the UI, as at this time the onCreateView()
 | 
| +            // might not has even been called yet.
 | 
| +            setState(UIState.createLoadingState(url));
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Initialization method that has 3 different behaviors based on whether bookmark model is
 | 
| +     * loaded. If the bookmark model is not loaded yet, it pushes a loading state to backstack which
 | 
| +     * contains the url from preference. If the model is loaded and the backstack is empty, it
 | 
| +     * creates a state by fetching the last visited bookmark url stored in preference. If the
 | 
| +     * bookmark model is loaded but backstack contains a pending loading state, it creates a new
 | 
| +     * state by getting the url of the loading state and replace the previous loading state with the
 | 
| +     * new normal state.
 | 
| +     */
 | 
| +    private void initializeIfBookmarkModelLoaded() {
 | 
| +        if (mEnhancedBookmarksModel.isBookmarkModelLoaded()) {
 | 
| +            mEnhancedBookmarksModel.addFiltersObserver(mFiltersObserver);
 | 
| +            mSearchView.onEnhancedBookmarkDelegateInitialized(this);
 | 
| +            mDrawerListView.onEnhancedBookmarkDelegateInitialized(this);
 | 
| +            mContentView.onEnhancedBookmarkDelegateInitialized(this);
 | 
| +            if (mStateStack.isEmpty()) {
 | 
| +                setState(UIState.createStateFromUrl(getUrlFromPreference(),
 | 
| +                        mEnhancedBookmarksModel));
 | 
| +            } else if (mStateStack.peek().mState == STATE_LOADING) {
 | 
| +                String url = mStateStack.pop().mUrl;
 | 
| +                setState(UIState.createStateFromUrl(url, mEnhancedBookmarksModel));
 | 
| +            }
 | 
| +            // Restore the previous view mode selection saved in preference.
 | 
| +            initListModeOptionTo(getListModePreference());
 | 
| +        } else {
 | 
| +            mContentView.showLoadingUi();
 | 
| +            mDrawerListView.showLoadingUi();
 | 
| +            mContentView.showLoadingUi();
 | 
| +            if (mStateStack.isEmpty() || mStateStack.peek().mState != STATE_LOADING) {
 | 
| +                setState(UIState.createLoadingState(getUrlFromPreference()));
 | 
| +            } else if (!mStateStack.isEmpty()) {
 | 
| +                // Refresh the UI. This is needed because on tablet, updateForUrl might set up
 | 
| +                // loading state too early and at that time all UI components are not created yet.
 | 
| +                // Therefore we need to set the previous loading state once again to trigger all UI
 | 
| +                // updates.
 | 
| +                setState(mStateStack.pop());
 | 
| +            }
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Saves url to preference. Note this method should be used after the main view is attached to
 | 
| +     * an activity.
 | 
| +     */
 | 
| +    private void saveUrlToPreference(String url) {
 | 
| +        PreferenceManager.getDefaultSharedPreferences(mActivity).edit()
 | 
| +                .putString(PREF_LAST_USED_URL, url).apply();
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Fetches url to preference. Note this method should be used after the main view is attached to
 | 
| +     * an activity.
 | 
| +     */
 | 
| +    private String getUrlFromPreference() {
 | 
| +        return PreferenceManager.getDefaultSharedPreferences(mActivity).getString(
 | 
| +                PREF_LAST_USED_URL, UrlConstants.BOOKMARKS_URL);
 | 
| +    }
 | 
| +
 | 
| +    private void saveListModePreference() {
 | 
| +        PreferenceManager.getDefaultSharedPreferences(mActivity).edit()
 | 
| +                .putInt(PREF_WAS_IN_LIST_MODE, mListModeEnabled ? LIST_MODE : GRID_MODE).apply();
 | 
| +    }
 | 
| +
 | 
| +    private boolean getListModePreference() {
 | 
| +        int mode = PreferenceManager.getDefaultSharedPreferences(mActivity).getInt(
 | 
| +                PREF_WAS_IN_LIST_MODE, DEFAULT_MODE);
 | 
| +        return mode == LIST_MODE ? true : false;
 | 
| +    }
 | 
| +
 | 
| +    private void initListModeOptionTo(boolean isListModeEnabled) {
 | 
| +        mListModeEnabled = isListModeEnabled;
 | 
| +        for (EnhancedBookmarkUIObserver observer: mUIObservers) {
 | 
| +            observer.onListModeChange(isListModeEnabled);
 | 
| +        }
 | 
| +        // Every time the enhanced bookmark manager launches or the user clicks the list-mode
 | 
| +        // toggle, we record the list view state.
 | 
| +        int listViewstate = PreferenceManager.getDefaultSharedPreferences(getView().getContext())
 | 
| +                .getInt(EnhancedBookmarkManager.PREF_WAS_IN_LIST_MODE,
 | 
| +                        EnhancedBookmarkManager.DEFAULT_MODE);
 | 
| +        RecordHistogram.recordEnumeratedHistogram("EnhancedBookmarks.ViewMode", listViewstate, 3);
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * This is the ultimate internal method that updates UI and controls backstack. And it is the
 | 
| +     * only method that pushes states to {@link #mStateStack}.
 | 
| +     * <p>
 | 
| +     * If the given state is not valid, all_bookmark state will be shown. Afterwards, this method
 | 
| +     * checks the current state: if currently in loading state, it pops it out and adds the new
 | 
| +     * state to the back stack. It also notifies the {@link #mUrlChangeListener} (if any) that the
 | 
| +     * url has changed.
 | 
| +     * <p>
 | 
| +     * Also note that even if we store states to {@link #mStateStack}, on tablet the back navigation
 | 
| +     * and back button are not controlled by the manager: the tab handles back key and backstack
 | 
| +     * navigation.
 | 
| +     */
 | 
| +    private void setState(UIState state) {
 | 
| +        if (!state.isValid(mEnhancedBookmarksModel)) {
 | 
| +            state = UIState.createAllBookmarksState(mEnhancedBookmarksModel);
 | 
| +        }
 | 
| +        if (!mStateStack.isEmpty()) {
 | 
| +            if (mStateStack.peek().equals(state)) return;
 | 
| +            if (mStateStack.peek().mState == STATE_LOADING) {
 | 
| +                mStateStack.pop();
 | 
| +            }
 | 
| +        }
 | 
| +        mStateStack.push(state);
 | 
| +        if (state.mState != STATE_LOADING) {
 | 
| +            // Loading state may be pushed to the stack but should never be stored in preferences.
 | 
| +            saveUrlToPreference(state.mUrl);
 | 
| +            // If a loading state is replaced by another loading state, do not notify this change.
 | 
| +            if (mUrlChangeListener != null) mUrlChangeListener.onBookmarkUIStateChange(state.mUrl);
 | 
| +        }
 | 
| +
 | 
| +        clearSelection();
 | 
| +
 | 
| +        for (EnhancedBookmarkUIObserver observer : mUIObservers) {
 | 
| +            notifyStateChange(observer);
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    // EnhancedBookmarkDelegate implementations.
 | 
| +
 | 
| +    @Override
 | 
| +    public void openFolder(BookmarkId folder) {
 | 
| +        closeSearchUI();
 | 
| +        setState(UIState.createFolderState(folder, mEnhancedBookmarksModel));
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void openFilter(String filter) {
 | 
| +        closeSearchUI();
 | 
| +        setState(UIState.createFilterState(filter, mEnhancedBookmarksModel));
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void openAllBookmarks() {
 | 
| +        closeSearchUI();
 | 
| +        setState(UIState.createAllBookmarksState(mEnhancedBookmarksModel));
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void clearSelection() {
 | 
| +        mSelectedBookmarks.clear();
 | 
| +        for (EnhancedBookmarkUIObserver observer : mUIObservers) {
 | 
| +            observer.onSelectionStateChange(new ArrayList<BookmarkId>(mSelectedBookmarks));
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public boolean toggleSelectionForBookmark(BookmarkId bookmark) {
 | 
| +        if (!mEnhancedBookmarksModel.getBookmarkById(bookmark).isEditable()) return false;
 | 
| +
 | 
| +        if (mSelectedBookmarks.contains(bookmark)) mSelectedBookmarks.remove(bookmark);
 | 
| +        else mSelectedBookmarks.add(bookmark);
 | 
| +        for (EnhancedBookmarkUIObserver observer : mUIObservers) {
 | 
| +            observer.onSelectionStateChange(new ArrayList<BookmarkId>(mSelectedBookmarks));
 | 
| +        }
 | 
| +
 | 
| +        return isBookmarkSelected(bookmark);
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public boolean isBookmarkSelected(BookmarkId bookmark) {
 | 
| +        return mSelectedBookmarks.contains(bookmark);
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public boolean isSelectionEnabled() {
 | 
| +        return !mSelectedBookmarks.isEmpty();
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public List<BookmarkId> getSelectedBookmarks() {
 | 
| +        return new ArrayList<BookmarkId>(mSelectedBookmarks);
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void setListModeEnabled(boolean isListModeEnabled) {
 | 
| +        initListModeOptionTo(isListModeEnabled);
 | 
| +        saveListModePreference();
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public boolean isListModeEnabled() {
 | 
| +        return mListModeEnabled;
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void notifyStateChange(EnhancedBookmarkUIObserver observer) {
 | 
| +        int state = getCurrentState();
 | 
| +        switch (state) {
 | 
| +            case STATE_ALL_BOOKMARKS:
 | 
| +                observer.onAllBookmarksStateSet();
 | 
| +                break;
 | 
| +            case STATE_FOLDER:
 | 
| +                observer.onFolderStateSet(mStateStack.peek().mFolder);
 | 
| +                break;
 | 
| +            case STATE_FILTER:
 | 
| +                observer.onFilterStateSet(mStateStack.peek().mFilter);
 | 
| +                break;
 | 
| +            case STATE_LOADING:
 | 
| +                // In loading state, onEnhancedBookmarkDelegateInitialized() is not called for all
 | 
| +                // UIObservers, which means that there will be no observers at the time. Do nothing.
 | 
| +                assert mUIObservers.isEmpty();
 | 
| +                break;
 | 
| +            default:
 | 
| +                assert false : "State not valid";
 | 
| +                break;
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public boolean doesDrawerExist() {
 | 
| +        return mDrawer != null;
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void closeDrawer() {
 | 
| +        if (!doesDrawerExist()) return;
 | 
| +
 | 
| +        mDrawer.closeDrawer(Gravity.START);
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public DrawerLayout getDrawerLayout() {
 | 
| +        return mDrawer;
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 | 
| +    public void startDetailActivity(BookmarkId bookmarkId, View view) {
 | 
| +        Intent intent = new Intent(mActivity, EnhancedBookmarkDetailActivity.class);
 | 
| +        intent.putExtra(EnhancedBookmarkDetailActivity.INTENT_BOOKMARK_ID, bookmarkId.toString());
 | 
| +        // Shared element animation is disabled on tablet because of bad quality.
 | 
| +        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || view == null
 | 
| +                || DeviceFormFactor.isTablet(mActivity)) {
 | 
| +            mActivity.startActivity(intent);
 | 
| +        } else {
 | 
| +            ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(mActivity, view,
 | 
| +                    mActivity.getString(R.string.enhanced_bookmark_detail_transition_name));
 | 
| +            mActivity.startActivity(intent, options.toBundle());
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void openBookmark(BookmarkId bookmark, int launchLocation) {
 | 
| +        clearSelection();
 | 
| +        NewTabPageUma.recordAction(NewTabPageUma.ACTION_OPENED_BOOKMARK);
 | 
| +        RecordHistogram.recordEnumeratedHistogram("Stars.LaunchLocation", launchLocation,
 | 
| +                LaunchLocation.COUNT);
 | 
| +        EnhancedBookmarkUtils.openBookmark(mActivity,
 | 
| +                mEnhancedBookmarksModel.getBookmarkById(bookmark).getUrl());
 | 
| +        finishActivityOnPhone();
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void openSearchUI() {
 | 
| +        // Give search view focus, because it needs to handle back key event.
 | 
| +        mViewSwitcher.showNext();
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void closeSearchUI() {
 | 
| +        if (mSearchView.getVisibility() != View.VISIBLE) return;
 | 
| +        mViewSwitcher.showPrevious();
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void finishActivityOnPhone() {
 | 
| +        Activity activity = mActivity;
 | 
| +        if (activity instanceof EnhancedBookmarkActivity) {
 | 
| +            activity.finish();
 | 
| +        }
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void addUIObserver(EnhancedBookmarkUIObserver observer) {
 | 
| +        mUIObservers.addObserver(observer);
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public void removeUIObserver(EnhancedBookmarkUIObserver observer) {
 | 
| +        mUIObservers.removeObserver(observer);
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public EnhancedBookmarksModel getModel() {
 | 
| +        return mEnhancedBookmarksModel;
 | 
| +    }
 | 
| +
 | 
| +    @Override
 | 
| +    public int getCurrentState() {
 | 
| +        if (mStateStack.isEmpty()) return STATE_LOADING;
 | 
| +        return mStateStack.peek().mState;
 | 
| +    }
 | 
| +
 | 
| +    /**
 | 
| +     * Internal state that represents a url. Note every state needs to have a _valid_ url. For
 | 
| +     * loading state, {@link #mUrl} indicates the target to open after the bookmark model is loaded.
 | 
| +     */
 | 
| +    private static class UIState {
 | 
| +        private static final String TAG = "UIState";
 | 
| +        private static final String URL_CHARSET = "UTF-8";
 | 
| +        /**
 | 
| +         * One of the four states:
 | 
| +         * {@link EnhancedBookmarkDelegate#STATE_ALL_BOOKMARKS},
 | 
| +         * {@link EnhancedBookmarkDelegate#STATE_FILTER},
 | 
| +         * {@link EnhancedBookmarkDelegate#STATE_FOLDER},
 | 
| +         * {@link EnhancedBookmarkDelegate#STATE_LOADING},
 | 
| +         */
 | 
| +        int mState;
 | 
| +        String mUrl;
 | 
| +        BookmarkId mFolder;
 | 
| +        String mFilter;
 | 
| +
 | 
| +        static UIState createLoadingState(String url) {
 | 
| +            UIState state = new UIState();
 | 
| +            state.mUrl = url;
 | 
| +            state.mState = STATE_LOADING;
 | 
| +            return state;
 | 
| +        }
 | 
| +
 | 
| +        static UIState createAllBookmarksState(EnhancedBookmarksModel bookmarkModel) {
 | 
| +            return createStateFromUrl(UrlConstants.BOOKMARKS_URL, bookmarkModel);
 | 
| +        }
 | 
| +
 | 
| +        static UIState createFolderState(BookmarkId folder, EnhancedBookmarksModel bookmarkModel) {
 | 
| +            return createStateFromUrl(UrlConstants.BOOKMARKS_FOLDER_URL + folder.toString(),
 | 
| +                    bookmarkModel);
 | 
| +        }
 | 
| +
 | 
| +        static UIState createFilterState(String filter, EnhancedBookmarksModel bookmarkModel) {
 | 
| +            return createStateFromUrl(encodeUrl(UrlConstants.BOOKMARKS_FILTER_URL, filter),
 | 
| +                    bookmarkModel);
 | 
| +        }
 | 
| +
 | 
| +        /**
 | 
| +         * @return A state corresponding to the url. If the url is not valid, return all_bookmarks.
 | 
| +         */
 | 
| +        static UIState createStateFromUrl(String url, EnhancedBookmarksModel bookmarkModel) {
 | 
| +            UIState state = new UIState();
 | 
| +            state.mUrl = url;
 | 
| +            if (url.equals(UrlConstants.BOOKMARKS_URL)) {
 | 
| +                state.mState = STATE_ALL_BOOKMARKS;
 | 
| +            } else if (url.startsWith(UrlConstants.BOOKMARKS_FILTER_URL)) {
 | 
| +                String suffix = decodeSuffix(url, UrlConstants.BOOKMARKS_FILTER_URL);
 | 
| +                if (!suffix.isEmpty()) {
 | 
| +                    state.mState = STATE_FILTER;
 | 
| +                    state.mFilter = suffix;
 | 
| +                }
 | 
| +            } else if (url.startsWith(UrlConstants.BOOKMARKS_FOLDER_URL)) {
 | 
| +                String suffix = decodeSuffix(url, UrlConstants.BOOKMARKS_FOLDER_URL);
 | 
| +                if (!suffix.isEmpty()) {
 | 
| +                    state.mFolder = BookmarkId.getBookmarkIdFromString(suffix);
 | 
| +                    state.mState = STATE_FOLDER;
 | 
| +                }
 | 
| +            }
 | 
| +
 | 
| +            if (!state.isValid(bookmarkModel)) {
 | 
| +                state.mState = STATE_ALL_BOOKMARKS;
 | 
| +                state.mUrl = UrlConstants.BOOKMARKS_URL;
 | 
| +            }
 | 
| +
 | 
| +            return state;
 | 
| +        }
 | 
| +
 | 
| +        /**
 | 
| +         * @return Whether this state is valid.
 | 
| +         */
 | 
| +        boolean isValid(EnhancedBookmarksModel bookmarkModel) {
 | 
| +            if (mUrl == null) return false;
 | 
| +            if (mState == STATE_FOLDER) {
 | 
| +                if (mFolder == null) return false;
 | 
| +
 | 
| +                return bookmarkModel.doesBookmarkExist(mFolder)
 | 
| +                        && !mFolder.equals(bookmarkModel.getRootFolderId());
 | 
| +            }
 | 
| +            if (mState == STATE_FILTER) {
 | 
| +                if (mFilter == null) return false;
 | 
| +                else return bookmarkModel.getFilters().contains(mFilter);
 | 
| +            }
 | 
| +
 | 
| +            return true;
 | 
| +        }
 | 
| +
 | 
| +        static String decodeSuffix(String url, String prefix) {
 | 
| +            String suffix = url.substring(prefix.length());
 | 
| +            try {
 | 
| +                suffix = URLDecoder.decode(suffix, URL_CHARSET);
 | 
| +            } catch (UnsupportedEncodingException e) {
 | 
| +                Log.w(TAG, "Bookmark URL parsing failed. " + URL_CHARSET + " not supported.");
 | 
| +            }
 | 
| +            return suffix;
 | 
| +        }
 | 
| +
 | 
| +        static String encodeUrl(String prefix, String suffix) {
 | 
| +            try {
 | 
| +                suffix = URLEncoder.encode(suffix, URL_CHARSET);
 | 
| +            } catch (UnsupportedEncodingException e) {
 | 
| +                Log.w(TAG, "Bookmark URL parsing failed. " + URL_CHARSET + " not supported.");
 | 
| +            }
 | 
| +            return prefix + suffix;
 | 
| +        }
 | 
| +
 | 
| +        @Override
 | 
| +        public int hashCode() {
 | 
| +            return 31 * mUrl.hashCode() + mState;
 | 
| +        }
 | 
| +
 | 
| +        @Override
 | 
| +        public boolean equals(Object obj) {
 | 
| +            if (!(obj instanceof UIState)) return false;
 | 
| +            UIState other = (UIState) obj;
 | 
| +            return mState == other.mState && mUrl.equals(other.mUrl);
 | 
| +        }
 | 
| +    }
 | 
| +}
 | 
| 
 |