summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java494
1 files changed, 0 insertions, 494 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
deleted file mode 100644
index 5b97a8f5f..000000000
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/* -*- 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.home;
-
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener;
-import org.mozilla.gecko.home.BrowserSearch.OnSearchListener;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.icons.IconResponse;
-import org.mozilla.gecko.preferences.GeckoPreferences;
-import org.mozilla.gecko.util.DrawableUtil;
-import org.mozilla.gecko.util.StringUtils;
-import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.widget.AnimatedHeightLayout;
-import org.mozilla.gecko.widget.FaviconView;
-import org.mozilla.gecko.widget.FlowLayout;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.graphics.drawable.Drawable;
-import android.graphics.Typeface;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.text.style.StyleSpan;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.util.AttributeSet;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.regex.Pattern;
-
-class SearchEngineRow extends AnimatedHeightLayout {
- // Duration for fade-in animation
- private static final int ANIMATION_DURATION = 250;
-
- // Inner views
- private final FlowLayout mSuggestionView;
- private final FaviconView mIconView;
- private final LinearLayout mUserEnteredView;
- private final TextView mUserEnteredTextView;
-
- // Inflater used when updating from suggestions
- private final LayoutInflater mInflater;
-
- // Search engine associated with this view
- private SearchEngine mSearchEngine;
-
- // Event listeners for suggestion views
- private final OnClickListener mClickListener;
- private final OnLongClickListener mLongClickListener;
-
- // On URL open listener
- private OnUrlOpenListener mUrlOpenListener;
-
- // On search listener
- private OnSearchListener mSearchListener;
-
- // On edit suggestion listener
- private OnEditSuggestionListener mEditSuggestionListener;
-
- // Selected suggestion view
- private int mSelectedView;
-
- // android:backgroundTint only works in Android 21 and higher so we can't do this statically in the xml
- private Drawable mSearchHistorySuggestionIcon;
-
- // Maximums for suggestions
- private int mMaxSavedSuggestions;
- private int mMaxSearchSuggestions;
-
- private final List<Integer> mOccurrences = new ArrayList<Integer>();
-
- public SearchEngineRow(Context context) {
- this(context, null);
- }
-
- public SearchEngineRow(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SearchEngineRow(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- mClickListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- final String suggestion = getSuggestionTextFromView(v);
-
- // If we're not clicking the user-entered view (the first suggestion item)
- // and the search matches a URL pattern, go to that URL. Otherwise, do a
- // search for the term.
- if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, true)) {
- if (mUrlOpenListener != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "url");
-
- mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class));
- }
- } else if (mSearchListener != null) {
- if (v == mUserEnteredView) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
- } else {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, (String) v.getTag());
- }
- mSearchListener.onSearch(mSearchEngine, suggestion, TelemetryContract.Method.SUGGESTION);
- }
- }
- };
-
- mLongClickListener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mEditSuggestionListener != null) {
- final String suggestion = getSuggestionTextFromView(v);
- mEditSuggestionListener.onEditSuggestion(suggestion);
- return true;
- }
-
- return false;
- }
- };
-
- mInflater = LayoutInflater.from(context);
- mInflater.inflate(R.layout.search_engine_row, this);
-
- mSuggestionView = (FlowLayout) findViewById(R.id.suggestion_layout);
- mIconView = (FaviconView) findViewById(R.id.suggestion_icon);
-
- // User-entered search term is first suggestion
- mUserEnteredView = (LinearLayout) findViewById(R.id.suggestion_user_entered);
- mUserEnteredView.setOnClickListener(mClickListener);
-
- mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text);
- mSearchHistorySuggestionIcon = DrawableUtil.tintDrawableWithColorRes(getContext(), R.drawable.icon_most_recent_empty, R.color.tabs_tray_icon_grey);
-
- // Suggestion limits
- mMaxSavedSuggestions = getResources().getInteger(R.integer.max_saved_suggestions);
- mMaxSearchSuggestions = getResources().getInteger(R.integer.max_search_suggestions);
- }
-
- private void setDescriptionOnSuggestion(View v, String suggestion) {
- v.setContentDescription(getResources().getString(R.string.suggestion_for_engine,
- mSearchEngine.name, suggestion));
- }
-
- private String getSuggestionTextFromView(View v) {
- final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
- return suggestionText.getText().toString();
- }
-
- /**
- * Finds all occurrences of pattern in string and returns a list of the starting indices
- * of each occurrence.
- *
- * @param pattern The pattern that is searched for
- * @param string The string where we search for the pattern
- */
- private void refreshOccurrencesWith(String pattern, String string) {
- mOccurrences.clear();
-
- // Don't try to search for an empty string - String.indexOf will return 0, which would result
- // in us iterating with lastIndexOfMatch = 0, which eventually results in an OOM.
- if (TextUtils.isEmpty(pattern)) {
- return;
- }
-
- final int patternLength = pattern.length();
-
- int indexOfMatch = 0;
- int lastIndexOfMatch = 0;
- while (indexOfMatch != -1) {
- indexOfMatch = string.indexOf(pattern, lastIndexOfMatch);
- lastIndexOfMatch = indexOfMatch + patternLength;
- if (indexOfMatch != -1) {
- mOccurrences.add(indexOfMatch);
- }
- }
- }
-
- /**
- * Sets the content for the suggestion view.
- *
- * If the suggestion doesn't contain mUserSearchTerm, nothing is made bold.
- * All instances of mUserSearchTerm in the suggestion are not bold.
- *
- * @param v The View that needs to be populated
- * @param suggestion The suggestion text that will be placed in the view
- * @param isUserSavedSearch whether the suggestion is from history or not
- */
- private void setSuggestionOnView(View v, String suggestion, boolean isUserSavedSearch) {
- final ImageView historyIcon = (ImageView) v.findViewById(R.id.suggestion_item_icon);
- if (isUserSavedSearch) {
- historyIcon.setImageDrawable(mSearchHistorySuggestionIcon);
- historyIcon.setVisibility(View.VISIBLE);
- } else {
- historyIcon.setVisibility(View.GONE);
- }
-
- final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
- final String searchTerm = getSuggestionTextFromView(mUserEnteredView);
- final int searchTermLength = searchTerm.length();
- refreshOccurrencesWith(searchTerm, suggestion);
- if (mOccurrences.size() > 0) {
- final SpannableStringBuilder sb = new SpannableStringBuilder(suggestion);
- int nextStartSpanIndex = 0;
- // Done to make sure that the stretch of text after the last occurrence, till the end of the suggestion, is made bold
- mOccurrences.add(suggestion.length());
- for (int occurrence : mOccurrences) {
- // Even though they're the same style, SpannableStringBuilder will interpret there as being only one Span present if we re-use a StyleSpan
- StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
- sb.setSpan(boldSpan, nextStartSpanIndex, occurrence, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
- nextStartSpanIndex = occurrence + searchTermLength;
- }
- mOccurrences.clear();
- suggestionText.setText(sb);
- } else {
- suggestionText.setText(suggestion);
- }
-
- setDescriptionOnSuggestion(suggestionText, suggestion);
- }
-
- /**
- * Perform a search for the user-entered term.
- */
- public void performUserEnteredSearch() {
- String searchTerm = getSuggestionTextFromView(mUserEnteredView);
- if (mSearchListener != null) {
- Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user");
- mSearchListener.onSearch(mSearchEngine, searchTerm, TelemetryContract.Method.SUGGESTION);
- }
- }
-
- public void setSearchTerm(String searchTerm) {
- mUserEnteredTextView.setText(searchTerm);
-
- // mSearchEngine is not set in the first call to this method; the content description
- // is instead initially set in updateSuggestions().
- if (mSearchEngine != null) {
- setDescriptionOnSuggestion(mUserEnteredTextView, searchTerm);
- }
- }
-
- public void setOnUrlOpenListener(OnUrlOpenListener listener) {
- mUrlOpenListener = listener;
- }
-
- public void setOnSearchListener(OnSearchListener listener) {
- mSearchListener = listener;
- }
-
- public void setOnEditSuggestionListener(OnEditSuggestionListener listener) {
- mEditSuggestionListener = listener;
- }
-
- private void bindSuggestionView(String suggestion, boolean animate, int recycledSuggestionCount, Integer previousSuggestionChildIndex, boolean isUserSavedSearch, String telemetryTag) {
- final View suggestionItem;
-
- // Reuse suggestion views from recycled view, if possible.
- if (previousSuggestionChildIndex + 1 < recycledSuggestionCount) {
- suggestionItem = mSuggestionView.getChildAt(previousSuggestionChildIndex + 1);
- suggestionItem.setVisibility(View.VISIBLE);
- } else {
- suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
-
- suggestionItem.setOnClickListener(mClickListener);
- suggestionItem.setOnLongClickListener(mLongClickListener);
-
- suggestionItem.setTag(telemetryTag);
-
- mSuggestionView.addView(suggestionItem);
- }
-
- setSuggestionOnView(suggestionItem, suggestion, isUserSavedSearch);
-
- if (animate) {
- AlphaAnimation anim = new AlphaAnimation(0, 1);
- anim.setDuration(ANIMATION_DURATION);
- anim.setStartOffset(previousSuggestionChildIndex * ANIMATION_DURATION);
- suggestionItem.startAnimation(anim);
- }
- }
-
- private void hideRecycledSuggestions(int lastVisibleChildIndex, int recycledSuggestionCount) {
- // Hide extra suggestions that have been recycled.
- for (int i = lastVisibleChildIndex + 1; i < recycledSuggestionCount; ++i) {
- mSuggestionView.getChildAt(i).setVisibility(View.GONE);
- }
- }
-
- /**
- * Displays search suggestions from previous searches.
- *
- * @param savedSuggestions The List to iterate over for saved search suggestions to display. This function does not
- * enforce a ui maximum or filter. It will show all the suggestions in this list.
- * @param suggestionStartIndex global index of where to start adding suggestion "buttons" in the search engine row. Also
- * acts as a counter for total number of suggestions visible.
- * @param animate whether or not to animate suggestions for visual polish
- * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
- */
- private void updateFromSavedSearches(List<String> savedSuggestions, boolean animate, int suggestionStartIndex, int recycledSuggestionCount) {
- if (savedSuggestions == null || savedSuggestions.isEmpty()) {
- hideRecycledSuggestions(suggestionStartIndex, recycledSuggestionCount);
- return;
- }
-
- final int numSavedSearches = savedSuggestions.size();
- int indexOfPreviousSuggestion = 0;
- for (int i = 0; i < numSavedSearches; i++) {
- String telemetryTag = "history." + i;
- final String suggestion = savedSuggestions.get(i);
- indexOfPreviousSuggestion = suggestionStartIndex + i;
- bindSuggestionView(suggestion, animate, recycledSuggestionCount, indexOfPreviousSuggestion, true, telemetryTag);
- }
-
- hideRecycledSuggestions(indexOfPreviousSuggestion + 1, recycledSuggestionCount);
- }
-
- /**
- * Displays suggestions supplied by the search engine, relative to number of suggestions from search history.
- *
- * @param animate whether or not to animate suggestions for visual polish
- * @param recycledSuggestionCount How many suggestion "button" views we could recycle from previous calls
- * @param savedSuggestionCount how many saved searches this searchTerm has
- * @return the global count of how many suggestions have been bound/shown in the search engine row
- */
- private int updateFromSearchEngine(boolean animate, List<String> searchEngineSuggestions, int recycledSuggestionCount, int savedSuggestionCount) {
- int maxSuggestions = mMaxSearchSuggestions;
- // If there are less than max saved searches on phones, fill the space with more search engine suggestions
- if (!HardwareUtils.isTablet() && savedSuggestionCount < mMaxSavedSuggestions) {
- maxSuggestions += mMaxSavedSuggestions - savedSuggestionCount;
- }
-
- final int numSearchEngineSuggestions = searchEngineSuggestions.size();
- int relativeIndex;
- for (relativeIndex = 0; relativeIndex < numSearchEngineSuggestions; relativeIndex++) {
- if (relativeIndex == maxSuggestions) {
- break;
- }
-
- // Since the search engine suggestions are listed first, their relative index is their global index
- String telemetryTag = "engine." + relativeIndex;
- final String suggestion = searchEngineSuggestions.get(relativeIndex);
- bindSuggestionView(suggestion, animate, recycledSuggestionCount, relativeIndex, false, telemetryTag);
- }
-
- hideRecycledSuggestions(relativeIndex + 1, recycledSuggestionCount);
-
- // Make sure mSelectedView is still valid.
- if (mSelectedView >= mSuggestionView.getChildCount()) {
- mSelectedView = mSuggestionView.getChildCount() - 1;
- }
-
- return relativeIndex;
- }
-
- /**
- * Updates the whole suggestions UI, the search engine UI, suggestions from the default search engine,
- * and suggestions from search history.
- *
- * This can be called before the opt-in permission prompt is shown or set.
- * Even if both suggestion types are disabled, we need to update the search engine, its image, and the content description.
- *
- * @param searchSuggestionsEnabled whether or not suggestions from the default search engine are enabled
- * @param searchEngine the search engine to use throughout the SearchEngineRow class
- * @param rawSearchHistorySuggestions search history suggestions
- * @param animate whether or not to use animations
- **/
- public void updateSuggestions(boolean searchSuggestionsEnabled, SearchEngine searchEngine, @Nullable List<String> rawSearchHistorySuggestions, boolean animate) {
- mSearchEngine = searchEngine;
- // Set the search engine icon (e.g., Google) for the row.
-
- mIconView.updateAndScaleImage(IconResponse.create(mSearchEngine.getIcon()));
- // Set the initial content description.
- setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
-
- final int recycledSuggestionCount = mSuggestionView.getChildCount();
- final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
- final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
-
- // Remove duplicates of search engine suggestions from saved searches.
- List<String> searchHistorySuggestions = (rawSearchHistorySuggestions != null) ? rawSearchHistorySuggestions : new ArrayList<String>();
-
- // Filter out URLs and long search suggestions
- Iterator<String> searchHistoryIterator = searchHistorySuggestions.iterator();
- while (searchHistoryIterator.hasNext()) {
- final String currentSearchHistory = searchHistoryIterator.next();
-
- if (currentSearchHistory.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", currentSearchHistory)) {
- searchHistoryIterator.remove();
- }
- }
-
-
- List<String> searchEngineSuggestions = new ArrayList<String>();
- for (String suggestion : searchEngine.getSuggestions()) {
- searchHistorySuggestions.remove(suggestion);
- searchEngineSuggestions.add(suggestion);
- }
- // Make sure the search term itself isn't duplicated. This is more important on phones than tablets where screen
- // space is more precious.
- searchHistorySuggestions.remove(getSuggestionTextFromView(mUserEnteredView));
-
- // Trim the history suggestions down to the maximum allowed.
- if (searchHistorySuggestions.size() >= mMaxSavedSuggestions) {
- // The second index to subList() is exclusive, so this looks like an off by one error but it is not.
- searchHistorySuggestions = searchHistorySuggestions.subList(0, mMaxSavedSuggestions);
- }
- final int searchHistoryCount = searchHistorySuggestions.size();
-
- if (searchSuggestionsEnabled && savedSearchesEnabled) {
- final int suggestionViewCount = updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, searchHistoryCount);
- updateFromSavedSearches(searchHistorySuggestions, animate, suggestionViewCount, recycledSuggestionCount);
- } else if (savedSearchesEnabled) {
- updateFromSavedSearches(searchHistorySuggestions, animate, 0, recycledSuggestionCount);
- } else if (searchSuggestionsEnabled) {
- updateFromSearchEngine(animate, searchEngineSuggestions, recycledSuggestionCount, 0);
- } else {
- // The current search term is treated separately from the suggestions list, hence we can
- // recycle ALL suggestion items here. (We always show the current search term, i.e. 1 item,
- // in front of the search engine suggestions and/or the search history.)
- hideRecycledSuggestions(0, recycledSuggestionCount);
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
- final View suggestion = mSuggestionView.getChildAt(mSelectedView);
-
- if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) {
- return false;
- }
-
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- final View nextSuggestion = mSuggestionView.getChildAt(mSelectedView + 1);
- if (nextSuggestion != null) {
- changeSelectedSuggestion(suggestion, nextSuggestion);
- mSelectedView++;
- return true;
- }
- break;
-
- case KeyEvent.KEYCODE_DPAD_LEFT:
- final View prevSuggestion = mSuggestionView.getChildAt(mSelectedView - 1);
- if (prevSuggestion != null) {
- changeSelectedSuggestion(suggestion, prevSuggestion);
- mSelectedView--;
- return true;
- }
- break;
-
- case KeyEvent.KEYCODE_BUTTON_A:
- // TODO: handle long pressing for editing suggestions
- return suggestion.performClick();
- }
-
- return false;
- }
-
- private void changeSelectedSuggestion(View oldSuggestion, View newSuggestion) {
- oldSuggestion.setDuplicateParentStateEnabled(false);
- newSuggestion.setDuplicateParentStateEnabled(true);
- oldSuggestion.refreshDrawableState();
- newSuggestion.refreshDrawableState();
- }
-
- public void onSelected() {
- mSelectedView = 0;
- mUserEnteredView.setDuplicateParentStateEnabled(true);
- mUserEnteredView.refreshDrawableState();
- }
-
- public void onDeselected() {
- final View suggestion = mSuggestionView.getChildAt(mSelectedView);
- suggestion.setDuplicateParentStateEnabled(false);
- suggestion.refreshDrawableState();
- }
-}