SearchableSource.java revision cec07f6776a01e811573653acb65f23a764fa55c
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 17package com.android.quicksearchbox; 18 19import android.app.SearchManager; 20import android.app.SearchableInfo; 21import android.content.ComponentName; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.pm.ActivityInfo; 25import android.content.pm.PackageManager; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.database.Cursor; 28import android.graphics.drawable.Drawable; 29import android.net.Uri; 30import android.util.Log; 31 32import java.util.Arrays; 33 34/** 35 * Represents a single suggestion source, e.g. Contacts. 36 * 37 */ 38public class SearchableSource implements Source { 39 40 private static final boolean DBG = true; 41 private static final String TAG = "QSB.SearchableSource"; 42 43 private final Context mContext; 44 45 private final SearchableInfo mSearchable; 46 47 private final ActivityInfo mActivityInfo; 48 49 // Cached label for the activity 50 private CharSequence mLabel = null; 51 52 // Cached icon for the activity 53 private Drawable.ConstantState mSourceIcon = null; 54 55 private final boolean mIsWebSuggestionSource; 56 57 private final IconLoader mIconLoader; 58 59 public SearchableSource(Context context, SearchableInfo searchable) 60 throws NameNotFoundException { 61 this(context, searchable, false); 62 } 63 64 public SearchableSource(Context context, SearchableInfo searchable, 65 boolean isWebSuggestionSource) throws NameNotFoundException { 66 ComponentName componentName = searchable.getSearchActivity(); 67 mContext = context; 68 mSearchable = searchable; 69 mActivityInfo = context.getPackageManager().getActivityInfo(componentName, 0); 70 mIsWebSuggestionSource = isWebSuggestionSource; 71 72 mIconLoader = createIconLoader(context, searchable.getSuggestPackage()); 73 } 74 75 private IconLoader createIconLoader(Context context, String providerPackage) { 76 if (providerPackage == null) return null; 77 try { 78 return new CachingIconLoader(new PackageIconLoader(context, providerPackage)); 79 } catch (PackageManager.NameNotFoundException ex) { 80 Log.e(TAG, "Suggestion provider package not found: " + providerPackage); 81 return null; 82 } 83 } 84 85 public ComponentName getComponentName() { 86 return mSearchable.getSearchActivity(); 87 } 88 89 public String getFlattenedComponentName() { 90 return getComponentName().flattenToShortString(); 91 } 92 93 public Drawable getIcon(String drawableId) { 94 return mIconLoader == null ? null : mIconLoader.getIcon(drawableId); 95 } 96 97 public Uri getIconUri(String drawableId) { 98 return mIconLoader == null ? null : mIconLoader.getIconUri(drawableId); 99 } 100 101 public CharSequence getLabel() { 102 if (mLabel == null) { 103 // Load label lazily 104 mLabel = mActivityInfo.loadLabel(mContext.getPackageManager()); 105 } 106 return mLabel; 107 } 108 109 public int getQueryThreshold() { 110 return mSearchable.getSuggestThreshold(); 111 } 112 113 public String getSettingsDescription() { 114 return mSearchable.getSettingsDescription(); 115 } 116 117 public Drawable getSourceIcon() { 118 if (mSourceIcon == null) { 119 // Load icon lazily 120 int iconRes = getSourceIconResource(); 121 PackageManager pm = mContext.getPackageManager(); 122 Drawable icon = pm.getDrawable(mActivityInfo.packageName, iconRes, 123 mActivityInfo.applicationInfo); 124 // Can't share Drawable instances, save constant state instead. 125 mSourceIcon = (icon != null) ? icon.getConstantState() : null; 126 // Optimization, return the Drawable the first time 127 return icon; 128 } 129 return (mSourceIcon != null) ? mSourceIcon.newDrawable() : null; 130 } 131 132 public Uri getSourceIconUri() { 133 int resourceId = getSourceIconResource(); 134 return new Uri.Builder() 135 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) 136 .authority(getComponentName().getPackageName()) 137 .appendEncodedPath(String.valueOf(resourceId)) 138 .build(); 139 } 140 141 private int getSourceIconResource() { 142 int icon = mActivityInfo.getIconResource(); 143 return (icon != 0) ? icon : android.R.drawable.sym_def_app_icon; 144 } 145 146 public SuggestionCursor getSuggestions(String query, int queryLimit) { 147 try { 148 Cursor cursor = getSuggestions(mContext, mSearchable, query, queryLimit); 149 if (DBG) Log.d(TAG, toString() + "[" + query + "] returned."); 150 return new SourceResult(this, query, cursor); 151 } catch (RuntimeException ex) { 152 Log.e(TAG, toString() + "[" + query + "] failed", ex); 153 return new SourceResult(this, query); 154 } 155 } 156 157 /** 158 * This is a copy of {@link SearchManager#getSuggestions(SearchableInfo, String)}. 159 */ 160 private static Cursor getSuggestions(Context context, SearchableInfo searchable, String query, 161 int queryLimit) { 162 if (searchable == null) { 163 return null; 164 } 165 166 String authority = searchable.getSuggestAuthority(); 167 if (authority == null) { 168 return null; 169 } 170 171 Uri.Builder uriBuilder = new Uri.Builder() 172 .scheme(ContentResolver.SCHEME_CONTENT) 173 .authority(authority); 174 175 // if content path provided, insert it now 176 final String contentPath = searchable.getSuggestPath(); 177 if (contentPath != null) { 178 uriBuilder.appendEncodedPath(contentPath); 179 } 180 181 // append standard suggestion query path 182 uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY); 183 184 // get the query selection, may be null 185 String selection = searchable.getSuggestSelection(); 186 // inject query, either as selection args or inline 187 String[] selArgs = null; 188 if (selection != null) { // use selection if provided 189 selArgs = new String[] { query }; 190 } else { // no selection, use REST pattern 191 uriBuilder.appendPath(query); 192 } 193 194 uriBuilder.appendQueryParameter("limit", String.valueOf(queryLimit)); 195 196 Uri uri = uriBuilder.build(); 197 198 // finally, make the query 199 if (DBG) { 200 Log.d(TAG, "query(" + uri + ",null," + selection + "," 201 + Arrays.toString(selArgs) + ",null)"); 202 } 203 return context.getContentResolver().query(uri, null, selection, selArgs, null); 204 } 205 206 public boolean isWebSuggestionSource() { 207 return mIsWebSuggestionSource; 208 } 209 210 public boolean queryAfterZeroResults() { 211 return mSearchable.queryAfterZeroResults(); 212 } 213 214 public boolean shouldRewriteQueryFromData() { 215 return mSearchable.shouldRewriteQueryFromData(); 216 } 217 218 public boolean shouldRewriteQueryFromText() { 219 return mSearchable.shouldRewriteQueryFromText(); 220 } 221 222 @Override 223 public boolean equals(Object o) { 224 if (o != null && o.getClass().equals(this.getClass())) { 225 SearchableSource s = (SearchableSource) o; 226 return s.mSearchable.getSearchActivity().equals(mSearchable.getSearchActivity()); 227 } 228 return false; 229 } 230 231 @Override 232 public int hashCode() { 233 return mSearchable.getSearchActivity().hashCode(); 234 } 235 236 @Override 237 public String toString() { 238 return "SearchableSource{component=" + getFlattenedComponentName() + "}"; 239 } 240 241 public String getDefaultIntentAction() { 242 return mSearchable.getSuggestIntentAction(); 243 } 244 245 public String getDefaultIntentData() { 246 return mSearchable.getSuggestIntentData(); 247 } 248 249 public String getSuggestActionMsg(int keyCode) { 250 SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); 251 if (actionKey == null) return null; 252 return actionKey.getSuggestActionMsg(); 253 } 254 255 public String getSuggestActionMsgColumn(int keyCode) { 256 SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); 257 if (actionKey == null) return null; 258 return actionKey.getSuggestActionMsgColumn(); 259 } 260 261} 262