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