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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/SuggestionView.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/SuggestionView.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/SuggestionView.java
new file mode 100644
index 0000000000000000000000000000000000000000..12cb7c6ed11ca5cddac3c595bcbdc15a4de762e6
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/omnibox/SuggestionView.java
@@ -0,0 +1,995 @@
+// 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.omnibox;
+
+import static org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type.HISTORY_URL;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.v7.app.AlertDialog;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.StyleSpan;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.TextView.BufferType;
+
+import com.google.android.apps.chrome.R;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultItem;
+import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggestionDelegate;
+import org.chromium.chrome.browser.omnibox.OmniboxSuggestion.Type;
+import org.chromium.chrome.browser.widget.TintedDrawable;
+import org.chromium.ui.base.DeviceFormFactor;
+
+import java.util.Locale;
+
+/**
+ * Container view for omnibox suggestions made very specific for omnibox suggestions to minimize
+ * any unnecessary measures and layouts.
+ */
+class SuggestionView extends ViewGroup {
+ private enum SuggestionIconType {
+ BOOKMARK,
+ HISTORY,
+ GLOBE,
+ MAGNIFIER,
+ VOICE
+ }
+
+ private static final int FIRST_LINE_TEXT_SIZE_SP = 17;
+ private static final int SECOND_LINE_TEXT_SIZE_SP = 14;
+
+ private static final long RELAYOUT_DELAY_MS = 20;
+
+ private static final int TITLE_COLOR_STANDARD_FONT_DARK = Color.rgb(51, 51, 51);
+ private static final int TITLE_COLOR_STANDARD_FONT_LIGHT = Color.rgb(255, 255, 255);
+ private static final int URL_COLOR = Color.rgb(85, 149, 254);
+
+ private static final int ANSWER_IMAGE_HORIZONTAL_SPACING_DP = 4;
+ private static final int ANSWER_IMAGE_VERTICAL_SPACING_DP = 5;
+ private static final float ANSWER_IMAGE_SCALING_FACTOR = 1.15f;
+
+ private LocationBar mLocationBar;
+ private UrlBar mUrlBar;
+ private ImageView mNavigationButton;
+
+ private int mSuggestionHeight;
+ private int mSuggestionAnswerHeight;
+
+ private OmniboxResultItem mSuggestionItem;
+ private OmniboxSuggestion mSuggestion;
+ private OmniboxSuggestionDelegate mSuggestionDelegate;
+ private Boolean mUseDarkColors;
+ private int mPosition;
+
+ private SuggestionContentsContainer mContentsView;
+
+ private int mRefineWidth;
+ private View mRefineView;
+ private TintedDrawable mRefineIcon;
+
+ private final int[] mViewPositionHolder = new int[2];
+
+ /**
+ * Constructs a new omnibox suggestion view.
+ *
+ * @param context The context used to construct the suggestion view.
+ * @param locationBar The location bar showing these suggestions.
+ */
+ public SuggestionView(Context context, LocationBar locationBar) {
+ super(context);
+ mLocationBar = locationBar;
+
+ mSuggestionHeight =
+ context.getResources().getDimensionPixelOffset(R.dimen.omnibox_suggestion_height);
+ mSuggestionAnswerHeight =
+ context.getResources().getDimensionPixelOffset(
+ R.dimen.omnibox_suggestion_answer_height);
+
+ TypedArray a = getContext().obtainStyledAttributes(
+ new int [] {R.attr.selectableItemBackground});
+ Drawable itemBackground = a.getDrawable(0);
+ a.recycle();
+
+ mContentsView = new SuggestionContentsContainer(context, itemBackground);
+ addView(mContentsView);
+
+ mRefineView = new View(context) {
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mRefineIcon == null) return;
+ canvas.save();
+ canvas.translate(
+ (getMeasuredWidth() - mRefineIcon.getIntrinsicWidth()) / 2f,
+ (getMeasuredHeight() - mRefineIcon.getIntrinsicHeight()) / 2f);
+ mRefineIcon.draw(canvas);
+ canvas.restore();
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ if (visibility == VISIBLE) {
+ setClickable(true);
+ setFocusable(true);
+ } else {
+ setClickable(false);
+ setFocusable(false);
+ }
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ if (mRefineIcon != null && mRefineIcon.isStateful()) {
+ mRefineIcon.setState(getDrawableState());
+ }
+ }
+ };
+ mRefineView.setContentDescription(getContext().getString(
+ R.string.accessibility_omnibox_btn_refine));
+
+ // Although this has the same background as the suggestion view, it can not be shared as
+ // it will result in the state of the drawable being shared and always showing up in the
+ // refine view.
+ mRefineView.setBackground(itemBackground.getConstantState().newDrawable());
+ mRefineView.setId(R.id.refine_view_id);
+ mRefineView.setClickable(true);
+ mRefineView.setFocusable(true);
+ mRefineView.setLayoutParams(new LayoutParams(0, 0));
+ addView(mRefineView);
+
+ mRefineWidth = (int) (getResources().getDisplayMetrics().density * 48);
+
+ mUrlBar = (UrlBar) locationBar.getContainerView().findViewById(R.id.url_bar);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (getMeasuredWidth() == 0) return;
+
+ if (mSuggestion.getType() != Type.SEARCH_SUGGEST_TAIL) {
+ mContentsView.resetTextWidths();
+ }
+
+ boolean refineVisible = mRefineView.getVisibility() == VISIBLE;
+ boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
+ int contentsViewOffsetX = isRtl ? mRefineWidth : 0;
+ if (!refineVisible) contentsViewOffsetX = 0;
+ mContentsView.layout(
+ contentsViewOffsetX,
+ 0,
+ contentsViewOffsetX + mContentsView.getMeasuredWidth(),
+ mContentsView.getMeasuredHeight());
+ int refineViewOffsetX = isRtl ? 0 : getMeasuredWidth() - mRefineWidth;
+ mRefineView.layout(
+ refineViewOffsetX,
+ 0,
+ refineViewOffsetX + mRefineWidth,
+ mContentsView.getMeasuredHeight());
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = mSuggestionHeight;
+ if (!TextUtils.isEmpty(mSuggestion.getAnswerContents())) {
+ height = mSuggestionAnswerHeight;
+ }
+ setMeasuredDimension(width, height);
+
+ // The width will be specified as 0 when determining the height of the popup, so exit early
+ // after setting the height.
+ if (width == 0) return;
+
+ boolean refineVisible = mRefineView.getVisibility() == VISIBLE;
+ int refineWidth = refineVisible ? mRefineWidth : 0;
+ mContentsView.measure(
+ MeasureSpec.makeMeasureSpec(width - refineWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ mContentsView.getLayoutParams().width = mContentsView.getMeasuredWidth();
+ mContentsView.getLayoutParams().height = mContentsView.getMeasuredHeight();
+
+ mRefineView.measure(
+ MeasureSpec.makeMeasureSpec(mRefineWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
+ mRefineView.getLayoutParams().width = mRefineView.getMeasuredWidth();
+ mRefineView.getLayoutParams().height = mRefineView.getMeasuredHeight();
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ mContentsView.invalidate();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ // Whenever the suggestion dropdown is touched, we dispatch onGestureDown which is
+ // used to let autocomplete controller know that it should stop updating suggestions.
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) mSuggestionDelegate.onGestureDown();
+ return super.dispatchTouchEvent(ev);
+ }
+
+ /**
+ * Sets the contents and state of the view for the given suggestion.
+ *
+ * @param suggestionItem The omnibox suggestion item this view represents.
+ * @param suggestionDelegate The suggestion delegate.
+ * @param position Position of the suggestion in the dropdown list.
+ * @param useDarkColors Whether dark colors should be used for fonts and icons.
+ */
+ public void init(OmniboxResultItem suggestionItem,
+ OmniboxSuggestionDelegate suggestionDelegate,
+ int position, boolean useDarkColors) {
+ // Update the position unconditionally.
+ mPosition = position;
+ jumpDrawablesToCurrentState();
+ boolean colorsChanged = mUseDarkColors == null || mUseDarkColors != useDarkColors;
+ if (suggestionItem.equals(mSuggestionItem) && !colorsChanged) return;
+ mUseDarkColors = useDarkColors;
+ if (colorsChanged) {
+ mContentsView.mTextLine1.setTextColor(getStandardFontColor());
+ setRefineIcon(true);
+ }
+
+ mSuggestionItem = suggestionItem;
+ mSuggestion = suggestionItem.getSuggestion();
+ mSuggestionDelegate = suggestionDelegate;
+ // Reset old computations.
+ mContentsView.resetTextWidths();
+ mContentsView.mAnswerImage.setVisibility(GONE);
+ mContentsView.mAnswerImage.getLayoutParams().height = 0;
+ mContentsView.mAnswerImage.getLayoutParams().width = 0;
+ mContentsView.mAnswerImage.setImageDrawable(null);
+ mContentsView.mAnswerImageMaxSize = 0;
+ mContentsView.mTextLine1.setTextSize(FIRST_LINE_TEXT_SIZE_SP);
+ mContentsView.mTextLine2.setTextSize(SECOND_LINE_TEXT_SIZE_SP);
+
+ // Suggestions with attached answers are rendered with rich results regardless of which
+ // suggestion type they are.
+ if (mSuggestion.hasAnswer()) {
+ setAnswer(mSuggestion.getAnswer());
+ mContentsView.setSuggestionIcon(SuggestionIconType.MAGNIFIER, colorsChanged);
+ mContentsView.mTextLine2.setVisibility(VISIBLE);
+ setRefinable(true);
+ return;
+ }
+
+ boolean sameAsTyped =
+ suggestionItem.getMatchedQuery().equalsIgnoreCase(mSuggestion.getDisplayText());
+ Type suggestionType = mSuggestion.getType();
+ switch (suggestionType) {
+ case HISTORY_URL:
+ case URL_WHAT_YOU_TYPED:
+ case NAVSUGGEST:
+ case HISTORY_TITLE:
+ case HISTORY_BODY:
+ case HISTORY_KEYWORD:
+ case OPEN_HISTORY_PAGE:
+ if (mSuggestion.isStarred()) {
+ mContentsView.setSuggestionIcon(SuggestionIconType.BOOKMARK, colorsChanged);
+ } else if (suggestionType == HISTORY_URL) {
+ mContentsView.setSuggestionIcon(SuggestionIconType.HISTORY, colorsChanged);
+ } else {
+ mContentsView.setSuggestionIcon(SuggestionIconType.GLOBE, colorsChanged);
+ }
+ boolean urlShown = !TextUtils.isEmpty(mSuggestion.getUrl());
+ boolean urlHighlighted = false;
+ if (urlShown) {
+ urlHighlighted = setUrlText(suggestionItem);
+ } else {
+ mContentsView.mTextLine2.setVisibility(INVISIBLE);
+ }
+ setSuggestedQuery(suggestionItem, true, urlShown, urlHighlighted);
+ setRefinable(!sameAsTyped);
+ break;
+ case SEARCH_WHAT_YOU_TYPED:
+ case SEARCH_HISTORY:
+ case SEARCH_SUGGEST:
+ case SEARCH_OTHER_ENGINE:
+ case SEARCH_SUGGEST_ENTITY:
+ case SEARCH_SUGGEST_TAIL:
+ case SEARCH_SUGGEST_PERSONALIZED:
+ case SEARCH_SUGGEST_PROFILE:
+ case VOICE_SUGGEST:
+ SuggestionIconType suggestionIcon = SuggestionIconType.MAGNIFIER;
+ if (suggestionType == Type.VOICE_SUGGEST) {
+ suggestionIcon = SuggestionIconType.VOICE;
+ } else if ((suggestionType == Type.SEARCH_SUGGEST_PERSONALIZED)
+ || (suggestionType == Type.SEARCH_HISTORY)) {
+ // Show history icon for suggestions based on user queries.
+ suggestionIcon = SuggestionIconType.HISTORY;
+ }
+ mContentsView.setSuggestionIcon(suggestionIcon, colorsChanged);
+ setRefinable(!sameAsTyped);
+ setSuggestedQuery(suggestionItem, false, false, false);
+ if ((suggestionType == Type.SEARCH_SUGGEST_ENTITY)
+ || (suggestionType == Type.SEARCH_SUGGEST_PROFILE)) {
+ showDescriptionLine(
+ SpannableString.valueOf(mSuggestion.getDescription()),
+ getStandardFontColor());
+ } else {
+ mContentsView.mTextLine2.setVisibility(INVISIBLE);
+ }
+ break;
+ default:
+ assert false : "Suggestion type (" + mSuggestion.getType() + ") is not handled";
+ break;
+ }
+ }
+
+ private void setRefinable(boolean refinable) {
+ if (refinable) {
+ mRefineView.setVisibility(VISIBLE);
+ mRefineView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Post the refine action to the end of the UI thread to allow the refine view
+ // a chance to update its background selection state.
+ PerformRefineSuggestion performRefine = new PerformRefineSuggestion();
+ if (!post(performRefine)) performRefine.run();
+ }
+ });
+ } else {
+ mRefineView.setOnClickListener(null);
+ mRefineView.setVisibility(GONE);
+ }
+ }
+
+ private int getStandardFontColor() {
+ return (mUseDarkColors == null || mUseDarkColors)
+ ? TITLE_COLOR_STANDARD_FONT_DARK : TITLE_COLOR_STANDARD_FONT_LIGHT;
+ }
+
+ @Override
+ public void setSelected(boolean selected) {
+ super.setSelected(selected);
+ if (selected && !isInTouchMode()) {
+ mSuggestionDelegate.onSetUrlToSuggestion(mSuggestion);
+ }
+ }
+
+ private void setRefineIcon(boolean invalidateIcon) {
+ if (!invalidateIcon && mRefineIcon != null) return;
+
+ mRefineIcon = TintedDrawable.constructTintedDrawable(
+ getResources(), R.drawable.btn_suggestion_refine);
+ mRefineIcon.setTint(getResources().getColorStateList(mUseDarkColors
+ ? R.color.dark_mode_tint
+ : R.color.light_mode_tint));
+ mRefineIcon.setBounds(
+ 0, 0,
+ mRefineIcon.getIntrinsicWidth(),
+ mRefineIcon.getIntrinsicHeight());
+ mRefineIcon.setState(mRefineView.getDrawableState());
+ mRefineView.postInvalidateOnAnimation();
+ }
+
+ /**
+ * Sets (and highlights) the URL text of the second line of the omnibox suggestion.
+ *
+ * @param suggestion The suggestion containing the URL.
+ * @return Whether the URL was highlighted based on the user query.
+ */
+ private boolean setUrlText(OmniboxResultItem suggestion) {
+ String query = suggestion.getMatchedQuery();
+ String url = suggestion.getSuggestion().getFormattedUrl();
+ int index = url.indexOf(query);
+ Spannable str = SpannableString.valueOf(url);
+ if (index >= 0) {
+ // Bold the part of the URL that matches the user query.
+ str.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
+ index, index + query.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ showDescriptionLine(str, URL_COLOR);
+ return index >= 0;
+ }
+
+ /**
+ * Sets a description line for the omnibox suggestion.
+ *
+ * @param str The description text.
+ */
+ private void showDescriptionLine(Spannable str, int textColor) {
+ if (mContentsView.mTextLine2.getVisibility() != VISIBLE) {
+ mContentsView.mTextLine2.setVisibility(VISIBLE);
+ }
+ mContentsView.mTextLine2.setTextColor(textColor);
+ mContentsView.mTextLine2.setText(str, BufferType.SPANNABLE);
+ }
+
+ /**
+ * Sets the text of the first line of the omnibox suggestion.
+ *
+ * @param suggestionItem The item containing the suggestion data.
+ * @param showDescriptionIfPresent Whether to show the description text of the suggestion if
+ * the item contains valid data.
+ * @param isUrlQuery Whether this suggestion is showing an URL.
+ * @param isUrlHighlighted Whether the URL contains any highlighted matching sections.
+ */
+ private void setSuggestedQuery(
+ OmniboxResultItem suggestionItem, boolean showDescriptionIfPresent,
+ boolean isUrlQuery, boolean isUrlHighlighted) {
+ String userQuery = suggestionItem.getMatchedQuery();
+ String suggestedQuery = null;
+ OmniboxSuggestion suggestion = suggestionItem.getSuggestion();
+ if (showDescriptionIfPresent && !TextUtils.isEmpty(suggestion.getUrl())
+ && !TextUtils.isEmpty(suggestion.getDescription())) {
+ suggestedQuery = suggestion.getDescription();
+ } else {
+ suggestedQuery = suggestion.getDisplayText();
+ }
+ if (suggestedQuery == null) {
+ assert false : "Invalid suggestion sent with no displayable text";
+ suggestedQuery = "";
+ } else if (suggestedQuery.equals(suggestion.getUrl())) {
+ // This is a navigation match with the title defaulted to the URL, display formatted URL
+ // so that they continue matching.
+ suggestedQuery = suggestion.getFormattedUrl();
+ }
+
+ if (mSuggestion.getType() == Type.SEARCH_SUGGEST_TAIL) {
+ String fillIntoEdit = mSuggestion.getFillIntoEdit();
+ // Data sanity checks.
+ if (fillIntoEdit.startsWith(userQuery)
+ && fillIntoEdit.endsWith(suggestedQuery)
+ && fillIntoEdit.length() < userQuery.length() + suggestedQuery.length()) {
+ String ignoredPrefix = fillIntoEdit.substring(
+ 0, fillIntoEdit.length() - suggestedQuery.length());
+ final String ellipsisPrefix = "\u2026 ";
+ suggestedQuery = ellipsisPrefix + suggestedQuery;
+ if (userQuery.startsWith(ignoredPrefix)) {
+ userQuery = ellipsisPrefix + userQuery.substring(ignoredPrefix.length());
+ }
+ if (DeviceFormFactor.isTablet(getContext())) {
+ TextPaint tp = mContentsView.mTextLine1.getPaint();
+ mContentsView.mRequiredWidth =
+ tp.measureText(fillIntoEdit, 0, fillIntoEdit.length());
+ mContentsView.mMatchContentsWidth =
+ tp.measureText(suggestedQuery, 0, suggestedQuery.length());
+
+ // Update the max text widths values in SuggestionList. These will be passed to
+ // the contents view on layout.
+ mSuggestionDelegate.onTextWidthsUpdated(
+ mContentsView.mRequiredWidth, mContentsView.mMatchContentsWidth);
+ }
+ }
+ }
+
+ Spannable str = SpannableString.valueOf(suggestedQuery);
+ int userQueryIndex = isUrlHighlighted ? -1
+ : suggestedQuery.toLowerCase(Locale.getDefault()).indexOf(
+ userQuery.toLowerCase(Locale.getDefault()));
+ if (userQueryIndex != -1) {
+ int spanStart = 0;
+ int spanEnd = 0;
+ if (isUrlQuery) {
+ spanStart = userQueryIndex;
+ spanEnd = userQueryIndex + userQuery.length();
+ } else {
+ spanStart = userQueryIndex + userQuery.length();
+ spanEnd = str.length();
+ }
+ spanStart = Math.min(spanStart, str.length());
+ spanEnd = Math.min(spanEnd, str.length());
+ if (spanStart != spanEnd) {
+ str.setSpan(
+ new StyleSpan(android.graphics.Typeface.BOLD),
+ spanStart, spanEnd,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ mContentsView.mTextLine1.setText(str, BufferType.SPANNABLE);
+ }
+
+ /**
+ * Sets both lines of the Omnibox suggestion based on an Answers in Suggest result.
+ *
+ * @param answer The answer to be displayed.
+ */
+ private void setAnswer(SuggestionAnswer answer) {
+ float density = getResources().getDisplayMetrics().density;
+
+ SuggestionAnswer.ImageLine firstLine = answer.getFirstLine();
+ mContentsView.mTextLine1.setTextSize(AnswerTextBuilder.getMaxTextHeightSp(firstLine));
+ Spannable firstLineText = AnswerTextBuilder.buildSpannable(
+ firstLine, mContentsView.mTextLine1.getPaint().getFontMetrics(), density);
+ mContentsView.mTextLine1.setText(firstLineText, BufferType.SPANNABLE);
+
+ SuggestionAnswer.ImageLine secondLine = answer.getSecondLine();
+ mContentsView.mTextLine2.setTextSize(AnswerTextBuilder.getMaxTextHeightSp(secondLine));
+ Spannable secondLineText = AnswerTextBuilder.buildSpannable(
+ secondLine, mContentsView.mTextLine2.getPaint().getFontMetrics(), density);
+ mContentsView.mTextLine2.setText(secondLineText, BufferType.SPANNABLE);
+
+ if (secondLine.hasImage()) {
+ mContentsView.mAnswerImage.setVisibility(VISIBLE);
+
+ float textSize = mContentsView.mTextLine2.getTextSize();
+ int imageSize = (int) (textSize * ANSWER_IMAGE_SCALING_FACTOR);
+ mContentsView.mAnswerImage.getLayoutParams().height = imageSize;
+ mContentsView.mAnswerImage.getLayoutParams().width = imageSize;
+ mContentsView.mAnswerImageMaxSize = imageSize;
+
+ String url = "https:" + secondLine.getImage().replace("\\/", "/");
+ AnswersImage.requestAnswersImage(
+ mLocationBar.getCurrentTab().getProfile(),
+ url,
+ new AnswersImage.AnswersImageObserver() {
+ @Override
+ public void onAnswersImageChanged(Bitmap bitmap) {
+ mContentsView.mAnswerImage.setImageBitmap(bitmap);
+ }
+ });
+ }
+ }
+
+ /**
+ * Handles triggering a selection request for the suggestion rendered by this view.
+ */
+ private class PerformSelectSuggestion implements Runnable {
+ @Override
+ public void run() {
+ mSuggestionDelegate.onSelection(mSuggestion, mPosition);
+ }
+ }
+
+ /**
+ * Handles triggering a refine request for the suggestion rendered by this view.
+ */
+ private class PerformRefineSuggestion implements Runnable {
+ @Override
+ public void run() {
+ mSuggestionDelegate.onRefineSuggestion(mSuggestion);
+ }
+ }
+
+ /**
+ * @return The left offset for the suggestion text that will align it with the url text.
+ */
+ private int getUrlBarTextLeftPosition() {
+ mUrlBar.getLocationOnScreen(mViewPositionHolder);
+ return mViewPositionHolder[0] + mUrlBar.getPaddingLeft();
+ }
+
+ /**
+ * @return The right offset for the suggestion text that will align it with the url text.
+ */
+ private int getUrlBarTextRightPosition() {
+ mUrlBar.getLocationOnScreen(mViewPositionHolder);
+ return mViewPositionHolder[0] + mUrlBar.getWidth() - mUrlBar.getPaddingRight();
+ }
+
+ /**
+ * Container view for the contents of the suggestion (the search query, URL, and suggestion type
+ * icon).
+ */
+ private class SuggestionContentsContainer extends ViewGroup implements OnLayoutChangeListener {
+ private int mSuggestionIconLeft = Integer.MIN_VALUE;
+ private int mTextLeft = Integer.MIN_VALUE;
+ private int mTextRight = Integer.MIN_VALUE;
+ private Drawable mSuggestionIcon;
+ private SuggestionIconType mSuggestionIconType;
+
+ private final TextView mTextLine1;
+ private final TextView mTextLine2;
+ private final ImageView mAnswerImage;
+
+ private int mAnswerImageMaxSize; // getMaxWidth() is API 16+, so store it locally.
+ private float mRequiredWidth;
+ private float mMatchContentsWidth;
+ private boolean mForceIsFocused;
+
+ private final Runnable mRelayoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ requestLayout();
+ }
+ };
+
+ @SuppressLint("InlinedApi")
+ SuggestionContentsContainer(Context context, Drawable backgroundDrawable) {
+ super(context);
+
+ ApiCompatibilityUtils.setLayoutDirection(this, View.LAYOUT_DIRECTION_INHERIT);
+
+ setBackground(backgroundDrawable);
+ setClickable(true);
+ setFocusable(true);
+ setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, mSuggestionHeight));
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Post the selection action to the end of the UI thread to allow the suggestion
+ // view a chance to update their background selection state.
+ PerformSelectSuggestion performSelection = new PerformSelectSuggestion();
+ if (!post(performSelection)) performSelection.run();
+ }
+ });
+ setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ RecordUserAction.record("MobileOmniboxDeleteGesture");
+ if (!mSuggestion.isDeletable()) return true;
+
+ AlertDialog.Builder b =
+ new AlertDialog.Builder(getContext(), R.style.AlertDialogTheme);
+ b.setTitle(mSuggestion.getDisplayText());
+ b.setMessage(R.string.omnibox_confirm_delete);
+ DialogInterface.OnClickListener okListener =
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ RecordUserAction.record("MobileOmniboxDeleteRequested");
+ mSuggestionDelegate.onDeleteSuggestion(mPosition);
+ }
+ };
+ b.setPositiveButton(android.R.string.ok, okListener);
+ DialogInterface.OnClickListener cancelListener =
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ };
+ b.setNegativeButton(android.R.string.cancel, cancelListener);
+
+ AlertDialog dialog = b.create();
+ dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mSuggestionDelegate.onHideModal();
+ }
+ });
+
+ mSuggestionDelegate.onShowModal();
+ dialog.show();
+ return true;
+ }
+ });
+
+ mTextLine1 = new TextView(context);
+ mTextLine1.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeight));
+ mTextLine1.setSingleLine();
+ mTextLine1.setTextColor(getStandardFontColor());
+ addView(mTextLine1);
+
+ mTextLine2 = new TextView(context);
+ mTextLine2.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, mSuggestionHeight));
+ mTextLine2.setSingleLine();
+ mTextLine2.setVisibility(INVISIBLE);
+ addView(mTextLine2);
+
+ mAnswerImage = new ImageView(context);
+ mAnswerImage.setVisibility(GONE);
+ mAnswerImage.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ mAnswerImage.setLayoutParams(new LayoutParams(0, 0));
+ mAnswerImageMaxSize = 0;
+ addView(mAnswerImage);
+ }
+
+ private void resetTextWidths() {
+ mRequiredWidth = 0;
+ mMatchContentsWidth = 0;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (DeviceFormFactor.isTablet(getContext())) {
+ // Use the same image transform matrix as the navigation icon to ensure the same
+ // scaling, which requires centering vertically based on the height of the
+ // navigation icon view and not the image itself.
+ canvas.save();
+ mSuggestionIconLeft = getSuggestionIconLeftPosition();
+ canvas.translate(
+ mSuggestionIconLeft,
+ (getMeasuredHeight() - mNavigationButton.getMeasuredHeight()) / 2f);
+ canvas.concat(mNavigationButton.getImageMatrix());
+ mSuggestionIcon.draw(canvas);
+ canvas.restore();
+ }
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ if (child != mTextLine1 && child != mTextLine2 && child != mAnswerImage) {
+ return super.drawChild(canvas, child, drawingTime);
+ }
+
+ int height = getMeasuredHeight();
+ int line1Height = mTextLine1.getMeasuredHeight();
+ int line2Height = mTextLine2.getVisibility() == VISIBLE
+ ? mTextLine2.getMeasuredHeight() : 0;
+
+ int verticalOffset = 0;
+ if (line1Height + line2Height > height) {
+ // The text lines total height is larger than this view, snap them to the top and
+ // bottom of the view.
+ if (child == mTextLine1) {
+ verticalOffset = 0;
+ } else {
+ verticalOffset = height - line2Height;
+ }
+ } else {
+ // The text lines fit comfortably, so vertically center them.
+ verticalOffset = (height - line1Height - line2Height) / 2;
+ if (child == mTextLine2) verticalOffset += line1Height;
+
+ // When one line is larger than the other, it contains extra vertical padding. This
+ // produces more apparent whitespace above or below the text lines. Add a small
+ // offset to compensate.
+ if (line1Height != line2Height) {
+ verticalOffset += (line2Height - line1Height) / 10;
+ }
+
+ // The image is positioned vertically aligned with the second text line but
+ // requires a small additional offset to align with the ascent of the text instead
+ // of the top of the text which includes some whitespace.
+ if (child == mAnswerImage) {
+ verticalOffset += ANSWER_IMAGE_VERTICAL_SPACING_DP
+ * getResources().getDisplayMetrics().density;
+ }
+ }
+
+ canvas.save();
+ canvas.translate(0, verticalOffset);
+ boolean retVal = super.drawChild(canvas, child, drawingTime);
+ canvas.restore();
+ return retVal;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ View locationBarView = mLocationBar.getContainerView();
+ if (mUrlBar == null) {
+ mUrlBar = (UrlBar) locationBarView.findViewById(R.id.url_bar);
+ mUrlBar.addOnLayoutChangeListener(this);
+ }
+ if (mNavigationButton == null) {
+ mNavigationButton =
+ (ImageView) locationBarView.findViewById(R.id.navigation_button);
+ mNavigationButton.addOnLayoutChangeListener(this);
+ }
+
+ // Align the text to be pixel perfectly aligned with the text in the url bar.
+ mTextLeft = getSuggestionTextLeftPosition();
+ mTextRight = getSuggestionTextRightPosition();
+ boolean isRTL = ApiCompatibilityUtils.isLayoutRtl(this);
+ if (DeviceFormFactor.isTablet(getContext())) {
+ int textWidth = isRTL ? mTextRight : (r - l - mTextLeft);
+ final float maxRequiredWidth = mSuggestionDelegate.getMaxRequiredWidth();
+ final float maxMatchContentsWidth = mSuggestionDelegate.getMaxMatchContentsWidth();
+ float paddingStart = (textWidth > maxRequiredWidth)
+ ? (mRequiredWidth - mMatchContentsWidth)
+ : Math.max(textWidth - maxMatchContentsWidth, 0);
+ ApiCompatibilityUtils.setPaddingRelative(
+ mTextLine1, (int) paddingStart, mTextLine1.getPaddingTop(),
+ 0, // TODO(skanuj) : Change to ApiCompatibilityUtils.getPaddingEnd(...).
+ mTextLine1.getPaddingBottom());
+ }
+
+ int imageWidth = mAnswerImageMaxSize;
+ int imageSpacing = 0;
+ if (mAnswerImage.getVisibility() == VISIBLE && imageWidth > 0) {
+ float density = getResources().getDisplayMetrics().density;
+ imageSpacing = (int) (ANSWER_IMAGE_HORIZONTAL_SPACING_DP * density);
+ }
+ if (isRTL) {
+ mTextLine1.layout(0, t, mTextRight, b);
+ mAnswerImage.layout(mTextRight - imageWidth , t, mTextRight, b);
+ mTextLine2.layout(0, t, mTextRight - (imageWidth + imageSpacing), b);
+ } else {
+ mTextLine1.layout(mTextLeft, t, r - l, b);
+ mAnswerImage.layout(mTextLeft, t, mTextLeft + imageWidth, b);
+ mTextLine2.layout(mTextLeft + imageWidth + imageSpacing, t, r - l, b);
+ }
+
+ int suggestionIconPosition = getSuggestionIconLeftPosition();
+ if (mSuggestionIconLeft != suggestionIconPosition
+ && mSuggestionIconLeft != Integer.MIN_VALUE) {
+ mContentsView.postInvalidateOnAnimation();
+ }
+ mSuggestionIconLeft = suggestionIconPosition;
+ }
+
+ /**
+ * @return The left offset for the suggestion text that will align it with the url text.
+ */
+ private int getSuggestionTextLeftPosition() {
+ if (mLocationBar == null) return 0;
+
+ int textLeftPosition = getUrlBarTextLeftPosition();
+ getLocationOnScreen(mViewPositionHolder);
+ return textLeftPosition - mViewPositionHolder[0];
+ }
+
+ /**
+ * @return The right offset for the suggestion text that will align it with the url text.
+ */
+ private int getSuggestionTextRightPosition() {
+ if (mLocationBar == null) return 0;
+
+ int textRightPosition = getUrlBarTextRightPosition();
+ getLocationOnScreen(mViewPositionHolder);
+ return textRightPosition - mViewPositionHolder[0];
+ }
+
+ /**
+ * @return The left offset for the suggestion type icon that aligns it with the url bar.
+ */
+ private int getSuggestionIconLeftPosition() {
+ if (mNavigationButton == null) return 0;
+
+ // Ensure the suggestion icon matches the location of the navigation icon in the omnibox
+ // perfectly.
+ mNavigationButton.getLocationOnScreen(mViewPositionHolder);
+ int navButtonXPosition = mViewPositionHolder[0] + mNavigationButton.getPaddingLeft();
+
+ getLocationOnScreen(mViewPositionHolder);
+
+ return navButtonXPosition - mViewPositionHolder[0];
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (mTextLine1.getMeasuredWidth() != width
+ || mTextLine1.getMeasuredHeight() != height) {
+ mTextLine1.measure(
+ MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(mSuggestionHeight, MeasureSpec.AT_MOST));
+ }
+
+ if (mTextLine2.getMeasuredWidth() != width
+ || mTextLine2.getMeasuredHeight() != height) {
+ mTextLine2.measure(
+ MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(mSuggestionHeight, MeasureSpec.AT_MOST));
+ }
+ }
+
+ @Override
+ public void invalidate() {
+ if (getSuggestionTextLeftPosition() != mTextLeft
+ || getSuggestionTextRightPosition() != mTextRight) {
+ // When the text position is changed, it typically is caused by the suggestions
+ // appearing while the URL bar on the phone is gaining focus (if you trigger an
+ // intent that will result in suggestions being shown before focusing the omnibox).
+ // Triggering a relayout will cause any animations to stutter, so we continually
+ // push the relayout to end of the UI queue until the animation is complete.
+ removeCallbacks(mRelayoutRunnable);
+ postDelayed(mRelayoutRunnable, RELAYOUT_DELAY_MS);
+ } else {
+ super.invalidate();
+ }
+ }
+
+ @Override
+ public boolean isFocused() {
+ return mForceIsFocused || super.isFocused();
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ // When creating the drawable states, treat selected as focused to get the proper
+ // highlight when in non-touch mode (i.e. physical keyboard). This is because only
+ // a single view in a window can have focus, and the these will only appear if
+ // the omnibox has focus, so we trick the drawable state into believing it has it.
+ mForceIsFocused = isSelected() && !isInTouchMode();
+ int[] drawableState = super.onCreateDrawableState(extraSpace);
+ mForceIsFocused = false;
+ return drawableState;
+ }
+
+ private void setSuggestionIcon(SuggestionIconType type, boolean invalidateCurrentIcon) {
+ if (mSuggestionIconType == type && !invalidateCurrentIcon) return;
+
+ int drawableId = R.drawable.ic_omnibox_page;
+ switch (type) {
+ case BOOKMARK:
+ drawableId = R.drawable.btn_star;
+ break;
+ case MAGNIFIER:
+ drawableId = R.drawable.ic_suggestion_magnifier;
+ break;
+ case HISTORY:
+ drawableId = R.drawable.ic_suggestion_history;
+ break;
+ case VOICE:
+ drawableId = R.drawable.btn_mic;
+ break;
+ default:
+ break;
+ }
+ mSuggestionIcon = ApiCompatibilityUtils.getDrawable(getResources(), drawableId);
+ mSuggestionIcon.setColorFilter(mUseDarkColors
+ ? getResources().getColor(R.color.light_normal_color)
+ : Color.WHITE, PorterDuff.Mode.SRC_IN);
+ mSuggestionIcon.setBounds(
+ 0, 0,
+ mSuggestionIcon.getIntrinsicWidth(),
+ mSuggestionIcon.getIntrinsicHeight());
+ mSuggestionIconType = type;
+ invalidate();
+ }
+
+ @Override
+ public void onLayoutChange(
+ View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ boolean needsInvalidate = false;
+ if (v == mNavigationButton) {
+ if (mSuggestionIconLeft != getSuggestionIconLeftPosition()
+ && mSuggestionIconLeft != Integer.MIN_VALUE) {
+ needsInvalidate = true;
+ }
+ } else {
+ if (mTextLeft != getSuggestionTextLeftPosition()
+ && mTextLeft != Integer.MIN_VALUE) {
+ needsInvalidate = true;
+ }
+ if (mTextRight != getSuggestionTextRightPosition()
+ && mTextRight != Integer.MIN_VALUE) {
+ needsInvalidate = true;
+ }
+ }
+ if (needsInvalidate) {
+ removeCallbacks(mRelayoutRunnable);
+ postDelayed(mRelayoutRunnable, RELAYOUT_DELAY_MS);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mNavigationButton != null) mNavigationButton.addOnLayoutChangeListener(this);
+ if (mUrlBar != null) mUrlBar.addOnLayoutChangeListener(this);
+ if (mLocationBar != null) {
+ mLocationBar.getContainerView().addOnLayoutChangeListener(this);
+ }
+ getRootView().addOnLayoutChangeListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ if (mNavigationButton != null) mNavigationButton.removeOnLayoutChangeListener(this);
+ if (mUrlBar != null) mUrlBar.removeOnLayoutChangeListener(this);
+ if (mLocationBar != null) {
+ mLocationBar.getContainerView().removeOnLayoutChangeListener(this);
+ }
+ getRootView().removeOnLayoutChangeListener(this);
+
+ super.onDetachedFromWindow();
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698