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