1818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banespackage android.support.v7.widget; 2818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 3818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes/* 4818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Copyright (C) 2013 The Android Open Source Project 5818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 6818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 7818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * you may not use this file except in compliance with the License. 8818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * You may obtain a copy of the License at 9818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 10818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * http://www.apache.org/licenses/LICENSE-2.0 11818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 12818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Unless required by applicable law or agreed to in writing, software 13818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * distributed under the License is distributed on an "AS IS" BASIS, 14818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * See the License for the specific language governing permissions and 16818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * limitations under the License. 17818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 18818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 19818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.app.SearchManager; 20818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.app.SearchableInfo; 21818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.ComponentName; 22818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.ContentResolver; 23818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.Context; 24818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.pm.ActivityInfo; 25818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.pm.PackageManager; 26818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.pm.PackageManager.NameNotFoundException; 27818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.res.ColorStateList; 28818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.res.Resources; 29818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.database.Cursor; 30818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.graphics.drawable.Drawable; 31818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.net.Uri; 32818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.os.Bundle; 33818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.support.v4.widget.ResourceCursorAdapter; 34818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.support.v7.appcompat.R; 35818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.Spannable; 36818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.SpannableString; 37818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.TextUtils; 38818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.style.TextAppearanceSpan; 39818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.util.Log; 40818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.util.TypedValue; 41818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.View; 42818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.View.OnClickListener; 43818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.ViewGroup; 44818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.ImageView; 45818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.TextView; 46818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 47818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport java.io.FileNotFoundException; 48818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport java.io.IOException; 49818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport java.io.InputStream; 50818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport java.util.List; 51818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport java.util.WeakHashMap; 52818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 53818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes/** 54818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Provides the contents for the suggestion drop-down list.in {@link SearchView}. 55818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @hide 56818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 57818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesclass SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListener { 58818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 59818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private static final boolean DBG = false; 60818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private static final String LOG_TAG = "SuggestionsAdapter"; 61818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private static final int QUERY_LIMIT = 50; 62818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 63818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes static final int REFINE_NONE = 0; 64818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes static final int REFINE_BY_ENTRY = 1; 65818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes static final int REFINE_ALL = 2; 66818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 67818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private SearchManager mSearchManager; 68818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private SearchView mSearchView; 69818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private SearchableInfo mSearchable; 70818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Context mProviderContext; 71818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache; 72818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private boolean mClosed = false; 73818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private int mQueryRefinement = REFINE_BY_ENTRY; 74818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 75818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // URL color 76818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private ColorStateList mUrlColor; 77818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 78818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes static final int INVALID_INDEX = -1; 79818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 80818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Cached column indexes, updated when the cursor changes. 81818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private int mText1Col = INVALID_INDEX; 82818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private int mText2Col = INVALID_INDEX; 83818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private int mText2UrlCol = INVALID_INDEX; 84818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private int mIconName1Col = INVALID_INDEX; 85818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private int mIconName2Col = INVALID_INDEX; 86818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private int mFlagsCol = INVALID_INDEX; 87818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 88818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // private final Runnable mStartSpinnerRunnable; 89818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // private final Runnable mStopSpinnerRunnable; 90818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 91818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public SuggestionsAdapter(Context context, SearchView searchView, 92818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes SearchableInfo searchable, 93818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) { 94818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes super(context, R.layout.abc_search_dropdown_item_icons_2line, 95818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes null, // no initial cursor 96818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes true); // auto-requery 97818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); 98818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mSearchView = searchView; 99818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mSearchable = searchable; 100818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // set up provider resources (gives us icons, etc.) 101818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mProviderContext = context; 102818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 103818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mOutsideDrawablesCache = outsideDrawablesCache; 104818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 105818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 106818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 107818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Enables query refinement for all suggestions. This means that an additional icon 108818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * will be shown for each entry. When clicked, the suggested text on that line will be 109818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * copied to the query text field. 110818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * <p> 111818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 112818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param refineWhat which queries to refine. Possible values are {@link #REFINE_NONE}, 113818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}. 114818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 115818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public void setQueryRefinement(int refineWhat) { 116818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mQueryRefinement = refineWhat; 117818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 118818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 119818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 120818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Returns the current query refinement preference. 121818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return value of query refinement preference 122818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 123818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public int getQueryRefinement() { 124818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return mQueryRefinement; 125818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 126818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 127818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 128818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Overridden to always return <code>false</code>, since we cannot be sure that 129818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * suggestion sources return stable IDs. 130818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 131818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 132818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public boolean hasStableIds() { 133818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return false; 134818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 135818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 136818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 137818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Use the search suggestions provider to obtain a live cursor. This will be called 138818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions). 139818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * The results will be processed in the UI thread and changeCursor() will be called. 140818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 141818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 142818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public Cursor runQueryOnBackgroundThread(CharSequence constraint) { 143818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")"); 144818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String query = (constraint == null) ? "" : constraint.toString(); 145818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 146818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * for in app search we show the progress spinner until the cursor is returned with 147818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * the results. 148818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 149818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Cursor cursor = null; 150818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mSearchView.getVisibility() != View.VISIBLE 151818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes || mSearchView.getWindowVisibility() != View.VISIBLE) { 152818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 153818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 154818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 155818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes cursor = getSearchManagerSuggestions(mSearchable, query, QUERY_LIMIT); 156818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // trigger fill window so the spinner stays up until the results are copied over and 157818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // closer to being ready 158818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (cursor != null) { 159818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes cursor.getCount(); 160818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return cursor; 161818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 162818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (RuntimeException e) { 163818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.w(LOG_TAG, "Search suggestions query threw an exception.", e); 164818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 165818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // If cursor is null or an exception was thrown, stop the spinner and return null. 166818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // changeCursor doesn't get called if cursor is null 167818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 168818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 169818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 170818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public void close() { 171818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (DBG) Log.d(LOG_TAG, "close()"); 172818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes changeCursor(null); 173818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mClosed = true; 174818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 175818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 176818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 177818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public void notifyDataSetChanged() { 178818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged"); 179818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes super.notifyDataSetChanged(); 180818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 181818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes updateSpinnerState(getCursor()); 182818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 183818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 184818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 185818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public void notifyDataSetInvalidated() { 186818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated"); 187818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes super.notifyDataSetInvalidated(); 188818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 189818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes updateSpinnerState(getCursor()); 190818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 191818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 192818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private void updateSpinnerState(Cursor cursor) { 193818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Bundle extras = cursor != null ? cursor.getExtras() : null; 194818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (DBG) { 195818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.d(LOG_TAG, "updateSpinnerState - extra = " 196818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes + (extras != null 197818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS) 198818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes : null)); 199818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 200818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Check if the Cursor indicates that the query is not complete and show the spinner 201818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (extras != null 202818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) { 203818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return; 204818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 205818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // If cursor is null or is done, stop the spinner 206818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 207818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 208818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 209818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Cache columns. 210818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 211818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 212818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public void changeCursor(Cursor c) { 213818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")"); 214818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 215818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mClosed) { 216818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.w(LOG_TAG, "Tried to change cursor after adapter was closed."); 217818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (c != null) c.close(); 218818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return; 219818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 220818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 221818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 222818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes super.changeCursor(c); 223818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 224818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (c != null) { 225818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); 226818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); 227818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL); 228818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); 229818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); 230818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mFlagsCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_FLAGS); 231818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 232818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (Exception e) { 233818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.e(LOG_TAG, "error changing cursor and caching columns", e); 234818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 235818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 236818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 237818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 238818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Tags the view with cached child view look-ups. 239818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 240818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 241818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public View newView(Context context, Cursor cursor, ViewGroup parent) { 242818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes View v = super.newView(context, cursor, parent); 243818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes v.setTag(new ChildViewCache(v)); 244818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return v; 245818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 246818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 247818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 248818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Cache of the child views of drop-drown list items, to avoid looking up the children 249818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * each time the contents of a list item are changed. 250818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 251818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private final static class ChildViewCache { 252818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public final TextView mText1; 253818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public final TextView mText2; 254818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public final ImageView mIcon1; 255818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public final ImageView mIcon2; 256818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public final ImageView mIconRefine; 257818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 258818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public ChildViewCache(View v) { 259818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mText1 = (TextView) v.findViewById(android.R.id.text1); 260818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mText2 = (TextView) v.findViewById(android.R.id.text2); 261818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mIcon1 = (ImageView) v.findViewById(android.R.id.icon1); 262818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mIcon2 = (ImageView) v.findViewById(android.R.id.icon2); 263818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mIconRefine = (ImageView) v.findViewById(R.id.edit_query); 264818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 265818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 266818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 267818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 268818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public void bindView(View view, Context context, Cursor cursor) { 269818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes ChildViewCache views = (ChildViewCache) view.getTag(); 270818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 271818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes int flags = 0; 272818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mFlagsCol != INVALID_INDEX) { 273818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes flags = cursor.getInt(mFlagsCol); 274818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 275818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (views.mText1 != null) { 276818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String text1 = getStringOrNull(cursor, mText1Col); 277818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes setViewText(views.mText1, text1); 278818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 279818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (views.mText2 != null) { 280818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // First check TEXT_2_URL 281818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes CharSequence text2 = getStringOrNull(cursor, mText2UrlCol); 282818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (text2 != null) { 283818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes text2 = formatUrl(text2); 284818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 285818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes text2 = getStringOrNull(cursor, mText2Col); 286818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 287818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 288818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // If no second line of text is indicated, allow the first line of text 289818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // to be up to two lines if it wants to be. 290818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (TextUtils.isEmpty(text2)) { 291818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (views.mText1 != null) { 292818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mText1.setSingleLine(false); 293818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mText1.setMaxLines(2); 294818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 295818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 296818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (views.mText1 != null) { 297818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mText1.setSingleLine(true); 298818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mText1.setMaxLines(1); 299818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 300818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 301818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes setViewText(views.mText2, text2); 302818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 303818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 304818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (views.mIcon1 != null) { 305818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes setViewDrawable(views.mIcon1, getIcon1(cursor), View.INVISIBLE); 306818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 307818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (views.mIcon2 != null) { 308818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes setViewDrawable(views.mIcon2, getIcon2(cursor), View.GONE); 309818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 310818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mQueryRefinement == REFINE_ALL 311818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes || (mQueryRefinement == REFINE_BY_ENTRY 312818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes && (flags & SearchManager.FLAG_QUERY_REFINEMENT) != 0)) { 313818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mIconRefine.setVisibility(View.VISIBLE); 314818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mIconRefine.setTag(views.mText1.getText()); 315818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mIconRefine.setOnClickListener(this); 316818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 317818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes views.mIconRefine.setVisibility(View.GONE); 318818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 319818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 320818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 321818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public void onClick(View v) { 322818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Object tag = v.getTag(); 323818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (tag instanceof CharSequence) { 324818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mSearchView.onQueryRefine((CharSequence) tag); 325818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 326818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 327818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 328818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private CharSequence formatUrl(CharSequence url) { 329818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mUrlColor == null) { 330818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Lazily get the URL color from the current theme. 331818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes TypedValue colorValue = new TypedValue(); 332818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true); 333818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId); 334818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 335818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 336818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes SpannableString text = new SpannableString(url); 337818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null), 338818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 0, url.length(), 339818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 340818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return text; 341818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 342818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 343818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private void setViewText(TextView v, CharSequence text) { 344818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Set the text even if it's null, since we need to clear any previous text. 345818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes v.setText(text); 346818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 347818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (TextUtils.isEmpty(text)) { 348818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes v.setVisibility(View.GONE); 349818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 350818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes v.setVisibility(View.VISIBLE); 351818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 352818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 353818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 354818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable getIcon1(Cursor cursor) { 355818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mIconName1Col == INVALID_INDEX) { 356818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 357818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 358818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String value = cursor.getString(mIconName1Col); 359818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable drawable = getDrawableFromResourceValue(value); 360818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawable != null) { 361818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 362818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 363818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return getDefaultIcon1(cursor); 364818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 365818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 366818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable getIcon2(Cursor cursor) { 367818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mIconName2Col == INVALID_INDEX) { 368818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 369818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 370818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String value = cursor.getString(mIconName2Col); 371818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return getDrawableFromResourceValue(value); 372818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 373818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 374818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 375818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Sets the drawable in an image view, makes sure the view is only visible if there 376818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * is a drawable. 377818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 378818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private void setViewDrawable(ImageView v, Drawable drawable, int nullVisibility) { 379818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Set the icon even if the drawable is null, since we need to clear any 380818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // previous icon. 381818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes v.setImageDrawable(drawable); 382818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 383818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawable == null) { 384818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes v.setVisibility(nullVisibility); 385818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 386818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes v.setVisibility(View.VISIBLE); 387818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 388818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // This is a hack to get any animated drawables (like a 'working' spinner) 389818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // to animate. You have to setVisible true on an AnimationDrawable to get 390818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // it to start animating, but it must first have been false or else the 391818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // call to setVisible will be ineffective. We need to clear up the story 392818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // about animated drawables in the future, see http://b/1878430. 393818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes drawable.setVisible(false, false); 394818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes drawable.setVisible(true, false); 395818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 396818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 397818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 398818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 399818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Gets the text to show in the query field when a suggestion is selected. 400818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 401818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param cursor The Cursor to read the suggestion data from. The Cursor should already 402818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * be moved to the suggestion that is to be read from. 403818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return The text to show, or <code>null</code> if the query should not be 404818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * changed when selecting this suggestion. 405818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 406818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 407818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public CharSequence convertToString(Cursor cursor) { 408818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (cursor == null) { 409818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 410818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 411818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 412818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY); 413818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (query != null) { 414818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return query; 415818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 416818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 417818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mSearchable.shouldRewriteQueryFromData()) { 418818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA); 419818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (data != null) { 420818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return data; 421818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 422818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 423818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 424818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mSearchable.shouldRewriteQueryFromText()) { 425818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1); 426818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (text1 != null) { 427818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return text1; 428818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 429818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 430818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 431818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 432818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 433818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 434818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 435818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * This method is overridden purely to provide a bit of protection against 436818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * flaky content providers. 437818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 438818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @see android.widget.ListAdapter#getView(int, View, ViewGroup) 439818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 440818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes @Override 441818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public View getView(int position, View convertView, ViewGroup parent) { 442818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 443818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return super.getView(position, convertView, parent); 444818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (RuntimeException e) { 445818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e); 446818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Put exception string in item title 447818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes View v = newView(mContext, mCursor, parent); 448818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (v != null) { 449818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes ChildViewCache views = (ChildViewCache) v.getTag(); 450818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes TextView tv = views.mText1; 451818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes tv.setText(e.toString()); 452818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 453818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return v; 454818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 455818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 456818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 457818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 458818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Gets a drawable given a value provided by a suggestion provider. 459818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 460818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * This value could be just the string value of a resource id 461818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * (e.g., "2130837524"), in which case we will try to retrieve a drawable from 462818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * the provider's resources. If the value is not an integer, it is 463818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * treated as a Uri and opened with 464818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * {@link ContentResolver#openOutputStream(android.net.Uri, String)}. 465818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 466818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * All resources and URIs are read using the suggestion provider's context. 467818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 468818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * If the string is not formatted as expected, or no drawable can be found for 469818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * the provided value, this method returns null. 470818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 471818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param drawableId a string like "2130837524", 472818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * "android.resource://com.android.alarmclock/2130837524", 473818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * or "content://contacts/photos/253". 474818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return a Drawable, or null if none found 475818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 476818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable getDrawableFromResourceValue(String drawableId) { 477818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) { 478818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 479818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 480818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 481818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // First, see if it's just an integer 482818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes int resourceId = Integer.parseInt(drawableId); 483818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // It's an int, look for it in the cache 484818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE 485818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes + "://" + mProviderContext.getPackageName() + "/" + resourceId; 486818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Must use URI as cache key, since ints are app-specific 487818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable drawable = checkIconCache(drawableUri); 488818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawable != null) { 489818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 490818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 491818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Not cached, find it by resource ID 492818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes drawable = mProviderContext.getResources().getDrawable(resourceId); 493818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Stick it in the cache, using the URI as key 494818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes storeInIconCache(drawableUri, drawable); 495818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 496818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (NumberFormatException nfe) { 497818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // It's not an integer, use it as a URI 498818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable drawable = checkIconCache(drawableId); 499818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawable != null) { 500818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 501818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 502818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Uri uri = Uri.parse(drawableId); 503818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes drawable = getDrawable(uri); 504818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes storeInIconCache(drawableId, drawable); 505818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 506818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (Resources.NotFoundException nfe) { 507818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // It was an integer, but it couldn't be found, bail out 508818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.w(LOG_TAG, "Icon resource not found: " + drawableId); 509818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 510818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 511818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 512818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 513818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 514818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Gets a drawable by URI, without using the cache. 515818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 516818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return A drawable, or {@code null} if the drawable could not be loaded. 517818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 518818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable getDrawable(Uri uri) { 519818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 520818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String scheme = uri.getScheme(); 521818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { 522818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Load drawables through Resources, to get the source density information 523818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 524818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return getDrawableFromResourceUri(uri); 525818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (Resources.NotFoundException ex) { 526818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("Resource does not exist: " + uri); 527818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 528818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 529818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Let the ContentResolver handle content and file URIs. 530818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes InputStream stream = mProviderContext.getContentResolver().openInputStream(uri); 531818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (stream == null) { 532818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("Failed to open " + uri); 533818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 534818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 535818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return Drawable.createFromStream(stream, null); 536818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } finally { 537818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 538818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes stream.close(); 539818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (IOException ex) { 540818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex); 541818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 542818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 543818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 544818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (FileNotFoundException fnfe) { 545818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage()); 546818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 547818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 548818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 549818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 550818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 551818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 552818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable checkIconCache(String resourceUri) { 553818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri); 554818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (cached == null) { 555818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 556818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 557818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri); 558818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return cached.newDrawable(); 559818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 560818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 561818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private void storeInIconCache(String resourceUri, Drawable drawable) { 562818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawable != null) { 563818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState()); 564818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 565818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 566818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 567818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 568818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Gets the left-hand side icon that will be used for the current suggestion 569818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * if the suggestion contains an icon column but no icon or a broken icon. 570818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 571818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param cursor A cursor positioned at the current suggestion. 572818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return A non-null drawable. 573818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 574818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable getDefaultIcon1(Cursor cursor) { 575818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Check the component that gave us the suggestion 576818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable drawable = getActivityIconWithCache(mSearchable.getSearchActivity()); 577818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawable != null) { 578818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 579818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 580818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 581818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Fall back to a default icon 582818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return mContext.getPackageManager().getDefaultActivityIcon(); 583818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 584818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 585818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 586818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Gets the activity or application icon for an activity. 587818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Uses the local icon cache for fast repeated lookups. 588818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 589818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param component Name of an activity. 590818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return A drawable, or {@code null} if neither the activity nor the application 591818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * has an icon set. 592818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 593818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable getActivityIconWithCache(ComponentName component) { 594818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // First check the icon cache 595818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String componentIconKey = component.flattenToShortString(); 596818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Using containsKey() since we also store null values. 597818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (mOutsideDrawablesCache.containsKey(componentIconKey)) { 598818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey); 599818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return cached == null ? null : cached.newDrawable(mProviderContext.getResources()); 600818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 601818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Then try the activity or application icon 602818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable drawable = getActivityIcon(component); 603818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // Stick it in the cache so we don't do this lookup again. 604818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState(); 605818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes mOutsideDrawablesCache.put(componentIconKey, toCache); 606818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 607818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 608818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 609818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 610818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Gets the activity or application icon for an activity. 611818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 612818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param component Name of an activity. 613818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return A drawable, or {@code null} if neither the acitivy or the application 614818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * have an icon set. 615818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 616818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private Drawable getActivityIcon(ComponentName component) { 617818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes PackageManager pm = mContext.getPackageManager(); 618818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes final ActivityInfo activityInfo; 619818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 620818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA); 621818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (NameNotFoundException ex) { 622818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.w(LOG_TAG, ex.toString()); 623818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 624818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 625818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes int iconId = activityInfo.getIconResource(); 626818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (iconId == 0) return null; 627818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String pkg = component.getPackageName(); 628818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo); 629818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (drawable == null) { 630818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for " 631818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes + component.flattenToShortString()); 632818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 633818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 634818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return drawable; 635818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 636818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 637818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 638818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Gets the value of a string column by name. 639818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * 640818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param cursor Cursor to read the value from. 641818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @param columnName The name of the column to read. 642818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @return The value of the given column, or <code>null</null> 643818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * if the cursor does not contain the given column. 644818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 645818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes public static String getColumnString(Cursor cursor, String columnName) { 646818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes int col = cursor.getColumnIndex(columnName); 647818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return getStringOrNull(cursor, col); 648818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 649818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 650818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes private static String getStringOrNull(Cursor cursor, int col) { 651818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (col == INVALID_INDEX) { 652818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 653818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 654818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 655818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return cursor.getString(col); 656818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (Exception e) { 657818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Log.e(LOG_TAG, 658818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes "unexpected error retrieving valid column from cursor, " 659818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes + "did the remote process die?", e); 660818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 661818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 662818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 663818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 664818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 665818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Import of hidden method: ContentResolver.getResourceId(Uri). 666818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Modified to return a drawable, rather than a hidden type. 667818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 668818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Drawable getDrawableFromResourceUri(Uri uri) throws FileNotFoundException { 669818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String authority = uri.getAuthority(); 670818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Resources r; 671818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (TextUtils.isEmpty(authority)) { 672818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("No authority: " + uri); 673818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 674818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 675818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes r = mContext.getPackageManager().getResourcesForApplication(authority); 676818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (NameNotFoundException ex) { 677818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("No package found for authority: " + uri); 678818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 679818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 680818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes List<String> path = uri.getPathSegments(); 681818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (path == null) { 682818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("No path: " + uri); 683818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 684818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes int len = path.size(); 685818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes int id; 686818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (len == 1) { 687818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes try { 688818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes id = Integer.parseInt(path.get(0)); 689818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } catch (NumberFormatException e) { 690818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("Single path segment is not a resource ID: " + uri); 691818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 692818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else if (len == 2) { 693818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes id = r.getIdentifier(path.get(1), path.get(0), authority); 694818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { 695818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("More than two path segments: " + uri); 696818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 697818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (id == 0) { 698818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes throw new FileNotFoundException("No resource found for: " + uri); 699818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 700818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return r.getDrawable(id); 701818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 702818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 703818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes /** 704818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Import of hidden method: SearchManager.getSuggestions(SearchableInfo, String, int). 705818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */ 706818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Cursor getSearchManagerSuggestions(SearchableInfo searchable, String query, int limit) { 707818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (searchable == null) { 708818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 709818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 710818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 711818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String authority = searchable.getSuggestAuthority(); 712818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (authority == null) { 713818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return null; 714818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 715818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 716818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Uri.Builder uriBuilder = new Uri.Builder() 717818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes .scheme(ContentResolver.SCHEME_CONTENT) 718818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes .authority(authority) 719818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() 720818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel() 721818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 722818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // if content path provided, insert it now 723818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes final String contentPath = searchable.getSuggestPath(); 724818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (contentPath != null) { 725818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes uriBuilder.appendEncodedPath(contentPath); 726818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 727818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 728818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // append standard suggestion query path 729818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY); 730818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 731818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // get the query selection, may be null 732818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String selection = searchable.getSuggestSelection(); 733818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // inject query, either as selection args or inline 734818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes String[] selArgs = null; 735818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (selection != null) { // use selection if provided 736818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes selArgs = new String[] { query }; 737818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } else { // no selection, use REST pattern 738818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes uriBuilder.appendPath(query); 739818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 740818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 741818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes if (limit > 0) { 742818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes uriBuilder.appendQueryParameter("limit", String.valueOf(limit)); 743818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 744818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 745818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes Uri uri = uriBuilder.build(); 746818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes 747818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes // finally, make the query 748818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes return mContext.getContentResolver().query(uri, null, selection, selArgs, null); 749818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes } 750818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes}