Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.quicksearchbox.ui;
     18 
     19 import com.android.quicksearchbox.R;
     20 import com.android.quicksearchbox.Source;
     21 import com.android.quicksearchbox.Suggestion;
     22 import com.android.quicksearchbox.util.Consumer;
     23 import com.android.quicksearchbox.util.NowOrLater;
     24 
     25 import android.content.Context;
     26 import android.content.res.ColorStateList;
     27 import android.graphics.drawable.Drawable;
     28 import android.net.Uri;
     29 import android.text.Html;
     30 import android.text.Spannable;
     31 import android.text.SpannableString;
     32 import android.text.TextUtils;
     33 import android.text.style.TextAppearanceSpan;
     34 import android.util.AttributeSet;
     35 import android.util.Log;
     36 import android.view.View;
     37 import android.widget.ImageView;
     38 import android.widget.TextView;
     39 
     40 /**
     41  * View for the items in the suggestions list. This includes promoted suggestions,
     42  * sources, and suggestions under each source.
     43  */
     44 public class DefaultSuggestionView extends BaseSuggestionView {
     45 
     46     private static final boolean DBG = false;
     47 
     48     private static final String VIEW_ID = "default";
     49 
     50     private final String TAG = "QSB.DefaultSuggestionView";
     51 
     52     private AsyncIcon mAsyncIcon1;
     53     private AsyncIcon mAsyncIcon2;
     54 
     55     public DefaultSuggestionView(Context context, AttributeSet attrs, int defStyle) {
     56         super(context, attrs, defStyle);
     57     }
     58 
     59     public DefaultSuggestionView(Context context, AttributeSet attrs) {
     60         super(context, attrs);
     61     }
     62 
     63     public DefaultSuggestionView(Context context) {
     64         super(context);
     65     }
     66 
     67     @Override
     68     protected void onFinishInflate() {
     69         super.onFinishInflate();
     70         mText1 = (TextView) findViewById(R.id.text1);
     71         mText2 = (TextView) findViewById(R.id.text2);
     72         mAsyncIcon1 = new AsyncIcon(mIcon1) {
     73             // override default icon (when no other available) with default source icon
     74             @Override
     75             protected String getFallbackIconId(Source source) {
     76                 return source.getSourceIconUri().toString();
     77             }
     78             @Override
     79             protected Drawable getFallbackIcon(Source source) {
     80                 return source.getSourceIcon();
     81             }
     82         };
     83         mAsyncIcon2 = new AsyncIcon(mIcon2);
     84     }
     85 
     86     @Override
     87     public void bindAsSuggestion(Suggestion suggestion, String userQuery) {
     88         super.bindAsSuggestion(suggestion, userQuery);
     89 
     90         CharSequence text1 = formatText(suggestion.getSuggestionText1(), suggestion);
     91         CharSequence text2 = suggestion.getSuggestionText2Url();
     92         if (text2 != null) {
     93             text2 = formatUrl(text2);
     94         } else {
     95             text2 = formatText(suggestion.getSuggestionText2(), suggestion);
     96         }
     97         // If there is no text for the second line, allow the first line to be up to two lines
     98         if (TextUtils.isEmpty(text2)) {
     99             mText1.setSingleLine(false);
    100             mText1.setMaxLines(2);
    101             mText1.setEllipsize(TextUtils.TruncateAt.START);
    102         } else {
    103             mText1.setSingleLine(true);
    104             mText1.setMaxLines(1);
    105             mText1.setEllipsize(TextUtils.TruncateAt.MIDDLE);
    106         }
    107         setText1(text1);
    108         setText2(text2);
    109         mAsyncIcon1.set(suggestion.getSuggestionSource(), suggestion.getSuggestionIcon1());
    110         mAsyncIcon2.set(suggestion.getSuggestionSource(), suggestion.getSuggestionIcon2());
    111 
    112         if (DBG) {
    113             Log.d(TAG, "bindAsSuggestion(), text1=" + text1 + ",text2=" + text2 + ",q='" +
    114                     userQuery + ",fromHistory=" + isFromHistory(suggestion));
    115         }
    116     }
    117 
    118     private CharSequence formatUrl(CharSequence url) {
    119         SpannableString text = new SpannableString(url);
    120         ColorStateList colors = getResources().getColorStateList(R.color.url_text);
    121         text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null),
    122                 0, url.length(),
    123                 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    124         return text;
    125     }
    126 
    127     private CharSequence formatText(String str, Suggestion suggestion) {
    128         boolean isHtml = "html".equals(suggestion.getSuggestionFormat());
    129         if (isHtml && looksLikeHtml(str)) {
    130             return Html.fromHtml(str);
    131         } else {
    132             return str;
    133         }
    134     }
    135 
    136     private boolean looksLikeHtml(String str) {
    137         if (TextUtils.isEmpty(str)) return false;
    138         for (int i = str.length() - 1; i >= 0; i--) {
    139             char c = str.charAt(i);
    140             if (c == '>' || c == '&') return true;
    141         }
    142         return false;
    143     }
    144 
    145     /**
    146      * Sets the drawable in an image view, makes sure the view is only visible if there
    147      * is a drawable.
    148      */
    149     private static void setViewDrawable(ImageView v, Drawable drawable) {
    150         // Set the icon even if the drawable is null, since we need to clear any
    151         // previous icon.
    152         v.setImageDrawable(drawable);
    153 
    154         if (drawable == null) {
    155             v.setVisibility(View.GONE);
    156         } else {
    157             v.setVisibility(View.VISIBLE);
    158 
    159             // This is a hack to get any animated drawables (like a 'working' spinner)
    160             // to animate. You have to setVisible true on an AnimationDrawable to get
    161             // it to start animating, but it must first have been false or else the
    162             // call to setVisible will be ineffective. We need to clear up the story
    163             // about animated drawables in the future, see http://b/1878430.
    164             drawable.setVisible(false, false);
    165             drawable.setVisible(true, false);
    166         }
    167     }
    168 
    169     private class AsyncIcon {
    170         private final ImageView mView;
    171         private String mCurrentId;
    172         private String mWantedId;
    173 
    174         public AsyncIcon(ImageView view) {
    175             mView = view;
    176         }
    177 
    178         public void set(final Source source, final String sourceIconId) {
    179             if (sourceIconId != null) {
    180                 // The iconId can just be a package-relative resource ID, which may overlap with
    181                 // other packages. Make sure it's globally unique.
    182                 Uri iconUri = source.getIconUri(sourceIconId);
    183                 final String uniqueIconId = iconUri == null ? null : iconUri.toString();
    184                 mWantedId = uniqueIconId;
    185                 if (!TextUtils.equals(mWantedId, mCurrentId)) {
    186                     if (DBG) Log.d(TAG, "getting icon Id=" + uniqueIconId);
    187                     NowOrLater<Drawable> icon = source.getIcon(sourceIconId);
    188                     if (icon.haveNow()) {
    189                         if (DBG) Log.d(TAG, "getIcon ready now");
    190                         handleNewDrawable(icon.getNow(), uniqueIconId, source);
    191                     } else {
    192                         // make sure old icon is not visible while new one is loaded
    193                         if (DBG) Log.d(TAG , "getIcon getting later");
    194                         clearDrawable();
    195                         icon.getLater(new Consumer<Drawable>(){
    196                             @Override
    197                             public boolean consume(Drawable icon) {
    198                                 if (DBG) {
    199                                     Log.d(TAG, "IconConsumer.consume got id " + uniqueIconId +
    200                                             " want id " + mWantedId);
    201                                 }
    202                                 // ensure we have not been re-bound since the request was made.
    203                                 if (TextUtils.equals(uniqueIconId, mWantedId)) {
    204                                     handleNewDrawable(icon, uniqueIconId, source);
    205                                     return true;
    206                                 }
    207                                 return false;
    208                             }});
    209                     }
    210                 }
    211             } else {
    212                 mWantedId = null;
    213                 handleNewDrawable(null, null, source);
    214             }
    215         }
    216 
    217         private void handleNewDrawable(Drawable icon, String id, Source source) {
    218             if (icon == null) {
    219                 mWantedId = getFallbackIconId(source);
    220                 if (TextUtils.equals(mWantedId, mCurrentId)) {
    221                     return;
    222                 }
    223                 icon = getFallbackIcon(source);
    224             }
    225             setDrawable(icon, id);
    226         }
    227 
    228         private void setDrawable(Drawable icon, String id) {
    229             mCurrentId = id;
    230             setViewDrawable(mView, icon);
    231         }
    232 
    233         private void clearDrawable() {
    234             mCurrentId = null;
    235             mView.setImageDrawable(null);
    236         }
    237 
    238         protected String getFallbackIconId(Source source) {
    239             return null;
    240         }
    241 
    242         protected Drawable getFallbackIcon(Source source) {
    243             return null;
    244         }
    245 
    246     }
    247 
    248     public static class Factory extends SuggestionViewInflater {
    249         public Factory(Context context) {
    250             super(VIEW_ID, DefaultSuggestionView.class, R.layout.suggestion, context);
    251         }
    252     }
    253 
    254 }
    255