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

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/SuggestionView.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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.omnibox;
6
7 import static org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type.HISTORY _URL;
8
9 import android.annotation.SuppressLint;
10 import android.content.Context;
11 import android.content.DialogInterface;
12 import android.content.res.TypedArray;
13 import android.graphics.Bitmap;
14 import android.graphics.Canvas;
15 import android.graphics.Color;
16 import android.graphics.PorterDuff;
17 import android.graphics.drawable.Drawable;
18 import android.support.v7.app.AlertDialog;
19 import android.text.Spannable;
20 import android.text.SpannableString;
21 import android.text.TextPaint;
22 import android.text.TextUtils;
23 import android.text.style.StyleSpan;
24 import android.view.MotionEvent;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.ImageView;
28 import android.widget.TextView;
29 import android.widget.TextView.BufferType;
30
31 import com.google.android.apps.chrome.R;
32
33 import org.chromium.base.ApiCompatibilityUtils;
34 import org.chromium.base.metrics.RecordUserAction;
35 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultIt em;
36 import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggesti onDelegate;
37 import org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type;
38 import org.chromium.chrome.browser.widget.TintedDrawable;
39 import org.chromium.ui.base.DeviceFormFactor;
40
41 import java.util.Locale;
42
43 /**
44 * Container view for omnibox suggestions made very specific for omnibox suggest ions to minimize
45 * any unnecessary measures and layouts.
46 */
47 class SuggestionView extends ViewGroup {
48 private enum SuggestionIconType {
49 BOOKMARK,
50 HISTORY,
51 GLOBE,
52 MAGNIFIER,
53 VOICE
54 }
55
56 private static final int FIRST_LINE_TEXT_SIZE_SP = 17;
57 private static final int SECOND_LINE_TEXT_SIZE_SP = 14;
58
59 private static final long RELAYOUT_DELAY_MS = 20;
60
61 private static final int TITLE_COLOR_STANDARD_FONT_DARK = Color.rgb(51, 51, 51);
62 private static final int TITLE_COLOR_STANDARD_FONT_LIGHT = Color.rgb(255, 25 5, 255);
63 private static final int URL_COLOR = Color.rgb(85, 149, 254);
64
65 private static final int ANSWER_IMAGE_HORIZONTAL_SPACING_DP = 4;
66 private static final int ANSWER_IMAGE_VERTICAL_SPACING_DP = 5;
67 private static final float ANSWER_IMAGE_SCALING_FACTOR = 1.15f;
68
69 private LocationBar mLocationBar;
70 private UrlBar mUrlBar;
71 private ImageView mNavigationButton;
72
73 private int mSuggestionHeight;
74 private int mSuggestionAnswerHeight;
75
76 private OmniboxResultItem mSuggestionItem;
77 private OmniboxSuggestion mSuggestion;
78 private OmniboxSuggestionDelegate mSuggestionDelegate;
79 private Boolean mUseDarkColors;
80 private int mPosition;
81
82 private SuggestionContentsContainer mContentsView;
83
84 private int mRefineWidth;
85 private View mRefineView;
86 private TintedDrawable mRefineIcon;
87
88 private final int[] mViewPositionHolder = new int[2];
89
90 /**
91 * Constructs a new omnibox suggestion view.
92 *
93 * @param context The context used to construct the suggestion view.
94 * @param locationBar The location bar showing these suggestions.
95 */
96 public SuggestionView(Context context, LocationBar locationBar) {
97 super(context);
98 mLocationBar = locationBar;
99
100 mSuggestionHeight =
101 context.getResources().getDimensionPixelOffset(R.dimen.omnibox_s uggestion_height);
102 mSuggestionAnswerHeight =
103 context.getResources().getDimensionPixelOffset(
104 R.dimen.omnibox_suggestion_answer_height);
105
106 TypedArray a = getContext().obtainStyledAttributes(
107 new int [] {R.attr.selectableItemBackground});
108 Drawable itemBackground = a.getDrawable(0);
109 a.recycle();
110
111 mContentsView = new SuggestionContentsContainer(context, itemBackground) ;
112 addView(mContentsView);
113
114 mRefineView = new View(context) {
115 @Override
116 protected void onDraw(Canvas canvas) {
117 super.onDraw(canvas);
118
119 if (mRefineIcon == null) return;
120 canvas.save();
121 canvas.translate(
122 (getMeasuredWidth() - mRefineIcon.getIntrinsicWidth()) / 2f,
123 (getMeasuredHeight() - mRefineIcon.getIntrinsicHeight()) / 2f);
124 mRefineIcon.draw(canvas);
125 canvas.restore();
126 }
127
128 @Override
129 public void setVisibility(int visibility) {
130 super.setVisibility(visibility);
131
132 if (visibility == VISIBLE) {
133 setClickable(true);
134 setFocusable(true);
135 } else {
136 setClickable(false);
137 setFocusable(false);
138 }
139 }
140
141 @Override
142 protected void drawableStateChanged() {
143 super.drawableStateChanged();
144
145 if (mRefineIcon != null && mRefineIcon.isStateful()) {
146 mRefineIcon.setState(getDrawableState());
147 }
148 }
149 };
150 mRefineView.setContentDescription(getContext().getString(
151 R.string.accessibility_omnibox_btn_refine));
152
153 // Although this has the same background as the suggestion view, it can not be shared as
154 // it will result in the state of the drawable being shared and always s howing up in the
155 // refine view.
156 mRefineView.setBackground(itemBackground.getConstantState().newDrawable( ));
157 mRefineView.setId(R.id.refine_view_id);
158 mRefineView.setClickable(true);
159 mRefineView.setFocusable(true);
160 mRefineView.setLayoutParams(new LayoutParams(0, 0));
161 addView(mRefineView);
162
163 mRefineWidth = (int) (getResources().getDisplayMetrics().density * 48);
164
165 mUrlBar = (UrlBar) locationBar.getContainerView().findViewById(R.id.url_ bar);
166 }
167
168 @Override
169 protected void onLayout(boolean changed, int left, int top, int right, int b ottom) {
170 if (getMeasuredWidth() == 0) return;
171
172 if (mSuggestion.getType() != Type.SEARCH_SUGGEST_TAIL) {
173 mContentsView.resetTextWidths();
174 }
175
176 boolean refineVisible = mRefineView.getVisibility() == VISIBLE;
177 boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
178 int contentsViewOffsetX = isRtl ? mRefineWidth : 0;
179 if (!refineVisible) contentsViewOffsetX = 0;
180 mContentsView.layout(
181 contentsViewOffsetX,
182 0,
183 contentsViewOffsetX + mContentsView.getMeasuredWidth(),
184 mContentsView.getMeasuredHeight());
185 int refineViewOffsetX = isRtl ? 0 : getMeasuredWidth() - mRefineWidth;
186 mRefineView.layout(
187 refineViewOffsetX,
188 0,
189 refineViewOffsetX + mRefineWidth,
190 mContentsView.getMeasuredHeight());
191 }
192
193 @Override
194 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
195 int width = MeasureSpec.getSize(widthMeasureSpec);
196 int height = mSuggestionHeight;
197 if (!TextUtils.isEmpty(mSuggestion.getAnswerContents())) {
198 height = mSuggestionAnswerHeight;
199 }
200 setMeasuredDimension(width, height);
201
202 // The width will be specified as 0 when determining the height of the p opup, so exit early
203 // after setting the height.
204 if (width == 0) return;
205
206 boolean refineVisible = mRefineView.getVisibility() == VISIBLE;
207 int refineWidth = refineVisible ? mRefineWidth : 0;
208 mContentsView.measure(
209 MeasureSpec.makeMeasureSpec(width - refineWidth, MeasureSpec.EXA CTLY),
210 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
211 mContentsView.getLayoutParams().width = mContentsView.getMeasuredWidth() ;
212 mContentsView.getLayoutParams().height = mContentsView.getMeasuredHeight ();
213
214 mRefineView.measure(
215 MeasureSpec.makeMeasureSpec(mRefineWidth, MeasureSpec.EXACTLY),
216 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
217 mRefineView.getLayoutParams().width = mRefineView.getMeasuredWidth();
218 mRefineView.getLayoutParams().height = mRefineView.getMeasuredHeight();
219 }
220
221 @Override
222 public void invalidate() {
223 super.invalidate();
224 mContentsView.invalidate();
225 }
226
227 @Override
228 public boolean dispatchTouchEvent(MotionEvent ev) {
229 // Whenever the suggestion dropdown is touched, we dispatch onGestureDow n which is
230 // used to let autocomplete controller know that it should stop updating suggestions.
231 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) mSuggestionDelegate .onGestureDown();
232 return super.dispatchTouchEvent(ev);
233 }
234
235 /**
236 * Sets the contents and state of the view for the given suggestion.
237 *
238 * @param suggestionItem The omnibox suggestion item this view represents.
239 * @param suggestionDelegate The suggestion delegate.
240 * @param position Position of the suggestion in the dropdown list.
241 * @param useDarkColors Whether dark colors should be used for fonts and ico ns.
242 */
243 public void init(OmniboxResultItem suggestionItem,
244 OmniboxSuggestionDelegate suggestionDelegate,
245 int position, boolean useDarkColors) {
246 // Update the position unconditionally.
247 mPosition = position;
248 jumpDrawablesToCurrentState();
249 boolean colorsChanged = mUseDarkColors == null || mUseDarkColors != useD arkColors;
250 if (suggestionItem.equals(mSuggestionItem) && !colorsChanged) return;
251 mUseDarkColors = useDarkColors;
252 if (colorsChanged) {
253 mContentsView.mTextLine1.setTextColor(getStandardFontColor());
254 setRefineIcon(true);
255 }
256
257 mSuggestionItem = suggestionItem;
258 mSuggestion = suggestionItem.getSuggestion();
259 mSuggestionDelegate = suggestionDelegate;
260 // Reset old computations.
261 mContentsView.resetTextWidths();
262 mContentsView.mAnswerImage.setVisibility(GONE);
263 mContentsView.mAnswerImage.getLayoutParams().height = 0;
264 mContentsView.mAnswerImage.getLayoutParams().width = 0;
265 mContentsView.mAnswerImage.setImageDrawable(null);
266 mContentsView.mAnswerImageMaxSize = 0;
267 mContentsView.mTextLine1.setTextSize(FIRST_LINE_TEXT_SIZE_SP);
268 mContentsView.mTextLine2.setTextSize(SECOND_LINE_TEXT_SIZE_SP);
269
270 // Suggestions with attached answers are rendered with rich results rega rdless of which
271 // suggestion type they are.
272 if (mSuggestion.hasAnswer()) {
273 setAnswer(mSuggestion.getAnswer());
274 mContentsView.setSuggestionIcon(SuggestionIconType.MAGNIFIER, colors Changed);
275 mContentsView.mTextLine2.setVisibility(VISIBLE);
276 setRefinable(true);
277 return;
278 }
279
280 boolean sameAsTyped =
281 suggestionItem.getMatchedQuery().equalsIgnoreCase(mSuggestion.ge tDisplayText());
282 Type suggestionType = mSuggestion.getType();
283 switch (suggestionType) {
284 case HISTORY_URL:
285 case URL_WHAT_YOU_TYPED:
286 case NAVSUGGEST:
287 case HISTORY_TITLE:
288 case HISTORY_BODY:
289 case HISTORY_KEYWORD:
290 case OPEN_HISTORY_PAGE:
291 if (mSuggestion.isStarred()) {
292 mContentsView.setSuggestionIcon(SuggestionIconType.BOOKMARK, colorsChanged);
293 } else if (suggestionType == HISTORY_URL) {
294 mContentsView.setSuggestionIcon(SuggestionIconType.HISTORY, colorsChanged);
295 } else {
296 mContentsView.setSuggestionIcon(SuggestionIconType.GLOBE, co lorsChanged);
297 }
298 boolean urlShown = !TextUtils.isEmpty(mSuggestion.getUrl());
299 boolean urlHighlighted = false;
300 if (urlShown) {
301 urlHighlighted = setUrlText(suggestionItem);
302 } else {
303 mContentsView.mTextLine2.setVisibility(INVISIBLE);
304 }
305 setSuggestedQuery(suggestionItem, true, urlShown, urlHighlighted );
306 setRefinable(!sameAsTyped);
307 break;
308 case SEARCH_WHAT_YOU_TYPED:
309 case SEARCH_HISTORY:
310 case SEARCH_SUGGEST:
311 case SEARCH_OTHER_ENGINE:
312 case SEARCH_SUGGEST_ENTITY:
313 case SEARCH_SUGGEST_TAIL:
314 case SEARCH_SUGGEST_PERSONALIZED:
315 case SEARCH_SUGGEST_PROFILE:
316 case VOICE_SUGGEST:
317 SuggestionIconType suggestionIcon = SuggestionIconType.MAGNIFIER ;
318 if (suggestionType == Type.VOICE_SUGGEST) {
319 suggestionIcon = SuggestionIconType.VOICE;
320 } else if ((suggestionType == Type.SEARCH_SUGGEST_PERSONALIZED)
321 || (suggestionType == Type.SEARCH_HISTORY)) {
322 // Show history icon for suggestions based on user queries.
323 suggestionIcon = SuggestionIconType.HISTORY;
324 }
325 mContentsView.setSuggestionIcon(suggestionIcon, colorsChanged);
326 setRefinable(!sameAsTyped);
327 setSuggestedQuery(suggestionItem, false, false, false);
328 if ((suggestionType == Type.SEARCH_SUGGEST_ENTITY)
329 || (suggestionType == Type.SEARCH_SUGGEST_PROFILE)) {
330 showDescriptionLine(
331 SpannableString.valueOf(mSuggestion.getDescription() ),
332 getStandardFontColor());
333 } else {
334 mContentsView.mTextLine2.setVisibility(INVISIBLE);
335 }
336 break;
337 default:
338 assert false : "Suggestion type (" + mSuggestion.getType() + ") is not handled";
339 break;
340 }
341 }
342
343 private void setRefinable(boolean refinable) {
344 if (refinable) {
345 mRefineView.setVisibility(VISIBLE);
346 mRefineView.setOnClickListener(new OnClickListener() {
347 @Override
348 public void onClick(View v) {
349 // Post the refine action to the end of the UI thread to all ow the refine view
350 // a chance to update its background selection state.
351 PerformRefineSuggestion performRefine = new PerformRefineSug gestion();
352 if (!post(performRefine)) performRefine.run();
353 }
354 });
355 } else {
356 mRefineView.setOnClickListener(null);
357 mRefineView.setVisibility(GONE);
358 }
359 }
360
361 private int getStandardFontColor() {
362 return (mUseDarkColors == null || mUseDarkColors)
363 ? TITLE_COLOR_STANDARD_FONT_DARK : TITLE_COLOR_STANDARD_FONT_LIG HT;
364 }
365
366 @Override
367 public void setSelected(boolean selected) {
368 super.setSelected(selected);
369 if (selected && !isInTouchMode()) {
370 mSuggestionDelegate.onSetUrlToSuggestion(mSuggestion);
371 }
372 }
373
374 private void setRefineIcon(boolean invalidateIcon) {
375 if (!invalidateIcon && mRefineIcon != null) return;
376
377 mRefineIcon = TintedDrawable.constructTintedDrawable(
378 getResources(), R.drawable.btn_suggestion_refine);
379 mRefineIcon.setTint(getResources().getColorStateList(mUseDarkColors
380 ? R.color.dark_mode_tint
381 : R.color.light_mode_tint));
382 mRefineIcon.setBounds(
383 0, 0,
384 mRefineIcon.getIntrinsicWidth(),
385 mRefineIcon.getIntrinsicHeight());
386 mRefineIcon.setState(mRefineView.getDrawableState());
387 mRefineView.postInvalidateOnAnimation();
388 }
389
390 /**
391 * Sets (and highlights) the URL text of the second line of the omnibox sugg estion.
392 *
393 * @param suggestion The suggestion containing the URL.
394 * @return Whether the URL was highlighted based on the user query.
395 */
396 private boolean setUrlText(OmniboxResultItem suggestion) {
397 String query = suggestion.getMatchedQuery();
398 String url = suggestion.getSuggestion().getFormattedUrl();
399 int index = url.indexOf(query);
400 Spannable str = SpannableString.valueOf(url);
401 if (index >= 0) {
402 // Bold the part of the URL that matches the user query.
403 str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
404 index, index + query.length(), Spannable.SPAN_EXCLUSIVE_EXCL USIVE);
405 }
406 showDescriptionLine(str, URL_COLOR);
407 return index >= 0;
408 }
409
410 /**
411 * Sets a description line for the omnibox suggestion.
412 *
413 * @param str The description text.
414 */
415 private void showDescriptionLine(Spannable str, int textColor) {
416 if (mContentsView.mTextLine2.getVisibility() != VISIBLE) {
417 mContentsView.mTextLine2.setVisibility(VISIBLE);
418 }
419 mContentsView.mTextLine2.setTextColor(textColor);
420 mContentsView.mTextLine2.setText(str, BufferType.SPANNABLE);
421 }
422
423 /**
424 * Sets the text of the first line of the omnibox suggestion.
425 *
426 * @param suggestionItem The item containing the suggestion data.
427 * @param showDescriptionIfPresent Whether to show the description text of t he suggestion if
428 * the item contains valid data.
429 * @param isUrlQuery Whether this suggestion is showing an URL.
430 * @param isUrlHighlighted Whether the URL contains any highlighted matching sections.
431 */
432 private void setSuggestedQuery(
433 OmniboxResultItem suggestionItem, boolean showDescriptionIfPresent,
434 boolean isUrlQuery, boolean isUrlHighlighted) {
435 String userQuery = suggestionItem.getMatchedQuery();
436 String suggestedQuery = null;
437 OmniboxSuggestion suggestion = suggestionItem.getSuggestion();
438 if (showDescriptionIfPresent && !TextUtils.isEmpty(suggestion.getUrl())
439 && !TextUtils.isEmpty(suggestion.getDescription())) {
440 suggestedQuery = suggestion.getDescription();
441 } else {
442 suggestedQuery = suggestion.getDisplayText();
443 }
444 if (suggestedQuery == null) {
445 assert false : "Invalid suggestion sent with no displayable text";
446 suggestedQuery = "";
447 } else if (suggestedQuery.equals(suggestion.getUrl())) {
448 // This is a navigation match with the title defaulted to the URL, d isplay formatted URL
449 // so that they continue matching.
450 suggestedQuery = suggestion.getFormattedUrl();
451 }
452
453 if (mSuggestion.getType() == Type.SEARCH_SUGGEST_TAIL) {
454 String fillIntoEdit = mSuggestion.getFillIntoEdit();
455 // Data sanity checks.
456 if (fillIntoEdit.startsWith(userQuery)
457 && fillIntoEdit.endsWith(suggestedQuery)
458 && fillIntoEdit.length() < userQuery.length() + suggestedQue ry.length()) {
459 String ignoredPrefix = fillIntoEdit.substring(
460 0, fillIntoEdit.length() - suggestedQuery.length());
461 final String ellipsisPrefix = "\u2026 ";
462 suggestedQuery = ellipsisPrefix + suggestedQuery;
463 if (userQuery.startsWith(ignoredPrefix)) {
464 userQuery = ellipsisPrefix + userQuery.substring(ignoredPref ix.length());
465 }
466 if (DeviceFormFactor.isTablet(getContext())) {
467 TextPaint tp = mContentsView.mTextLine1.getPaint();
468 mContentsView.mRequiredWidth =
469 tp.measureText(fillIntoEdit, 0, fillIntoEdit.length( ));
470 mContentsView.mMatchContentsWidth =
471 tp.measureText(suggestedQuery, 0, suggestedQuery.len gth());
472
473 // Update the max text widths values in SuggestionList. Thes e will be passed to
474 // the contents view on layout.
475 mSuggestionDelegate.onTextWidthsUpdated(
476 mContentsView.mRequiredWidth, mContentsView.mMatchCo ntentsWidth);
477 }
478 }
479 }
480
481 Spannable str = SpannableString.valueOf(suggestedQuery);
482 int userQueryIndex = isUrlHighlighted ? -1
483 : suggestedQuery.toLowerCase(Locale.getDefault()).indexOf(
484 userQuery.toLowerCase(Locale.getDefault()));
485 if (userQueryIndex != -1) {
486 int spanStart = 0;
487 int spanEnd = 0;
488 if (isUrlQuery) {
489 spanStart = userQueryIndex;
490 spanEnd = userQueryIndex + userQuery.length();
491 } else {
492 spanStart = userQueryIndex + userQuery.length();
493 spanEnd = str.length();
494 }
495 spanStart = Math.min(spanStart, str.length());
496 spanEnd = Math.min(spanEnd, str.length());
497 if (spanStart != spanEnd) {
498 str.setSpan(
499 new StyleSpan(android.graphics.Typeface.BOLD),
500 spanStart, spanEnd,
501 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
502 }
503 }
504 mContentsView.mTextLine1.setText(str, BufferType.SPANNABLE);
505 }
506
507 /**
508 * Sets both lines of the Omnibox suggestion based on an Answers in Suggest result.
509 *
510 * @param answer The answer to be displayed.
511 */
512 private void setAnswer(SuggestionAnswer answer) {
513 float density = getResources().getDisplayMetrics().density;
514
515 SuggestionAnswer.ImageLine firstLine = answer.getFirstLine();
516 mContentsView.mTextLine1.setTextSize(AnswerTextBuilder.getMaxTextHeightS p(firstLine));
517 Spannable firstLineText = AnswerTextBuilder.buildSpannable(
518 firstLine, mContentsView.mTextLine1.getPaint().getFontMetrics(), density);
519 mContentsView.mTextLine1.setText(firstLineText, BufferType.SPANNABLE);
520
521 SuggestionAnswer.ImageLine secondLine = answer.getSecondLine();
522 mContentsView.mTextLine2.setTextSize(AnswerTextBuilder.getMaxTextHeightS p(secondLine));
523 Spannable secondLineText = AnswerTextBuilder.buildSpannable(
524 secondLine, mContentsView.mTextLine2.getPaint().getFontMetrics() , density);
525 mContentsView.mTextLine2.setText(secondLineText, BufferType.SPANNABLE);
526
527 if (secondLine.hasImage()) {
528 mContentsView.mAnswerImage.setVisibility(VISIBLE);
529
530 float textSize = mContentsView.mTextLine2.getTextSize();
531 int imageSize = (int) (textSize * ANSWER_IMAGE_SCALING_FACTOR);
532 mContentsView.mAnswerImage.getLayoutParams().height = imageSize;
533 mContentsView.mAnswerImage.getLayoutParams().width = imageSize;
534 mContentsView.mAnswerImageMaxSize = imageSize;
535
536 String url = "https:" + secondLine.getImage().replace("\\/", "/");
537 AnswersImage.requestAnswersImage(
538 mLocationBar.getCurrentTab().getProfile(),
539 url,
540 new AnswersImage.AnswersImageObserver() {
541 @Override
542 public void onAnswersImageChanged(Bitmap bitmap) {
543 mContentsView.mAnswerImage.setImageBitmap(bitmap);
544 }
545 });
546 }
547 }
548
549 /**
550 * Handles triggering a selection request for the suggestion rendered by thi s view.
551 */
552 private class PerformSelectSuggestion implements Runnable {
553 @Override
554 public void run() {
555 mSuggestionDelegate.onSelection(mSuggestion, mPosition);
556 }
557 }
558
559 /**
560 * Handles triggering a refine request for the suggestion rendered by this v iew.
561 */
562 private class PerformRefineSuggestion implements Runnable {
563 @Override
564 public void run() {
565 mSuggestionDelegate.onRefineSuggestion(mSuggestion);
566 }
567 }
568
569 /**
570 * @return The left offset for the suggestion text that will align it with t he url text.
571 */
572 private int getUrlBarTextLeftPosition() {
573 mUrlBar.getLocationOnScreen(mViewPositionHolder);
574 return mViewPositionHolder[0] + mUrlBar.getPaddingLeft();
575 }
576
577 /**
578 * @return The right offset for the suggestion text that will align it with the url text.
579 */
580 private int getUrlBarTextRightPosition() {
581 mUrlBar.getLocationOnScreen(mViewPositionHolder);
582 return mViewPositionHolder[0] + mUrlBar.getWidth() - mUrlBar.getPaddingR ight();
583 }
584
585 /**
586 * Container view for the contents of the suggestion (the search query, URL, and suggestion type
587 * icon).
588 */
589 private class SuggestionContentsContainer extends ViewGroup implements OnLay outChangeListener {
590 private int mSuggestionIconLeft = Integer.MIN_VALUE;
591 private int mTextLeft = Integer.MIN_VALUE;
592 private int mTextRight = Integer.MIN_VALUE;
593 private Drawable mSuggestionIcon;
594 private SuggestionIconType mSuggestionIconType;
595
596 private final TextView mTextLine1;
597 private final TextView mTextLine2;
598 private final ImageView mAnswerImage;
599
600 private int mAnswerImageMaxSize; // getMaxWidth() is API 16+, so store it locally.
601 private float mRequiredWidth;
602 private float mMatchContentsWidth;
603 private boolean mForceIsFocused;
604
605 private final Runnable mRelayoutRunnable = new Runnable() {
606 @Override
607 public void run() {
608 requestLayout();
609 }
610 };
611
612 @SuppressLint("InlinedApi")
613 SuggestionContentsContainer(Context context, Drawable backgroundDrawable ) {
614 super(context);
615
616 ApiCompatibilityUtils.setLayoutDirection(this, View.LAYOUT_DIRECTION _INHERIT);
617
618 setBackground(backgroundDrawable);
619 setClickable(true);
620 setFocusable(true);
621 setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, mSuggest ionHeight));
622 setOnClickListener(new OnClickListener() {
623 @Override
624 public void onClick(View v) {
625 // Post the selection action to the end of the UI thread to allow the suggestion
626 // view a chance to update their background selection state.
627 PerformSelectSuggestion performSelection = new PerformSelect Suggestion();
628 if (!post(performSelection)) performSelection.run();
629 }
630 });
631 setOnLongClickListener(new OnLongClickListener() {
632 @Override
633 public boolean onLongClick(View v) {
634 RecordUserAction.record("MobileOmniboxDeleteGesture");
635 if (!mSuggestion.isDeletable()) return true;
636
637 AlertDialog.Builder b =
638 new AlertDialog.Builder(getContext(), R.style.AlertD ialogTheme);
639 b.setTitle(mSuggestion.getDisplayText());
640 b.setMessage(R.string.omnibox_confirm_delete);
641 DialogInterface.OnClickListener okListener =
642 new DialogInterface.OnClickListener() {
643 @Override
644 public void onClick(DialogInterface dialog, int which) {
645 RecordUserAction.record("MobileOmniboxDelete Requested");
646 mSuggestionDelegate.onDeleteSuggestion(mPosi tion);
647 }
648 };
649 b.setPositiveButton(android.R.string.ok, okListener);
650 DialogInterface.OnClickListener cancelListener =
651 new DialogInterface.OnClickListener() {
652 @Override
653 public void onClick(DialogInterface dialog, int which) {
654 dialog.cancel();
655 }
656 };
657 b.setNegativeButton(android.R.string.cancel, cancelListener) ;
658
659 AlertDialog dialog = b.create();
660 dialog.setOnDismissListener(new DialogInterface.OnDismissLis tener() {
661 @Override
662 public void onDismiss(DialogInterface dialog) {
663 mSuggestionDelegate.onHideModal();
664 }
665 });
666
667 mSuggestionDelegate.onShowModal();
668 dialog.show();
669 return true;
670 }
671 });
672
673 mTextLine1 = new TextView(context);
674 mTextLine1.setLayoutParams(
675 new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeigh t));
676 mTextLine1.setSingleLine();
677 mTextLine1.setTextColor(getStandardFontColor());
678 addView(mTextLine1);
679
680 mTextLine2 = new TextView(context);
681 mTextLine2.setLayoutParams(
682 new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeigh t));
683 mTextLine2.setSingleLine();
684 mTextLine2.setVisibility(INVISIBLE);
685 addView(mTextLine2);
686
687 mAnswerImage = new ImageView(context);
688 mAnswerImage.setVisibility(GONE);
689 mAnswerImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
690 mAnswerImage.setLayoutParams(new LayoutParams(0, 0));
691 mAnswerImageMaxSize = 0;
692 addView(mAnswerImage);
693 }
694
695 private void resetTextWidths() {
696 mRequiredWidth = 0;
697 mMatchContentsWidth = 0;
698 }
699
700 @Override
701 protected void onDraw(Canvas canvas) {
702 super.onDraw(canvas);
703
704 if (DeviceFormFactor.isTablet(getContext())) {
705 // Use the same image transform matrix as the navigation icon to ensure the same
706 // scaling, which requires centering vertically based on the hei ght of the
707 // navigation icon view and not the image itself.
708 canvas.save();
709 mSuggestionIconLeft = getSuggestionIconLeftPosition();
710 canvas.translate(
711 mSuggestionIconLeft,
712 (getMeasuredHeight() - mNavigationButton.getMeasuredHeig ht()) / 2f);
713 canvas.concat(mNavigationButton.getImageMatrix());
714 mSuggestionIcon.draw(canvas);
715 canvas.restore();
716 }
717 }
718
719 @Override
720 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
721 if (child != mTextLine1 && child != mTextLine2 && child != mAnswerIm age) {
722 return super.drawChild(canvas, child, drawingTime);
723 }
724
725 int height = getMeasuredHeight();
726 int line1Height = mTextLine1.getMeasuredHeight();
727 int line2Height = mTextLine2.getVisibility() == VISIBLE
728 ? mTextLine2.getMeasuredHeight() : 0;
729
730 int verticalOffset = 0;
731 if (line1Height + line2Height > height) {
732 // The text lines total height is larger than this view, snap th em to the top and
733 // bottom of the view.
734 if (child == mTextLine1) {
735 verticalOffset = 0;
736 } else {
737 verticalOffset = height - line2Height;
738 }
739 } else {
740 // The text lines fit comfortably, so vertically center them.
741 verticalOffset = (height - line1Height - line2Height) / 2;
742 if (child == mTextLine2) verticalOffset += line1Height;
743
744 // When one line is larger than the other, it contains extra ver tical padding. This
745 // produces more apparent whitespace above or below the text lin es. Add a small
746 // offset to compensate.
747 if (line1Height != line2Height) {
748 verticalOffset += (line2Height - line1Height) / 10;
749 }
750
751 // The image is positioned vertically aligned with the second te xt line but
752 // requires a small additional offset to align with the ascent o f the text instead
753 // of the top of the text which includes some whitespace.
754 if (child == mAnswerImage) {
755 verticalOffset += ANSWER_IMAGE_VERTICAL_SPACING_DP
756 * getResources().getDisplayMetrics().density;
757 }
758 }
759
760 canvas.save();
761 canvas.translate(0, verticalOffset);
762 boolean retVal = super.drawChild(canvas, child, drawingTime);
763 canvas.restore();
764 return retVal;
765 }
766
767 @Override
768 protected void onLayout(boolean changed, int l, int t, int r, int b) {
769 View locationBarView = mLocationBar.getContainerView();
770 if (mUrlBar == null) {
771 mUrlBar = (UrlBar) locationBarView.findViewById(R.id.url_bar);
772 mUrlBar.addOnLayoutChangeListener(this);
773 }
774 if (mNavigationButton == null) {
775 mNavigationButton =
776 (ImageView) locationBarView.findViewById(R.id.navigation _button);
777 mNavigationButton.addOnLayoutChangeListener(this);
778 }
779
780 // Align the text to be pixel perfectly aligned with the text in the url bar.
781 mTextLeft = getSuggestionTextLeftPosition();
782 mTextRight = getSuggestionTextRightPosition();
783 boolean isRTL = ApiCompatibilityUtils.isLayoutRtl(this);
784 if (DeviceFormFactor.isTablet(getContext())) {
785 int textWidth = isRTL ? mTextRight : (r - l - mTextLeft);
786 final float maxRequiredWidth = mSuggestionDelegate.getMaxRequire dWidth();
787 final float maxMatchContentsWidth = mSuggestionDelegate.getMaxMa tchContentsWidth();
788 float paddingStart = (textWidth > maxRequiredWidth)
789 ? (mRequiredWidth - mMatchContentsWidth)
790 : Math.max(textWidth - maxMatchContentsWidth, 0);
791 ApiCompatibilityUtils.setPaddingRelative(
792 mTextLine1, (int) paddingStart, mTextLine1.getPaddingTop (),
793 0, // TODO(skanuj) : Change to ApiCompatibilityUtils.get PaddingEnd(...).
794 mTextLine1.getPaddingBottom());
795 }
796
797 int imageWidth = mAnswerImageMaxSize;
798 int imageSpacing = 0;
799 if (mAnswerImage.getVisibility() == VISIBLE && imageWidth > 0) {
800 float density = getResources().getDisplayMetrics().density;
801 imageSpacing = (int) (ANSWER_IMAGE_HORIZONTAL_SPACING_DP * densi ty);
802 }
803 if (isRTL) {
804 mTextLine1.layout(0, t, mTextRight, b);
805 mAnswerImage.layout(mTextRight - imageWidth , t, mTextRight, b);
806 mTextLine2.layout(0, t, mTextRight - (imageWidth + imageSpacing) , b);
807 } else {
808 mTextLine1.layout(mTextLeft, t, r - l, b);
809 mAnswerImage.layout(mTextLeft, t, mTextLeft + imageWidth, b);
810 mTextLine2.layout(mTextLeft + imageWidth + imageSpacing, t, r - l, b);
811 }
812
813 int suggestionIconPosition = getSuggestionIconLeftPosition();
814 if (mSuggestionIconLeft != suggestionIconPosition
815 && mSuggestionIconLeft != Integer.MIN_VALUE) {
816 mContentsView.postInvalidateOnAnimation();
817 }
818 mSuggestionIconLeft = suggestionIconPosition;
819 }
820
821 /**
822 * @return The left offset for the suggestion text that will align it wi th the url text.
823 */
824 private int getSuggestionTextLeftPosition() {
825 if (mLocationBar == null) return 0;
826
827 int textLeftPosition = getUrlBarTextLeftPosition();
828 getLocationOnScreen(mViewPositionHolder);
829 return textLeftPosition - mViewPositionHolder[0];
830 }
831
832 /**
833 * @return The right offset for the suggestion text that will align it w ith the url text.
834 */
835 private int getSuggestionTextRightPosition() {
836 if (mLocationBar == null) return 0;
837
838 int textRightPosition = getUrlBarTextRightPosition();
839 getLocationOnScreen(mViewPositionHolder);
840 return textRightPosition - mViewPositionHolder[0];
841 }
842
843 /**
844 * @return The left offset for the suggestion type icon that aligns it w ith the url bar.
845 */
846 private int getSuggestionIconLeftPosition() {
847 if (mNavigationButton == null) return 0;
848
849 // Ensure the suggestion icon matches the location of the navigation icon in the omnibox
850 // perfectly.
851 mNavigationButton.getLocationOnScreen(mViewPositionHolder);
852 int navButtonXPosition = mViewPositionHolder[0] + mNavigationButton. getPaddingLeft();
853
854 getLocationOnScreen(mViewPositionHolder);
855
856 return navButtonXPosition - mViewPositionHolder[0];
857 }
858
859 @Override
860 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
861 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
862
863 int width = MeasureSpec.getSize(widthMeasureSpec);
864 int height = MeasureSpec.getSize(heightMeasureSpec);
865
866 if (mTextLine1.getMeasuredWidth() != width
867 || mTextLine1.getMeasuredHeight() != height) {
868 mTextLine1.measure(
869 MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpe c.AT_MOST),
870 MeasureSpec.makeMeasureSpec(mSuggestionHeight, MeasureSp ec.AT_MOST));
871 }
872
873 if (mTextLine2.getMeasuredWidth() != width
874 || mTextLine2.getMeasuredHeight() != height) {
875 mTextLine2.measure(
876 MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpe c.AT_MOST),
877 MeasureSpec.makeMeasureSpec(mSuggestionHeight, MeasureSp ec.AT_MOST));
878 }
879 }
880
881 @Override
882 public void invalidate() {
883 if (getSuggestionTextLeftPosition() != mTextLeft
884 || getSuggestionTextRightPosition() != mTextRight) {
885 // When the text position is changed, it typically is caused by the suggestions
886 // appearing while the URL bar on the phone is gaining focus (if you trigger an
887 // intent that will result in suggestions being shown before foc using the omnibox).
888 // Triggering a relayout will cause any animations to stutter, s o we continually
889 // push the relayout to end of the UI queue until the animation is complete.
890 removeCallbacks(mRelayoutRunnable);
891 postDelayed(mRelayoutRunnable, RELAYOUT_DELAY_MS);
892 } else {
893 super.invalidate();
894 }
895 }
896
897 @Override
898 public boolean isFocused() {
899 return mForceIsFocused || super.isFocused();
900 }
901
902 @Override
903 protected int[] onCreateDrawableState(int extraSpace) {
904 // When creating the drawable states, treat selected as focused to g et the proper
905 // highlight when in non-touch mode (i.e. physical keyboard). This is because only
906 // a single view in a window can have focus, and the these will only appear if
907 // the omnibox has focus, so we trick the drawable state into believ ing it has it.
908 mForceIsFocused = isSelected() && !isInTouchMode();
909 int[] drawableState = super.onCreateDrawableState(extraSpace);
910 mForceIsFocused = false;
911 return drawableState;
912 }
913
914 private void setSuggestionIcon(SuggestionIconType type, boolean invalida teCurrentIcon) {
915 if (mSuggestionIconType == type && !invalidateCurrentIcon) return;
916
917 int drawableId = R.drawable.ic_omnibox_page;
918 switch (type) {
919 case BOOKMARK:
920 drawableId = R.drawable.btn_star;
921 break;
922 case MAGNIFIER:
923 drawableId = R.drawable.ic_suggestion_magnifier;
924 break;
925 case HISTORY:
926 drawableId = R.drawable.ic_suggestion_history;
927 break;
928 case VOICE:
929 drawableId = R.drawable.btn_mic;
930 break;
931 default:
932 break;
933 }
934 mSuggestionIcon = ApiCompatibilityUtils.getDrawable(getResources(), drawableId);
935 mSuggestionIcon.setColorFilter(mUseDarkColors
936 ? getResources().getColor(R.color.light_normal_color)
937 : Color.WHITE, PorterDuff.Mode.SRC_IN);
938 mSuggestionIcon.setBounds(
939 0, 0,
940 mSuggestionIcon.getIntrinsicWidth(),
941 mSuggestionIcon.getIntrinsicHeight());
942 mSuggestionIconType = type;
943 invalidate();
944 }
945
946 @Override
947 public void onLayoutChange(
948 View v, int left, int top, int right, int bottom, int oldLeft,
949 int oldTop, int oldRight, int oldBottom) {
950 boolean needsInvalidate = false;
951 if (v == mNavigationButton) {
952 if (mSuggestionIconLeft != getSuggestionIconLeftPosition()
953 && mSuggestionIconLeft != Integer.MIN_VALUE) {
954 needsInvalidate = true;
955 }
956 } else {
957 if (mTextLeft != getSuggestionTextLeftPosition()
958 && mTextLeft != Integer.MIN_VALUE) {
959 needsInvalidate = true;
960 }
961 if (mTextRight != getSuggestionTextRightPosition()
962 && mTextRight != Integer.MIN_VALUE) {
963 needsInvalidate = true;
964 }
965 }
966 if (needsInvalidate) {
967 removeCallbacks(mRelayoutRunnable);
968 postDelayed(mRelayoutRunnable, RELAYOUT_DELAY_MS);
969 }
970 }
971
972 @Override
973 protected void onAttachedToWindow() {
974 super.onAttachedToWindow();
975 if (mNavigationButton != null) mNavigationButton.addOnLayoutChangeLi stener(this);
976 if (mUrlBar != null) mUrlBar.addOnLayoutChangeListener(this);
977 if (mLocationBar != null) {
978 mLocationBar.getContainerView().addOnLayoutChangeListener(this);
979 }
980 getRootView().addOnLayoutChangeListener(this);
981 }
982
983 @Override
984 protected void onDetachedFromWindow() {
985 if (mNavigationButton != null) mNavigationButton.removeOnLayoutChang eListener(this);
986 if (mUrlBar != null) mUrlBar.removeOnLayoutChangeListener(this);
987 if (mLocationBar != null) {
988 mLocationBar.getContainerView().removeOnLayoutChangeListener(thi s);
989 }
990 getRootView().removeOnLayoutChangeListener(this);
991
992 super.onDetachedFromWindow();
993 }
994 }
995 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698