michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.home; michael@0: michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.Telemetry; michael@0: import org.mozilla.gecko.TelemetryContract; michael@0: import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener; michael@0: import org.mozilla.gecko.home.BrowserSearch.OnSearchListener; michael@0: import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; michael@0: import org.mozilla.gecko.util.StringUtils; michael@0: import org.mozilla.gecko.widget.AnimatedHeightLayout; michael@0: import org.mozilla.gecko.widget.FaviconView; michael@0: import org.mozilla.gecko.widget.FlowLayout; michael@0: michael@0: import android.content.Context; michael@0: import android.util.AttributeSet; michael@0: import android.view.KeyEvent; michael@0: import android.view.LayoutInflater; michael@0: import android.view.View; michael@0: import android.view.animation.AlphaAnimation; michael@0: import android.widget.ImageView; michael@0: import android.widget.LinearLayout; michael@0: import android.widget.TextView; michael@0: michael@0: import java.util.EnumSet; michael@0: michael@0: class SearchEngineRow extends AnimatedHeightLayout { michael@0: // Duration for fade-in animation michael@0: private static final int ANIMATION_DURATION = 250; michael@0: michael@0: // Inner views michael@0: private final FlowLayout mSuggestionView; michael@0: private final FaviconView mIconView; michael@0: private final LinearLayout mUserEnteredView; michael@0: private final TextView mUserEnteredTextView; michael@0: michael@0: // Inflater used when updating from suggestions michael@0: private final LayoutInflater mInflater; michael@0: michael@0: // Search engine associated with this view michael@0: private SearchEngine mSearchEngine; michael@0: michael@0: // Event listeners for suggestion views michael@0: private final OnClickListener mClickListener; michael@0: private final OnLongClickListener mLongClickListener; michael@0: michael@0: // On URL open listener michael@0: private OnUrlOpenListener mUrlOpenListener; michael@0: michael@0: // On search listener michael@0: private OnSearchListener mSearchListener; michael@0: michael@0: // On edit suggestion listener michael@0: private OnEditSuggestionListener mEditSuggestionListener; michael@0: michael@0: // Selected suggestion view michael@0: private int mSelectedView = 0; michael@0: michael@0: public SearchEngineRow(Context context) { michael@0: this(context, null); michael@0: } michael@0: michael@0: public SearchEngineRow(Context context, AttributeSet attrs) { michael@0: this(context, attrs, 0); michael@0: } michael@0: michael@0: public SearchEngineRow(Context context, AttributeSet attrs, int defStyle) { michael@0: super(context, attrs, defStyle); michael@0: michael@0: mClickListener = new OnClickListener() { michael@0: @Override michael@0: public void onClick(View v) { michael@0: final String suggestion = getSuggestionTextFromView(v); michael@0: michael@0: // If we're not clicking the user-entered view (the first suggestion item) michael@0: // and the search matches a URL pattern, go to that URL. Otherwise, do a michael@0: // search for the term. michael@0: if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, false)) { michael@0: if (mUrlOpenListener != null) { michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "url"); michael@0: michael@0: mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class)); michael@0: } michael@0: } else if (mSearchListener != null) { michael@0: if (v == mUserEnteredView) { michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user"); michael@0: } else { michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "engine"); michael@0: } michael@0: mSearchListener.onSearch(mSearchEngine, suggestion); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: mLongClickListener = new OnLongClickListener() { michael@0: @Override michael@0: public boolean onLongClick(View v) { michael@0: if (mEditSuggestionListener != null) { michael@0: final String suggestion = getSuggestionTextFromView(v); michael@0: mEditSuggestionListener.onEditSuggestion(suggestion); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: mInflater = LayoutInflater.from(context); michael@0: mInflater.inflate(R.layout.search_engine_row, this); michael@0: michael@0: mSuggestionView = (FlowLayout) findViewById(R.id.suggestion_layout); michael@0: mIconView = (FaviconView) findViewById(R.id.suggestion_icon); michael@0: michael@0: // User-entered search term is first suggestion michael@0: mUserEnteredView = (LinearLayout) findViewById(R.id.suggestion_user_entered); michael@0: mUserEnteredView.setOnClickListener(mClickListener); michael@0: michael@0: mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text); michael@0: } michael@0: michael@0: private void setDescriptionOnSuggestion(View v, String suggestion) { michael@0: v.setContentDescription(getResources().getString(R.string.suggestion_for_engine, michael@0: mSearchEngine.name, suggestion)); michael@0: } michael@0: michael@0: private String getSuggestionTextFromView(View v) { michael@0: final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text); michael@0: return suggestionText.getText().toString(); michael@0: } michael@0: michael@0: private void setSuggestionOnView(View v, String suggestion) { michael@0: final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text); michael@0: suggestionText.setText(suggestion); michael@0: setDescriptionOnSuggestion(suggestionText, suggestion); michael@0: } michael@0: michael@0: /** michael@0: * Perform a search for the user-entered term. michael@0: */ michael@0: public void performUserEnteredSearch() { michael@0: String searchTerm = getSuggestionTextFromView(mUserEnteredView); michael@0: if (mSearchListener != null) { michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user"); michael@0: mSearchListener.onSearch(mSearchEngine, searchTerm); michael@0: } michael@0: } michael@0: michael@0: public void setSearchTerm(String searchTerm) { michael@0: mUserEnteredTextView.setText(searchTerm); michael@0: michael@0: // mSearchEngine is not set in the first call to this method; the content description michael@0: // is instead initially set in updateFromSearchEngine. michael@0: if (mSearchEngine != null) { michael@0: setDescriptionOnSuggestion(mUserEnteredTextView, searchTerm); michael@0: } michael@0: } michael@0: michael@0: public void setOnUrlOpenListener(OnUrlOpenListener listener) { michael@0: mUrlOpenListener = listener; michael@0: } michael@0: michael@0: public void setOnSearchListener(OnSearchListener listener) { michael@0: mSearchListener = listener; michael@0: } michael@0: michael@0: public void setOnEditSuggestionListener(OnEditSuggestionListener listener) { michael@0: mEditSuggestionListener = listener; michael@0: } michael@0: michael@0: public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) { michael@0: // Update search engine reference. michael@0: mSearchEngine = searchEngine; michael@0: michael@0: // Set the search engine icon (e.g., Google) for the row. michael@0: mIconView.updateAndScaleImage(mSearchEngine.getIcon(), mSearchEngine.getEngineIdentifier()); michael@0: michael@0: // Set the initial content description. michael@0: setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString()); michael@0: michael@0: // Add additional suggestions given by this engine. michael@0: final int recycledSuggestionCount = mSuggestionView.getChildCount(); michael@0: michael@0: int suggestionCounter = 0; michael@0: for (String suggestion : mSearchEngine.getSuggestions()) { michael@0: final View suggestionItem; michael@0: michael@0: // Reuse suggestion views from recycled view, if possible. michael@0: if (suggestionCounter + 1 < recycledSuggestionCount) { michael@0: suggestionItem = mSuggestionView.getChildAt(suggestionCounter + 1); michael@0: suggestionItem.setVisibility(View.VISIBLE); michael@0: } else { michael@0: suggestionItem = mInflater.inflate(R.layout.suggestion_item, null); michael@0: michael@0: suggestionItem.setOnClickListener(mClickListener); michael@0: suggestionItem.setOnLongClickListener(mLongClickListener); michael@0: michael@0: final ImageView magnifier = michael@0: (ImageView) suggestionItem.findViewById(R.id.suggestion_magnifier); michael@0: magnifier.setVisibility(View.GONE); michael@0: michael@0: mSuggestionView.addView(suggestionItem); michael@0: } michael@0: michael@0: setSuggestionOnView(suggestionItem, suggestion); michael@0: michael@0: if (animate) { michael@0: AlphaAnimation anim = new AlphaAnimation(0, 1); michael@0: anim.setDuration(ANIMATION_DURATION); michael@0: anim.setStartOffset(suggestionCounter * ANIMATION_DURATION); michael@0: suggestionItem.startAnimation(anim); michael@0: } michael@0: michael@0: ++suggestionCounter; michael@0: } michael@0: michael@0: // Hide extra suggestions that have been recycled. michael@0: for (int i = suggestionCounter + 1; i < recycledSuggestionCount; ++i) { michael@0: mSuggestionView.getChildAt(i).setVisibility(View.GONE); michael@0: } michael@0: michael@0: // Make sure mSelectedView is still valid. michael@0: if (mSelectedView >= mSuggestionView.getChildCount()) { michael@0: mSelectedView = mSuggestionView.getChildCount() - 1; michael@0: } michael@0: } michael@0: michael@0: @Override michael@0: public boolean onKeyDown(int keyCode, android.view.KeyEvent event) { michael@0: final View suggestion = mSuggestionView.getChildAt(mSelectedView); michael@0: michael@0: if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) { michael@0: return false; michael@0: } michael@0: michael@0: switch (event.getKeyCode()) { michael@0: case KeyEvent.KEYCODE_DPAD_RIGHT: michael@0: final View nextSuggestion = mSuggestionView.getChildAt(mSelectedView + 1); michael@0: if (nextSuggestion != null) { michael@0: changeSelectedSuggestion(suggestion, nextSuggestion); michael@0: mSelectedView++; michael@0: return true; michael@0: } michael@0: break; michael@0: michael@0: case KeyEvent.KEYCODE_DPAD_LEFT: michael@0: final View prevSuggestion = mSuggestionView.getChildAt(mSelectedView - 1); michael@0: if (prevSuggestion != null) { michael@0: changeSelectedSuggestion(suggestion, prevSuggestion); michael@0: mSelectedView--; michael@0: return true; michael@0: } michael@0: break; michael@0: michael@0: case KeyEvent.KEYCODE_BUTTON_A: michael@0: // TODO: handle long pressing for editing suggestions michael@0: return suggestion.performClick(); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: private void changeSelectedSuggestion(View oldSuggestion, View newSuggestion) { michael@0: oldSuggestion.setDuplicateParentStateEnabled(false); michael@0: newSuggestion.setDuplicateParentStateEnabled(true); michael@0: oldSuggestion.refreshDrawableState(); michael@0: newSuggestion.refreshDrawableState(); michael@0: } michael@0: michael@0: public void onSelected() { michael@0: mSelectedView = 0; michael@0: mUserEnteredView.setDuplicateParentStateEnabled(true); michael@0: mUserEnteredView.refreshDrawableState(); michael@0: } michael@0: michael@0: public void onDeselected() { michael@0: final View suggestion = mSuggestionView.getChildAt(mSelectedView); michael@0: suggestion.setDuplicateParentStateEnabled(false); michael@0: suggestion.refreshDrawableState(); michael@0: } michael@0: }