DefaultSuggestionView.java revision fb8ce18922dae59db424fce906b5c113797fe81e
11e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney/*
21e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * Copyright (C) 2009 The Android Open Source Project
31e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney *
41e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * Licensed under the Apache License, Version 2.0 (the "License");
51e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * you may not use this file except in compliance with the License.
61e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * You may obtain a copy of the License at
71e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney *
81e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney *      http://www.apache.org/licenses/LICENSE-2.0
91e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney *
101e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * Unless required by applicable law or agreed to in writing, software
111e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * distributed under the License is distributed on an "AS IS" BASIS,
121e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
131e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * See the License for the specific language governing permissions and
141e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * limitations under the License.
151e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney */
161e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
171e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneypackage com.android.quicksearchbox.ui;
181e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
192fb3a129925a42e72944b836e85a1a2d55a0d950Mathew Inwoodimport com.android.quicksearchbox.QsbApplication;
201e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport com.android.quicksearchbox.R;
21fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport com.android.quicksearchbox.Source;
2293bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringertimport com.android.quicksearchbox.Suggestion;
232fb3a129925a42e72944b836e85a1a2d55a0d950Mathew Inwoodimport com.android.quicksearchbox.SuggestionFormatter;
24e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwoodimport com.android.quicksearchbox.util.Consumer;
25e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwoodimport com.android.quicksearchbox.util.NowOrLater;
261e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
271e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.content.Context;
28965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringertimport android.content.res.ColorStateList;
291e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.graphics.drawable.Drawable;
30fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.text.Html;
31965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringertimport android.text.Spannable;
32965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringertimport android.text.SpannableString;
331e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.text.TextUtils;
34965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringertimport android.text.style.TextAppearanceSpan;
351e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.util.AttributeSet;
361e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.util.Log;
37fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringertimport android.view.ContextMenu;
3866ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwoodimport android.view.KeyEvent;
39fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringertimport android.view.MenuInflater;
40fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringertimport android.view.MenuItem;
411e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.view.View;
421e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.widget.ImageView;
431e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.widget.RelativeLayout;
441e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneyimport android.widget.TextView;
451e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
461e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney/**
471e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * View for the items in the suggestions list. This includes promoted suggestions,
481e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney * sources, and suggestions under each source.
491e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney */
501e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinneypublic class DefaultSuggestionView extends RelativeLayout implements SuggestionView {
511e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
521e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    private static final boolean DBG = false;
531e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
545229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood    public static final String VIEW_ID = "default";
555229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood
56e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood    private static int sId = 0;
57e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood    // give the TAG an unique ID to make debugging easier (there are lots of these!)
58e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood    private final String TAG = "QSB.SuggestionView:" + (sId++);
59e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
601e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    private TextView mText1;
611e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    private TextView mText2;
62e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood    private AsyncIcon mIcon1;
63e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood    private AsyncIcon mIcon2;
642fb3a129925a42e72944b836e85a1a2d55a0d950Mathew Inwood    private final SuggestionFormatter mSuggestionFormatter;
65fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    private boolean mIsFromHistory;
6666ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    private boolean mRefineable;
6766ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    private int mPosition;
687010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert    private SuggestionsAdapter mAdapter;
6966ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    private KeyListener mKeyListener;
701e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
711e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    public DefaultSuggestionView(Context context, AttributeSet attrs, int defStyle) {
721e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        super(context, attrs, defStyle);
732fb3a129925a42e72944b836e85a1a2d55a0d950Mathew Inwood        mSuggestionFormatter = QsbApplication.get(context).getSuggestionFormatter();
7466ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    }
751e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
761e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    public DefaultSuggestionView(Context context, AttributeSet attrs) {
771e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        super(context, attrs);
782fb3a129925a42e72944b836e85a1a2d55a0d950Mathew Inwood        mSuggestionFormatter = QsbApplication.get(context).getSuggestionFormatter();
791e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    }
801e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
811e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    public DefaultSuggestionView(Context context) {
821e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        super(context);
832fb3a129925a42e72944b836e85a1a2d55a0d950Mathew Inwood        mSuggestionFormatter = QsbApplication.get(context).getSuggestionFormatter();
841e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    }
851e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
861e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    @Override
871e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    protected void onFinishInflate() {
881e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        super.onFinishInflate();
891e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        mText1 = (TextView) findViewById(R.id.text1);
901e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        mText2 = (TextView) findViewById(R.id.text2);
91e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        mIcon1 = new AsyncIcon((ImageView) findViewById(R.id.icon1)){
92e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            // override default icon (when no other available) with default source icon
93e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            @Override
94e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            protected String getFallbackIconId(Source source) {
95e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                return source.getSourceIconUri().toString();
96e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            }
97e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            @Override
98e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            protected Drawable getFallbackIcon(Source source) {
99e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                return source.getSourceIcon();
100e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            }
101e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        };
102e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        mIcon2 = new AsyncIcon((ImageView) findViewById(R.id.icon2));
10366ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        // for some reason, creating mKeyListener inside the constructor causes it not to work.
10466ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        mKeyListener = new KeyListener();
10566ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood
10666ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        setOnKeyListener(mKeyListener);
1071e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    }
1081e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
1095229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood    public void bindAsSuggestion(Suggestion suggestion, String userQuery) {
11066ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        setOnClickListener(new ClickListener());
11166ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood
1125229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood        CharSequence text1 = formatText(suggestion.getSuggestionText1(), suggestion, userQuery);
113965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        CharSequence text2 = suggestion.getSuggestionText2Url();
114965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        if (text2 != null) {
115965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert            text2 = formatUrl(text2);
116965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        } else {
1175229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood            text2 = formatText(suggestion.getSuggestionText2(), suggestion, null);
118965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        }
119145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert        // If there is no text for the second line, allow the first line to be up to two lines
120bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert        if (TextUtils.isEmpty(text2)) {
121bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert            mText1.setSingleLine(false);
122bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert            mText1.setMaxLines(2);
123bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert            mText1.setEllipsize(TextUtils.TruncateAt.START);
124bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert        } else {
125bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert            mText1.setSingleLine(true);
126bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert            mText1.setMaxLines(1);
127bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert            mText1.setEllipsize(TextUtils.TruncateAt.MIDDLE);
128bf18086dbcdc3cbc50409940b58fbc3a7dcf3a88Bjorn Bringert        }
1291e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        setText1(text1);
1301e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        setText2(text2);
131e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        mIcon1.set(suggestion.getSuggestionSource(), suggestion.getSuggestionIcon1());
132e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        mIcon2.set(suggestion.getSuggestionSource(), suggestion.getSuggestionIcon2());
133fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        updateIsFromHistory(suggestion);
134145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert        updateRefinable(suggestion);
135fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert
136fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        setLongClickable(needsContextMenu());
137fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert
138fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        if (DBG) {
139fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            Log.d(TAG, "bindAsSuggestion(), text1=" + text1 + ",text2=" + text2 + ",q='" +
140fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert                    userQuery + "',refinable=" + mRefineable + ",fromHistory=" + mIsFromHistory);
141fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        }
142145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert    }
143145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert
1445229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood    public void bindAdapter(SuggestionsAdapter adapter, int position) {
1455229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood        mAdapter = adapter;
1465229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood        mPosition = position;
1475229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood    }
1485229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood
149fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    protected boolean needsContextMenu() {
150fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        return isFromHistory();
151fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    }
152fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert
153fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    protected boolean isFromHistory() {
154fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        return mIsFromHistory;
155fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    }
156fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert
157fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    protected void updateIsFromHistory(Suggestion suggestion) {
158fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        mIsFromHistory = suggestion.isSuggestionShortcut() || suggestion.isHistorySuggestion();
159fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    }
160fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert
1615229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood    protected void updateRefinable(Suggestion suggestion) {
16266ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        mRefineable =
163014e0d0c0a5102b7cc1c5576a3af25a646731dd0Bjorn Bringert                suggestion.isWebSearchSuggestion()
164e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                && mIcon2.getView().getDrawable() == null
165145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert                && !TextUtils.isEmpty(suggestion.getSuggestionQuery());
1665c383769c9cf23ebb38e03f1245007b8f7e48950Mathew Inwood        if (DBG) Log.d(TAG, "updateRefinable: " + mRefineable);
16766ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        setRefinable(suggestion, mRefineable);
168145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert    }
169145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert
1705229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood    protected void setRefinable(Suggestion suggestion, boolean refinable) {
171145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert        if (refinable) {
172e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mIcon2.getView().setOnClickListener(new View.OnClickListener() {
173145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert                public void onClick(View v) {
1747010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert                    onSuggestionQueryRefineClicked();
175145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert                }
176145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert            });
177e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mIcon2.getView().setFocusable(true);
178e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mIcon2.getView().setOnKeyListener(mKeyListener);
1796b8dba4c144efb18faa9720d022db2aa7a532f59Bjorn Bringert            Drawable icon2 = getContext().getResources().getDrawable(R.drawable.edit_query);
1806b8dba4c144efb18faa9720d022db2aa7a532f59Bjorn Bringert            Drawable background =
1816b8dba4c144efb18faa9720d022db2aa7a532f59Bjorn Bringert                    getContext().getResources().getDrawable(R.drawable.edit_query_background);
182e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mIcon2.setDrawable(icon2, background, String.valueOf(R.drawable.edit_query));
183145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert        } else {
184e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mIcon2.getView().setOnClickListener(null);
185e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mIcon2.getView().setFocusable(false);
186e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mIcon2.getView().setOnKeyListener(null);
187145693e12b77c193a65b7eaa038a272dd1f48f33Bjorn Bringert        }
1881e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    }
1891e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
190965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert    private CharSequence formatUrl(CharSequence url) {
191965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        SpannableString text = new SpannableString(url);
192965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        ColorStateList colors = getResources().getColorStateList(R.color.url_text);
193965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null),
194965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert                0, url.length(),
195965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
196965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert        return text;
197965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert    }
198965d98377ddfdc52b772c2444d840000b665e000Bjorn Bringert
1995229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood    private CharSequence formatText(String str, Suggestion suggestion,
2005229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood                String userQuery) {
2012fb3a129925a42e72944b836e85a1a2d55a0d950Mathew Inwood        boolean isHtml = "html".equals(suggestion.getSuggestionFormat());
202fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (isHtml && looksLikeHtml(str)) {
203fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return Html.fromHtml(str);
2045229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood        } else if (suggestion.isWebSearchSuggestion() && !TextUtils.isEmpty(userQuery)) {
2055229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood            return mSuggestionFormatter.formatSuggestion(userQuery, str);
206fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        } else {
207fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return str;
208fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
209fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
210fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
211fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private boolean looksLikeHtml(String str) {
212fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (TextUtils.isEmpty(str)) return false;
213fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        for (int i = str.length() - 1; i >= 0; i--) {
214fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            char c = str.charAt(i);
215fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            if (c == '>' || c == '&') return true;
216fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
217fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return false;
218fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
219fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
220dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert    /**
221dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert     * Sets the first text line.
222dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert     */
223dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert    private void setText1(CharSequence text) {
2241e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        mText1.setText(text);
2251e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    }
2261e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
227dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert    /**
228dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert     * Sets the second text line.
229dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert     */
230dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert    private void setText2(CharSequence text) {
2311e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        mText2.setText(text);
2321e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        if (TextUtils.isEmpty(text)) {
2331e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            mText2.setVisibility(GONE);
2341e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        } else {
2351e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            mText2.setVisibility(VISIBLE);
2361e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        }
2371e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    }
2381e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
239dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert    /**
2401e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney     * Sets the drawable in an image view, makes sure the view is only visible if there
2411e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney     * is a drawable.
2421e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney     */
2431e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    private static void setViewDrawable(ImageView v, Drawable drawable) {
2441e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        // Set the icon even if the drawable is null, since we need to clear any
2451e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        // previous icon.
2461e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        v.setImageDrawable(drawable);
2471e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
2481e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        if (drawable == null) {
2491e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            v.setVisibility(View.GONE);
2501e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        } else {
2511e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            v.setVisibility(View.VISIBLE);
2521e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
2531e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            // This is a hack to get any animated drawables (like a 'working' spinner)
2541e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            // to animate. You have to setVisible true on an AnimationDrawable to get
2551e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            // it to start animating, but it must first have been false or else the
2561e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            // call to setVisible will be ineffective. We need to clear up the story
2571e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            // about animated drawables in the future, see http://b/1878430.
2581e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            drawable.setVisible(false, false);
2591e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney            drawable.setVisible(true, false);
2601e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney        }
2611e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney    }
2621e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney
263fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    @Override
264fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    protected void onCreateContextMenu(ContextMenu menu) {
265fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        super.onCreateContextMenu(menu);
266fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        if (isFromHistory()) {
267fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            MenuInflater inflater = new MenuInflater(getContext());
268fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            inflater.inflate(R.menu.remove_from_history, menu);
269fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            MenuItem removeFromHistory = menu.findItem(R.id.remove_from_history);
270fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            removeFromHistory.setOnMenuItemClickListener(new RemoveFromHistoryListener());
271fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        }
272fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    }
273fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert
2747010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert    protected void onSuggestionClicked() {
2757010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert        if (mAdapter != null) {
2767010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert            mAdapter.onSuggestionClicked(mPosition);
2777010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert        }
2787010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert    }
2797010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert
2807010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert    protected void onSuggestionQuickContactClicked() {
2817010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert        if (mAdapter != null) {
2827010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert            mAdapter.onSuggestionQuickContactClicked(mPosition);
2837010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert        }
2847010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert    }
2857010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert
286fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    protected void onRemoveFromHistoryClicked() {
2877010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert        if (mAdapter != null) {
288fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            mAdapter.onSuggestionRemoveFromHistoryClicked(mPosition);
2897010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert        }
2907010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert    }
2917010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert
2927010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert    protected void onSuggestionQueryRefineClicked() {
2937010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert        if (mAdapter != null) {
2947010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert            mAdapter.onSuggestionQueryRefineClicked(mPosition);
295c020c1844b0fb3a825e8a6fa6ad96288bc432fbcBjorn Bringert        }
296c020c1844b0fb3a825e8a6fa6ad96288bc432fbcBjorn Bringert    }
297c020c1844b0fb3a825e8a6fa6ad96288bc432fbcBjorn Bringert
29866ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    private class ClickListener implements OnClickListener {
29966ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        public void onClick(View v) {
3007010c51b51c97fa43d7b24d2158ecbc1d064e0a6Bjorn Bringert            onSuggestionClicked();
30166ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        }
30266ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    }
30366ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood
304fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert    private class RemoveFromHistoryListener implements MenuItem.OnMenuItemClickListener {
305fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert        public boolean onMenuItemClick(MenuItem item) {
306fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            onRemoveFromHistoryClicked();
307fb8ce18922dae59db424fce906b5c113797fe81eBjorn Bringert            return false;
30866ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        }
30966ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    }
31066ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood
31166ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    private class KeyListener implements View.OnKeyListener {
31266ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        public boolean onKey(View v, int keyCode, KeyEvent event) {
31366ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood            boolean consumed = false;
31466ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood            if (event.getAction() == KeyEvent.ACTION_DOWN) {
315e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && v != mIcon2.getView()) {
316e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                    consumed = mIcon2.getView().requestFocus();
31766ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood                    if (DBG) Log.d(TAG, "onKey Icon2 accepted focus: " + consumed);
318e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && v == mIcon2.getView()) {
31966ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood                    consumed = requestFocus();
32066ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood                    if (DBG) Log.d(TAG, "onKey SuggestionView accepted focus: " + consumed);
32166ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood                }
32266ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood            }
32366ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood            return consumed;
32466ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood        }
32566ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood    }
32666ee1cc883f77fee930587503ecdcd8d18f12b23Mathew Inwood
327e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood    private class AsyncIcon {
328e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        private final ImageView mView;
329e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        private String mCurrentId;
330e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        private String mWantedId;
331e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
332e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        public AsyncIcon(ImageView view) {
333e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mView = view;
334e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
335e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
336e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        public void set(final Source source, final String iconId) {
337e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            if (iconId != null) {
338e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                mWantedId = iconId;
339e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                if (!TextUtils.equals(mWantedId, mCurrentId)) {
340e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                    if (DBG) Log.d(TAG, "getting icon Id=" + iconId);
341e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                    NowOrLater<Drawable> icon = source.getIcon(iconId);
342e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                    if (icon.haveNow()) {
343e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                        if (DBG) Log.d(TAG, "getIcon ready now");
344e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                        handleNewDrawable(icon.getNow(), iconId, source);
345e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                    } else {
346e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                        // make sure old icon is not visible while new one is loaded
347e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                        if (DBG) Log.d(TAG , "getIcon getting later");
348e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                        clearDrawable();
349e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                        icon.getLater(new Consumer<Drawable>(){
350e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                            public boolean consume(Drawable icon) {
351e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                if (DBG) {
352e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                    Log.d(TAG, "IconConsumer.consume got id " + iconId +
353e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                            " want id " + mWantedId);
354e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                }
355e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                // ensure we have not been re-bound since the request was made.
356e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                if (TextUtils.equals(iconId, mWantedId)) {
357e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                    handleNewDrawable(icon, iconId, source);
358e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                    return true;
359e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                }
360e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                                return false;
361e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                            }});
362e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                    }
363e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                }
364e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            } else {
365e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                mWantedId = null;
366e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                handleNewDrawable(null, null, source);
367e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            }
368e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
369e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
370e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        public ImageView getView() {
371e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            return mView;
372e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
373e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
374e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        private void handleNewDrawable(Drawable icon, String id, Source source) {
375e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            if (icon == null) {
376e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                mWantedId = getFallbackIconId(source);
377e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                if (TextUtils.equals(mWantedId, mCurrentId)) {
378e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                    return;
379e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                }
380e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood                icon = getFallbackIcon(source);
381e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            }
382e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            setDrawable(icon, id);
383e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
384e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
385e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        public void setDrawable(Drawable icon, Drawable background, String id) {
386e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mCurrentId = id;
3875c383769c9cf23ebb38e03f1245007b8f7e48950Mathew Inwood            mWantedId = id;
3885c383769c9cf23ebb38e03f1245007b8f7e48950Mathew Inwood            setViewDrawable(mView, icon);
389e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mView.setBackgroundDrawable(background);
390e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
391e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
392e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        private void setDrawable(Drawable icon, String id) {
393e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mCurrentId = id;
394e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            setViewDrawable(mView, icon);
395e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
396e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
397e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        private void clearDrawable() {
398e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mCurrentId = null;
399e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            mView.setImageDrawable(null);
400e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
401e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
402e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        protected String getFallbackIconId(Source source) {
403e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            return null;
404e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
405e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
406e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        protected Drawable getFallbackIcon(Source source) {
407e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood            return null;
408e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        }
409e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood
410e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood    }
4111e938ea2f5edefab446b9562b316bc5dc72adebbBryan Mawhinney}
412