Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | package org.mozilla.gecko.home; |
michael@0 | 7 | |
michael@0 | 8 | import org.mozilla.gecko.R; |
michael@0 | 9 | import org.mozilla.gecko.Telemetry; |
michael@0 | 10 | import org.mozilla.gecko.TelemetryContract; |
michael@0 | 11 | import org.mozilla.gecko.home.BrowserSearch.OnEditSuggestionListener; |
michael@0 | 12 | import org.mozilla.gecko.home.BrowserSearch.OnSearchListener; |
michael@0 | 13 | import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; |
michael@0 | 14 | import org.mozilla.gecko.util.StringUtils; |
michael@0 | 15 | import org.mozilla.gecko.widget.AnimatedHeightLayout; |
michael@0 | 16 | import org.mozilla.gecko.widget.FaviconView; |
michael@0 | 17 | import org.mozilla.gecko.widget.FlowLayout; |
michael@0 | 18 | |
michael@0 | 19 | import android.content.Context; |
michael@0 | 20 | import android.util.AttributeSet; |
michael@0 | 21 | import android.view.KeyEvent; |
michael@0 | 22 | import android.view.LayoutInflater; |
michael@0 | 23 | import android.view.View; |
michael@0 | 24 | import android.view.animation.AlphaAnimation; |
michael@0 | 25 | import android.widget.ImageView; |
michael@0 | 26 | import android.widget.LinearLayout; |
michael@0 | 27 | import android.widget.TextView; |
michael@0 | 28 | |
michael@0 | 29 | import java.util.EnumSet; |
michael@0 | 30 | |
michael@0 | 31 | class SearchEngineRow extends AnimatedHeightLayout { |
michael@0 | 32 | // Duration for fade-in animation |
michael@0 | 33 | private static final int ANIMATION_DURATION = 250; |
michael@0 | 34 | |
michael@0 | 35 | // Inner views |
michael@0 | 36 | private final FlowLayout mSuggestionView; |
michael@0 | 37 | private final FaviconView mIconView; |
michael@0 | 38 | private final LinearLayout mUserEnteredView; |
michael@0 | 39 | private final TextView mUserEnteredTextView; |
michael@0 | 40 | |
michael@0 | 41 | // Inflater used when updating from suggestions |
michael@0 | 42 | private final LayoutInflater mInflater; |
michael@0 | 43 | |
michael@0 | 44 | // Search engine associated with this view |
michael@0 | 45 | private SearchEngine mSearchEngine; |
michael@0 | 46 | |
michael@0 | 47 | // Event listeners for suggestion views |
michael@0 | 48 | private final OnClickListener mClickListener; |
michael@0 | 49 | private final OnLongClickListener mLongClickListener; |
michael@0 | 50 | |
michael@0 | 51 | // On URL open listener |
michael@0 | 52 | private OnUrlOpenListener mUrlOpenListener; |
michael@0 | 53 | |
michael@0 | 54 | // On search listener |
michael@0 | 55 | private OnSearchListener mSearchListener; |
michael@0 | 56 | |
michael@0 | 57 | // On edit suggestion listener |
michael@0 | 58 | private OnEditSuggestionListener mEditSuggestionListener; |
michael@0 | 59 | |
michael@0 | 60 | // Selected suggestion view |
michael@0 | 61 | private int mSelectedView = 0; |
michael@0 | 62 | |
michael@0 | 63 | public SearchEngineRow(Context context) { |
michael@0 | 64 | this(context, null); |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | public SearchEngineRow(Context context, AttributeSet attrs) { |
michael@0 | 68 | this(context, attrs, 0); |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | public SearchEngineRow(Context context, AttributeSet attrs, int defStyle) { |
michael@0 | 72 | super(context, attrs, defStyle); |
michael@0 | 73 | |
michael@0 | 74 | mClickListener = new OnClickListener() { |
michael@0 | 75 | @Override |
michael@0 | 76 | public void onClick(View v) { |
michael@0 | 77 | final String suggestion = getSuggestionTextFromView(v); |
michael@0 | 78 | |
michael@0 | 79 | // If we're not clicking the user-entered view (the first suggestion item) |
michael@0 | 80 | // and the search matches a URL pattern, go to that URL. Otherwise, do a |
michael@0 | 81 | // search for the term. |
michael@0 | 82 | if (v != mUserEnteredView && !StringUtils.isSearchQuery(suggestion, false)) { |
michael@0 | 83 | if (mUrlOpenListener != null) { |
michael@0 | 84 | Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "url"); |
michael@0 | 85 | |
michael@0 | 86 | mUrlOpenListener.onUrlOpen(suggestion, EnumSet.noneOf(OnUrlOpenListener.Flags.class)); |
michael@0 | 87 | } |
michael@0 | 88 | } else if (mSearchListener != null) { |
michael@0 | 89 | if (v == mUserEnteredView) { |
michael@0 | 90 | Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user"); |
michael@0 | 91 | } else { |
michael@0 | 92 | Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "engine"); |
michael@0 | 93 | } |
michael@0 | 94 | mSearchListener.onSearch(mSearchEngine, suggestion); |
michael@0 | 95 | } |
michael@0 | 96 | } |
michael@0 | 97 | }; |
michael@0 | 98 | |
michael@0 | 99 | mLongClickListener = new OnLongClickListener() { |
michael@0 | 100 | @Override |
michael@0 | 101 | public boolean onLongClick(View v) { |
michael@0 | 102 | if (mEditSuggestionListener != null) { |
michael@0 | 103 | final String suggestion = getSuggestionTextFromView(v); |
michael@0 | 104 | mEditSuggestionListener.onEditSuggestion(suggestion); |
michael@0 | 105 | return true; |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | return false; |
michael@0 | 109 | } |
michael@0 | 110 | }; |
michael@0 | 111 | |
michael@0 | 112 | mInflater = LayoutInflater.from(context); |
michael@0 | 113 | mInflater.inflate(R.layout.search_engine_row, this); |
michael@0 | 114 | |
michael@0 | 115 | mSuggestionView = (FlowLayout) findViewById(R.id.suggestion_layout); |
michael@0 | 116 | mIconView = (FaviconView) findViewById(R.id.suggestion_icon); |
michael@0 | 117 | |
michael@0 | 118 | // User-entered search term is first suggestion |
michael@0 | 119 | mUserEnteredView = (LinearLayout) findViewById(R.id.suggestion_user_entered); |
michael@0 | 120 | mUserEnteredView.setOnClickListener(mClickListener); |
michael@0 | 121 | |
michael@0 | 122 | mUserEnteredTextView = (TextView) findViewById(R.id.suggestion_text); |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | private void setDescriptionOnSuggestion(View v, String suggestion) { |
michael@0 | 126 | v.setContentDescription(getResources().getString(R.string.suggestion_for_engine, |
michael@0 | 127 | mSearchEngine.name, suggestion)); |
michael@0 | 128 | } |
michael@0 | 129 | |
michael@0 | 130 | private String getSuggestionTextFromView(View v) { |
michael@0 | 131 | final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text); |
michael@0 | 132 | return suggestionText.getText().toString(); |
michael@0 | 133 | } |
michael@0 | 134 | |
michael@0 | 135 | private void setSuggestionOnView(View v, String suggestion) { |
michael@0 | 136 | final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text); |
michael@0 | 137 | suggestionText.setText(suggestion); |
michael@0 | 138 | setDescriptionOnSuggestion(suggestionText, suggestion); |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | /** |
michael@0 | 142 | * Perform a search for the user-entered term. |
michael@0 | 143 | */ |
michael@0 | 144 | public void performUserEnteredSearch() { |
michael@0 | 145 | String searchTerm = getSuggestionTextFromView(mUserEnteredView); |
michael@0 | 146 | if (mSearchListener != null) { |
michael@0 | 147 | Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.SUGGESTION, "user"); |
michael@0 | 148 | mSearchListener.onSearch(mSearchEngine, searchTerm); |
michael@0 | 149 | } |
michael@0 | 150 | } |
michael@0 | 151 | |
michael@0 | 152 | public void setSearchTerm(String searchTerm) { |
michael@0 | 153 | mUserEnteredTextView.setText(searchTerm); |
michael@0 | 154 | |
michael@0 | 155 | // mSearchEngine is not set in the first call to this method; the content description |
michael@0 | 156 | // is instead initially set in updateFromSearchEngine. |
michael@0 | 157 | if (mSearchEngine != null) { |
michael@0 | 158 | setDescriptionOnSuggestion(mUserEnteredTextView, searchTerm); |
michael@0 | 159 | } |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | public void setOnUrlOpenListener(OnUrlOpenListener listener) { |
michael@0 | 163 | mUrlOpenListener = listener; |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | public void setOnSearchListener(OnSearchListener listener) { |
michael@0 | 167 | mSearchListener = listener; |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | public void setOnEditSuggestionListener(OnEditSuggestionListener listener) { |
michael@0 | 171 | mEditSuggestionListener = listener; |
michael@0 | 172 | } |
michael@0 | 173 | |
michael@0 | 174 | public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) { |
michael@0 | 175 | // Update search engine reference. |
michael@0 | 176 | mSearchEngine = searchEngine; |
michael@0 | 177 | |
michael@0 | 178 | // Set the search engine icon (e.g., Google) for the row. |
michael@0 | 179 | mIconView.updateAndScaleImage(mSearchEngine.getIcon(), mSearchEngine.getEngineIdentifier()); |
michael@0 | 180 | |
michael@0 | 181 | // Set the initial content description. |
michael@0 | 182 | setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString()); |
michael@0 | 183 | |
michael@0 | 184 | // Add additional suggestions given by this engine. |
michael@0 | 185 | final int recycledSuggestionCount = mSuggestionView.getChildCount(); |
michael@0 | 186 | |
michael@0 | 187 | int suggestionCounter = 0; |
michael@0 | 188 | for (String suggestion : mSearchEngine.getSuggestions()) { |
michael@0 | 189 | final View suggestionItem; |
michael@0 | 190 | |
michael@0 | 191 | // Reuse suggestion views from recycled view, if possible. |
michael@0 | 192 | if (suggestionCounter + 1 < recycledSuggestionCount) { |
michael@0 | 193 | suggestionItem = mSuggestionView.getChildAt(suggestionCounter + 1); |
michael@0 | 194 | suggestionItem.setVisibility(View.VISIBLE); |
michael@0 | 195 | } else { |
michael@0 | 196 | suggestionItem = mInflater.inflate(R.layout.suggestion_item, null); |
michael@0 | 197 | |
michael@0 | 198 | suggestionItem.setOnClickListener(mClickListener); |
michael@0 | 199 | suggestionItem.setOnLongClickListener(mLongClickListener); |
michael@0 | 200 | |
michael@0 | 201 | final ImageView magnifier = |
michael@0 | 202 | (ImageView) suggestionItem.findViewById(R.id.suggestion_magnifier); |
michael@0 | 203 | magnifier.setVisibility(View.GONE); |
michael@0 | 204 | |
michael@0 | 205 | mSuggestionView.addView(suggestionItem); |
michael@0 | 206 | } |
michael@0 | 207 | |
michael@0 | 208 | setSuggestionOnView(suggestionItem, suggestion); |
michael@0 | 209 | |
michael@0 | 210 | if (animate) { |
michael@0 | 211 | AlphaAnimation anim = new AlphaAnimation(0, 1); |
michael@0 | 212 | anim.setDuration(ANIMATION_DURATION); |
michael@0 | 213 | anim.setStartOffset(suggestionCounter * ANIMATION_DURATION); |
michael@0 | 214 | suggestionItem.startAnimation(anim); |
michael@0 | 215 | } |
michael@0 | 216 | |
michael@0 | 217 | ++suggestionCounter; |
michael@0 | 218 | } |
michael@0 | 219 | |
michael@0 | 220 | // Hide extra suggestions that have been recycled. |
michael@0 | 221 | for (int i = suggestionCounter + 1; i < recycledSuggestionCount; ++i) { |
michael@0 | 222 | mSuggestionView.getChildAt(i).setVisibility(View.GONE); |
michael@0 | 223 | } |
michael@0 | 224 | |
michael@0 | 225 | // Make sure mSelectedView is still valid. |
michael@0 | 226 | if (mSelectedView >= mSuggestionView.getChildCount()) { |
michael@0 | 227 | mSelectedView = mSuggestionView.getChildCount() - 1; |
michael@0 | 228 | } |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | @Override |
michael@0 | 232 | public boolean onKeyDown(int keyCode, android.view.KeyEvent event) { |
michael@0 | 233 | final View suggestion = mSuggestionView.getChildAt(mSelectedView); |
michael@0 | 234 | |
michael@0 | 235 | if (event.getAction() != android.view.KeyEvent.ACTION_DOWN) { |
michael@0 | 236 | return false; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | switch (event.getKeyCode()) { |
michael@0 | 240 | case KeyEvent.KEYCODE_DPAD_RIGHT: |
michael@0 | 241 | final View nextSuggestion = mSuggestionView.getChildAt(mSelectedView + 1); |
michael@0 | 242 | if (nextSuggestion != null) { |
michael@0 | 243 | changeSelectedSuggestion(suggestion, nextSuggestion); |
michael@0 | 244 | mSelectedView++; |
michael@0 | 245 | return true; |
michael@0 | 246 | } |
michael@0 | 247 | break; |
michael@0 | 248 | |
michael@0 | 249 | case KeyEvent.KEYCODE_DPAD_LEFT: |
michael@0 | 250 | final View prevSuggestion = mSuggestionView.getChildAt(mSelectedView - 1); |
michael@0 | 251 | if (prevSuggestion != null) { |
michael@0 | 252 | changeSelectedSuggestion(suggestion, prevSuggestion); |
michael@0 | 253 | mSelectedView--; |
michael@0 | 254 | return true; |
michael@0 | 255 | } |
michael@0 | 256 | break; |
michael@0 | 257 | |
michael@0 | 258 | case KeyEvent.KEYCODE_BUTTON_A: |
michael@0 | 259 | // TODO: handle long pressing for editing suggestions |
michael@0 | 260 | return suggestion.performClick(); |
michael@0 | 261 | } |
michael@0 | 262 | |
michael@0 | 263 | return false; |
michael@0 | 264 | } |
michael@0 | 265 | |
michael@0 | 266 | private void changeSelectedSuggestion(View oldSuggestion, View newSuggestion) { |
michael@0 | 267 | oldSuggestion.setDuplicateParentStateEnabled(false); |
michael@0 | 268 | newSuggestion.setDuplicateParentStateEnabled(true); |
michael@0 | 269 | oldSuggestion.refreshDrawableState(); |
michael@0 | 270 | newSuggestion.refreshDrawableState(); |
michael@0 | 271 | } |
michael@0 | 272 | |
michael@0 | 273 | public void onSelected() { |
michael@0 | 274 | mSelectedView = 0; |
michael@0 | 275 | mUserEnteredView.setDuplicateParentStateEnabled(true); |
michael@0 | 276 | mUserEnteredView.refreshDrawableState(); |
michael@0 | 277 | } |
michael@0 | 278 | |
michael@0 | 279 | public void onDeselected() { |
michael@0 | 280 | final View suggestion = mSuggestionView.getChildAt(mSelectedView); |
michael@0 | 281 | suggestion.setDuplicateParentStateEnabled(false); |
michael@0 | 282 | suggestion.refreshDrawableState(); |
michael@0 | 283 | } |
michael@0 | 284 | } |