Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java
new file mode 100644
index 0000000000000000000000000000000000000000..a150f447b5a75b7ec31cd5da483ecc7c7a80f67b
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/enhancedbookmarks/EnhancedBookmarkSearchView.java
@@ -0,0 +1,496 @@
+// 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.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.TextUtils.TruncateAt;
+import android.text.TextWatcher;
+import android.text.style.StyleSpan;
+import android.util.AttributeSet;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.ViewSwitcher;
+
+import com.google.android.apps.chrome.R;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.browser.BookmarksBridge.BookmarkItem;
+import org.chromium.chrome.browser.BookmarksBridge.BookmarkModelObserver;
+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.enhancedbookmarks.EnhancedBookmarkSalientImageView.SalientImageDrawableFactory;
+import org.chromium.chrome.browser.widget.CustomShapeDrawable.CircularDrawable;
+import org.chromium.components.bookmarks.BookmarkId;
+import org.chromium.components.bookmarks.BookmarkMatch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * UI implementation for search in enhanced bookmark. Search results will be updated when user is
+ * typing. Before typing, a list of search history is shown.
+ */
+public class EnhancedBookmarkSearchView extends LinearLayout implements View.OnClickListener,
+ OnItemClickListener, FiltersObserver, EnhancedBookmarkUIObserver {
+
+ private static enum UIState {HISTORY, RESULT, EMPTY}
+ private static final int HISTORY_ITEM_PADDING_START_DP = 72;
+ private static final int MAXIMUM_NUMBER_OF_RESULTS = 500;
+ private static final String HORIZONTAL_ELLIPSIS = "\u2026";
+ private EnhancedBookmarksModel mEnhancedBookmarksModel;
+ private EnhancedBookmarkDelegate mDelegate;
+ private ImageButton mBackButton;
+ private EditText mSearchText;
+ private ListView mResultList;
+ private ListView mHistoryList;
+ private HistoryResultSwitcher mHistoryResultSwitcher;
+ private UIState mCurrentUIState;
+
+ private BookmarkModelObserver mModelObserver = new BookmarkModelObserver() {
+ @Override
+ public void bookmarkModelChanged() {
+ if (mCurrentUIState == UIState.RESULT || mCurrentUIState == UIState.EMPTY) {
+ sendLocalSearchQuery();
+ }
+ }
+
+ @Override
+ public void bookmarkNodeRemoved(BookmarkItem parent, int oldIndex, BookmarkItem node,
+ boolean isDoingExtensiveChanges) {
+ // If isDoingExtensiveChanges is false, it will fall back to bookmarkModelChange()
+ if (isDoingExtensiveChanges && mCurrentUIState == UIState.RESULT) {
+ sendLocalSearchQuery();
+ }
+ }
+ };
+
+ /**
+ * Constructor for inflating from XML.
+ */
+ public EnhancedBookmarkSearchView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBackButton = (ImageButton) findViewById(R.id.eb_search_back);
+ mSearchText = (EditText) findViewById(R.id.eb_search_text);
+ mResultList = (ListView) findViewById(R.id.eb_result_list);
+ mHistoryList = (ListView) findViewById(R.id.eb_history_list);
+ mHistoryResultSwitcher = (HistoryResultSwitcher) findViewById(R.id.history_result_switcher);
+
+ mHistoryList.setOnItemClickListener(this);
+ mResultList.setOnItemClickListener(this);
+ mBackButton.setOnClickListener(this);
+ mSearchText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (TextUtils.isEmpty(s.toString().trim())) {
+ resetUI();
+ } else {
+ sendLocalSearchQuery();
+ }
+ }
+ });
+ mCurrentUIState = UIState.HISTORY;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ if (parent == mHistoryList && view instanceof TextView) {
+ List<BookmarkId> bookmarkIds = mEnhancedBookmarksModel
+ .getBookmarksForFilter((String) parent.getAdapter().getItem(position));
+ List<BookmarkMatch> bookmarkMatches = new ArrayList<BookmarkMatch>();
+ for (BookmarkId bookmarkId : bookmarkIds) {
+ bookmarkMatches.add(new BookmarkMatch(bookmarkId, null, null));
+ }
+ populateResultListView(bookmarkMatches);
+ } else if (parent == mResultList) {
+ mDelegate.openBookmark(
+ ((BookmarkMatch) parent.getAdapter().getItem(position)).getBookmarkId(),
+ LaunchLocation.SEARCH);
+ mDelegate.closeSearchUI();
+ }
+ }
+
+ private void resetUI() {
+ setUIState(UIState.HISTORY);
+ mResultList.setAdapter(null);
+ if (!mSearchText.getText().toString().isEmpty()) mSearchText.setText("");
+ }
+
+ private void sendLocalSearchQuery() {
+ String currentText = mSearchText.getText().toString().trim();
+ if (TextUtils.isEmpty(currentText)) return;
+
+ List<BookmarkMatch> results = mEnhancedBookmarksModel.getLocalSearchForQuery(currentText,
+ MAXIMUM_NUMBER_OF_RESULTS);
+ if (results != null) populateResultListView(results);
+ }
+
+ /**
+ * Make result list visible and popuplate the list with given list of bookmarks.
+ */
+ private void populateResultListView(List<BookmarkMatch> ids) {
+ if (ids.isEmpty()) {
+ setUIState(UIState.EMPTY);
+ } else {
+ setUIState(UIState.RESULT);
+ mResultList.setAdapter(new ResultListAdapter(ids, mEnhancedBookmarksModel));
+ }
+ }
+
+ @Override
+ protected void onVisibilityChanged(View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ // This method might be called very early. Null check on bookmark model here.
+ if (mEnhancedBookmarksModel == null) return;
+ InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
+ Context.INPUT_METHOD_SERVICE);
+ if (visibility != View.VISIBLE) {
+ imm.hideSoftInputFromWindow(mSearchText.getWindowToken(), 0);
+ mEnhancedBookmarksModel.removeFiltersObserver(this);
+ mEnhancedBookmarksModel.removeModelObserver(mModelObserver);
+ resetUI();
+ clearFocus();
+ } else {
+ mEnhancedBookmarksModel.addModelObserver(mModelObserver);
+ mEnhancedBookmarksModel.addFiltersObserver(this);
+ onFiltersChanged();
+ mSearchText.requestFocus();
+ imm.showSoftInput(mSearchText, 0);
+ }
+ }
+
+ private void setUIState(UIState state) {
+ if (mCurrentUIState == state) return;
+ mCurrentUIState = state;
+ if (state == UIState.HISTORY) {
+ mHistoryResultSwitcher.showHistory();
+ } else if (state == UIState.RESULT) {
+ mHistoryResultSwitcher.showResult();
+ } else if (state == UIState.EMPTY) {
+ mHistoryResultSwitcher.showEmpty();
+ }
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ // To intercept hardware key, a view must have focus.
+ if (mDelegate == null) return super.dispatchKeyEvent(event);
+
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ KeyEvent.DispatcherState state = getKeyDispatcherState();
+ if (state != null) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ state.startTracking(event, this);
+ return true;
+ } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()
+ && state.isTracking(event)) {
+ onClick(mBackButton);
+ return true;
+ }
+ }
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ /**
+ * Click listener for back button.
+ */
+ @Override
+ public void onClick(View v) {
+ assert v == mBackButton;
+ if (mCurrentUIState == UIState.HISTORY) {
+ mDelegate.closeSearchUI();
+ } else {
+ resetUI();
+ }
+ }
+
+ @Override
+ public void onFiltersChanged() {
+ // We use android default list and textviews for history. Only start padding is modified.
+ mHistoryList.setAdapter(new ArrayAdapter<String>(getContext(),
+ android.R.layout.simple_list_item_1, android.R.id.text1,
+ mEnhancedBookmarksModel.getFilters()) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View textView = super.getView(position, convertView, parent);
+ // Set padding start to specific size.
+ int paddingStart = (int) (HISTORY_ITEM_PADDING_START_DP
+ * getResources().getDisplayMetrics().density);
+ ApiCompatibilityUtils.setPaddingRelative(textView, paddingStart,
+ textView.getPaddingTop(), textView.getPaddingRight(),
+ textView.getPaddingBottom());
+ return textView;
+ }
+ });
+ }
+
+ // EnhancedBookmarkUIObserver implementation
+
+ @Override
+ public void onEnhancedBookmarkDelegateInitialized(EnhancedBookmarkDelegate delegate) {
+ mDelegate = delegate;
+ mDelegate.addUIObserver(this);
+ mEnhancedBookmarksModel = mDelegate.getModel();
+ }
+
+ @Override
+ public void onDestroy() {
+ mEnhancedBookmarksModel.removeFiltersObserver(this);
+ mEnhancedBookmarksModel.removeModelObserver(mModelObserver);
+ mDelegate.removeUIObserver(this);
+ }
+
+ @Override
+ public void onAllBookmarksStateSet() {
+ }
+
+ @Override
+ public void onFolderStateSet(BookmarkId folder) {
+ }
+
+ @Override
+ public void onFilterStateSet(String filter) {
+ }
+
+ @Override
+ public void onSelectionStateChange(List<BookmarkId> selectedBookmarks) {
+ }
+
+ @Override
+ public void onListModeChange(boolean isListModeEnabled) {}
+
+ /**
+ * List Adapter that organizes search results.
+ */
+ private static class ResultListAdapter extends BaseAdapter implements
+ SalientImageDrawableFactory {
+
+ private EnhancedBookmarksModel mBookmarksModel;
+ private List<BookmarkMatch> mResultList;
+
+ public ResultListAdapter(List<BookmarkMatch> bookmarkMatches,
+ EnhancedBookmarksModel enhancedBookmarksModel) {
+ mBookmarksModel = enhancedBookmarksModel;
+ mResultList = bookmarkMatches;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final BookmarkMatch bookmarkMatch = getItem(position);
+ BookmarkItem bookmarkItem = mBookmarksModel.getBookmarkById(
+ bookmarkMatch.getBookmarkId());
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext()).inflate(
+ R.layout.eb_search_result_item, parent, false);
+ }
+ final TextView titleTextView = (TextView) convertView.findViewById(R.id.title);
+ final TextView urlTextView = (TextView) convertView.findViewById(R.id.url);
+ EnhancedBookmarkSalientImageView imageView = (EnhancedBookmarkSalientImageView)
+ convertView.findViewById(R.id.round_image);
+ imageView.load(mBookmarksModel, bookmarkItem.getUrl(),
+ EnhancedBookmarkUtils.generateBackgroundColor(bookmarkItem), this);
+
+ final SpannableString title = boldMatchPositions(bookmarkItem.getTitle(),
+ bookmarkMatch.getTitleMatchPositions());
+ final SpannableString url = boldMatchPositions(bookmarkItem.getUrl(),
+ bookmarkMatch.getUrlMatchPositions());
+
+ titleTextView.setText(title);
+ urlTextView.setText(url);
+
+ // Ensure the first matched search term is visible on the screen by calling
+ // moveCharactersOnScreen after the titleTextView.onLayout() has been executed
+ if (bookmarkMatch.getTitleMatchPositions() != null
+ && bookmarkMatch.getTitleMatchPositions().size() > 0) {
+ titleTextView.addOnLayoutChangeListener(new OnLayoutChangeListener(){
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ moveCharactersOnScreen(titleTextView, title,
+ bookmarkMatch.getTitleMatchPositions().get(0).first,
+ bookmarkMatch.getTitleMatchPositions().get(0).second);
+ }
+ });
+ }
+
+ // Ensure the first matched search term is visible on the screen by calling
+ // moveCharactersOnScreen after the urlTextView.onLayout() has been executed
+ if (bookmarkMatch.getUrlMatchPositions() != null
+ && bookmarkMatch.getUrlMatchPositions().size() > 0) {
+ urlTextView.addOnLayoutChangeListener(new OnLayoutChangeListener(){
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ moveCharactersOnScreen(urlTextView, url,
+ bookmarkMatch.getUrlMatchPositions().get(0).first,
+ bookmarkMatch.getUrlMatchPositions().get(0).second);
+ }
+ });
+ }
+
+ return convertView;
+ }
+
+ private SpannableString boldMatchPositions(String input,
+ List<Pair<Integer, Integer>> matches) {
+ SpannableString output = new SpannableString(input);
+ if (matches != null) {
+ for (Pair<Integer, Integer> match : matches) {
+ StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
+ output.setSpan(boldSpan, match.first, match.second, 0);
+ }
+ }
+
+ return output;
+ }
+
+ /**
+ * Ensures the characters from [charStartPos, charEndPos) are on screen.
+ *
+ * @param textView The textView to manipulate
+ * @param viewText A SpannableString containing the textView's text
+ * @param charStartPos The starting position for characters that should be on screen
+ * @param charEndPos The ending position for characters that should be on screen
+ */
+ private void moveCharactersOnScreen(TextView textView, SpannableString viewText,
+ int charStartPos, int charEndPos) {
+ if (textView.getLayout() == null) return;
+
+ if (!isCharacterOnScreen(textView.getLayout(), charEndPos - 1)) {
+ int subStringLength = textView.getLayout().getEllipsisStart(0);
+ int termLength = charEndPos - charStartPos;
+ int subStringStartPos = charStartPos - ((subStringLength - termLength) / 2);
+
+ if (subStringStartPos + subStringLength > viewText.length()) {
+ // Characters are near the end of viewText, ellipsize the start
+ textView.setEllipsize(TruncateAt.START);
+ } else {
+ // Characters are somwhere in the middle of the viewText, change the textView's
+ // text so the characters are on screen; both sides of the text will be
+ // ellipsized
+ SpannableStringBuilder newText = new SpannableStringBuilder(viewText,
+ subStringStartPos, viewText.length());
+ newText.insert(0, HORIZONTAL_ELLIPSIS);
+ textView.setText(newText);
+ }
+ }
+ }
+
+ private boolean isCharacterOnScreen(Layout textViewLayout, int charPosition) {
+ int ellipsisPosition = textViewLayout.getEllipsisStart(0);
+ return ellipsisPosition == 0 || charPosition < ellipsisPosition;
+ }
+
+ @Override
+ public int getCount() {
+ return mResultList.size();
+ }
+
+ @Override
+ public BookmarkMatch getItem(int position) {
+ return mResultList.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ // SalientImageDrawableFactory implementation
+
+ @Override
+ public Drawable getSalientDrawable(int color) {
+ return new CircularDrawable(color);
+ }
+
+ @Override
+ public Drawable getSalientDrawable(Bitmap bitmap) {
+ return new CircularDrawable(bitmap);
+ }
+ }
+
+ /**
+ * A custom {@link ViewSwitcher} that makes specific assumptions about the corresponding xml. It
+ * assumes it has two children: a {@link ListView} and a {@link ViewSwitcher}, and in the other
+ * {@link ViewSwitcher}, it contains a {@link TextView} and a {@link ListView}.
+ */
+ public static class HistoryResultSwitcher extends ViewSwitcher {
+ ViewSwitcher mResultEmptySwitcher;
+
+ /**
+ * Constructor for xml inflation.
+ */
+ public HistoryResultSwitcher(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mResultEmptySwitcher = (ViewSwitcher) findViewById(R.id.result_empty_switcher);
+ }
+
+ void showHistory() {
+ if (getCurrentView().getId() == R.id.eb_history_list) return;
+ showNext();
+ }
+
+ void showResult() {
+ // If currently showing history, toggle.
+ if (getCurrentView().getId() == R.id.eb_history_list) showNext();
+ // If currently showing empty view, toggle.
+ if (mResultEmptySwitcher.getCurrentView().getId() == R.id.eb_search_empty_view) {
+ mResultEmptySwitcher.showNext();
+ }
+ }
+
+ void showEmpty() {
+ // If currently showing history, toggle.
+ if (getCurrentView().getId() == R.id.eb_history_list) showNext();
+ // If currently showing result list, toggle.
+ if (mResultEmptySwitcher.getCurrentView().getId() == R.id.eb_result_list) {
+ mResultEmptySwitcher.showNext();
+ }
+ }
+ }
+
+
+}

Powered by Google App Engine
This is Rietveld 408576698