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