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); |
+ } |
+ } |
+} |