SearchableSource.java revision 2353e9c002c45c0adc8a979a49ff317cae5838b8
13e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert/*
23e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Copyright (C) 2009 The Android Open Source Project
33e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
43e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License");
53e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * you may not use this file except in compliance with the License.
63e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * You may obtain a copy of the License at
73e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
83e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *      http://www.apache.org/licenses/LICENSE-2.0
93e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Unless required by applicable law or agreed to in writing, software
113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS,
123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * See the License for the specific language governing permissions and
143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * limitations under the License.
153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert */
163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertpackage com.android.quicksearchbox;
183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
192353e9c002c45c0adc8a979a49ff317cae5838b8Bjorn Bringertimport com.android.quicksearchbox.util.Util;
202353e9c002c45c0adc8a979a49ff317cae5838b8Bjorn Bringert
21fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.app.PendingIntent;
223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.app.SearchManager;
23227180196cf8d92172cbb62f8ddaacf864be28e7Bjorn Bringertimport android.app.SearchableInfo;
243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.ComponentName;
253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.ContentResolver;
263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.Context;
27fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.Intent;
283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.pm.ActivityInfo;
296d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringertimport android.content.pm.PackageInfo;
303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.pm.PackageManager;
31cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringertimport android.content.pm.PathPermission;
32cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringertimport android.content.pm.ProviderInfo;
333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.pm.PackageManager.NameNotFoundException;
343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.database.Cursor;
353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.graphics.drawable.Drawable;
363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.net.Uri;
37fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.os.Bundle;
38fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.speech.RecognizerIntent;
393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.util.Log;
403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
41227180196cf8d92172cbb62f8ddaacf864be28e7Bjorn Bringertimport java.util.Arrays;
42227180196cf8d92172cbb62f8ddaacf864be28e7Bjorn Bringert
433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert/**
443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Represents a single suggestion source, e.g. Contacts.
453e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert */
473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertpublic class SearchableSource implements Source {
483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private static final boolean DBG = true;
500484fb4d652bfa9d5c7fb238a7cec1a6f2244e44Bjorn Bringert    private static final String TAG = "QSB.SearchableSource";
513e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
52fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // TODO: This should be exposed or moved to android-common, see http://b/issue?id=2440614
53fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // The extra key used in an intent to the speech recognizer for in-app voice search.
54fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private static final String EXTRA_CALLING_PACKAGE = "calling_package";
55fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private final Context mContext;
573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private final SearchableInfo mSearchable;
593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6062adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert    private final String mName;
6162adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert
623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private final ActivityInfo mActivityInfo;
633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
646d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert    private final int mVersionCode;
656d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert
663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    // Cached label for the activity
673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private CharSequence mLabel = null;
683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    // Cached icon for the activity
703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private Drawable.ConstantState mSourceIcon = null;
713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private final IconLoader mIconLoader;
733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public SearchableSource(Context context, SearchableInfo searchable)
753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            throws NameNotFoundException {
763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        ComponentName componentName = searchable.getSearchActivity();
773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mContext = context;
783e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSearchable = searchable;
7962adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert        mName = componentName.flattenToShortString();
806d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert        PackageManager pm = context.getPackageManager();
816d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert        mActivityInfo = pm.getActivityInfo(componentName, 0);
826d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert        PackageInfo pkgInfo = pm.getPackageInfo(componentName.getPackageName(), 0);
836d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert        mVersionCode = pkgInfo.versionCode;
84e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert        mIconLoader = createIconLoader(context, searchable.getSuggestPackage());
85e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert    }
86e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert
87fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected Context getContext() {
88fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return mContext;
89fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
90fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
91fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected SearchableInfo getSearchableInfo() {
92fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return mSearchable;
93fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
94fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
95cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    /**
96cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     * Checks if the current process can read the suggestion provider in this source.
97cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     */
98cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    public boolean canRead() {
99cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String authority = mSearchable.getSuggestAuthority();
100cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (authority == null) {
101cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            Log.w(TAG, getName() + " has no searchSuggestAuthority");
102cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return false;
103cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
104cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert
105cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        Uri.Builder uriBuilder = new Uri.Builder()
106cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                .scheme(ContentResolver.SCHEME_CONTENT)
107cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                .authority(authority);
108cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        // if content path provided, insert it now
109cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String contentPath = mSearchable.getSuggestPath();
110cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (contentPath != null) {
111cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            uriBuilder.appendEncodedPath(contentPath);
112cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
113cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        // append standard suggestion query path
114cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        uriBuilder.appendEncodedPath(SearchManager.SUGGEST_URI_PATH_QUERY);
115cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        Uri uri = uriBuilder.build();
116cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        return canRead(uri);
117cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    }
118cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert
119cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    /**
120cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     * Checks if the current process can read the given content URI.
121cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     *
122cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     * TODO: Shouldn't this be a PackageManager / Context / ContentResolver method?
123cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     */
124cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    private boolean canRead(Uri uri) {
125cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        ProviderInfo provider = mContext.getPackageManager().resolveContentProvider(
126cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                uri.getAuthority(), 0);
127cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (provider == null) {
128cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            Log.w(TAG, getName() + " has bad suggestion authority " + uri.getAuthority());
129cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return false;
130cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
131cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String readPermission = provider.readPermission;
132cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (readPermission == null) {
133cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            // No permission required to read anything in the content provider
134cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return true;
135cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
136cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        int pid = android.os.Process.myPid();
137cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        int uid = android.os.Process.myUid();
138cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (mContext.checkPermission(readPermission, pid, uid)
139cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                == PackageManager.PERMISSION_GRANTED) {
140cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            // We have permission to read everything in the content provider
141cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return true;
142cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
143cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        PathPermission[] pathPermissions = provider.pathPermissions;
144cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (pathPermissions == null || pathPermissions.length == 0) {
145cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            // We don't have the readPermission, and there are no pathPermissions
146cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            if (DBG) Log.d(TAG, "Missing " + readPermission);
147cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return false;
148cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
149cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String path = uri.getPath();
150cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        for (PathPermission perm : pathPermissions) {
151cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            String pathReadPermission = perm.getReadPermission();
152cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            if (pathReadPermission != null
153cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                    && perm.match(path)
154cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                    && mContext.checkPermission(pathReadPermission, pid, uid)
155cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                            == PackageManager.PERMISSION_GRANTED) {
156cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                // We have the path permission
157cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                return true;
158cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            }
159cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
160cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (DBG) Log.d(TAG, "Missing " + readPermission + " and no path permission applies");
161cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        return false;
162cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    }
163cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert
164e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert    private IconLoader createIconLoader(Context context, String providerPackage) {
165e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert        if (providerPackage == null) return null;
166e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert        try {
167e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert            return new CachingIconLoader(new PackageIconLoader(context, providerPackage));
168e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert        } catch (PackageManager.NameNotFoundException ex) {
169e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert            Log.e(TAG, "Suggestion provider package not found: " + providerPackage);
170e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert            return null;
171e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert        }
1723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public ComponentName getComponentName() {
1753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.getSearchActivity();
1763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1786d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert    public int getVersionCode() {
1796d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert        return mVersionCode;
1806d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert    }
1816d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert
182f252dc7a25ba08b973ecc1cfbbce58eb78d42167Bjorn Bringert    public String getName() {
18362adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert        return mName;
184d32d6753347e872aa5d9d6e9015ed7f24db3e362Bjorn Bringert    }
185d32d6753347e872aa5d9d6e9015ed7f24db3e362Bjorn Bringert
1863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public Drawable getIcon(String drawableId) {
187e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert        return mIconLoader == null ? null : mIconLoader.getIcon(drawableId);
1883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public Uri getIconUri(String drawableId) {
191e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert        return mIconLoader == null ? null : mIconLoader.getIconUri(drawableId);
1923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public CharSequence getLabel() {
1953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (mLabel == null) {
1963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Load label lazily
1973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mLabel = mActivityInfo.loadLabel(mContext.getPackageManager());
1983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
1993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mLabel;
2003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2025691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert    public CharSequence getHint() {
2035691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert        return getText(mSearchable.getHintId());
2045691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert    }
2055691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert
2063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public int getQueryThreshold() {
2073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.getSuggestThreshold();
2083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2108d9d845323514c34bb07d6eb91146a975324fcedBjorn Bringert    public CharSequence getSettingsDescription() {
211fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return getText(mSearchable.getSettingsDescriptionId());
2123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public Drawable getSourceIcon() {
2153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (mSourceIcon == null) {
2163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Load icon lazily
2173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            int iconRes = getSourceIconResource();
2183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            PackageManager pm = mContext.getPackageManager();
2193e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Drawable icon = pm.getDrawable(mActivityInfo.packageName, iconRes,
2203e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                    mActivityInfo.applicationInfo);
2213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Can't share Drawable instances, save constant state instead.
2223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mSourceIcon = (icon != null) ? icon.getConstantState() : null;
2233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Optimization, return the Drawable the first time
2243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            return icon;
2253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
2263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return (mSourceIcon != null) ? mSourceIcon.newDrawable() : null;
2273e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public Uri getSourceIconUri() {
230d32d6753347e872aa5d9d6e9015ed7f24db3e362Bjorn Bringert        int resourceId = getSourceIconResource();
2312353e9c002c45c0adc8a979a49ff317cae5838b8Bjorn Bringert        return Util.getResourceUri(getContext(), mActivityInfo.applicationInfo, resourceId);
2323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private int getSourceIconResource() {
2353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        int icon = mActivityInfo.getIconResource();
2363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return (icon != 0) ? icon : android.R.drawable.sym_def_app_icon;
2373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
239fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public boolean voiceSearchEnabled() {
240fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return mSearchable.getVoiceSearchEnabled();
241fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
242fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
243fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // TODO: not all apps handle ACTION_SEARCH properly, e.g. ApplicationsProvider.
244fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // Maybe we should add a flag to searchable, so that QSB can hide the search button?
245fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public Intent createSearchIntent(String query, Bundle appData) {
246fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Intent intent = new Intent(Intent.ACTION_SEARCH);
247fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        intent.setComponent(getComponentName());
248fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
249fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // We need CLEAR_TOP to avoid reusing an old task that has other activities
250fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // on top of the one we want.
251fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
252fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        intent.putExtra(SearchManager.USER_QUERY, query);
253fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        intent.putExtra(SearchManager.QUERY, query);
254fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (appData != null) {
255fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            intent.putExtra(SearchManager.APP_DATA, appData);
256fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
257fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return intent;
258fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
259fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
260fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public Intent createVoiceSearchIntent(Bundle appData) {
261fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (mSearchable.getVoiceSearchLaunchWebSearch()) {
262fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return WebCorpus.createVoiceWebSearchIntent(appData);
263fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
264fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return createVoiceAppSearchIntent(appData);
265fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
266fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return null;
267fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
268fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
269fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    /**
270fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * Create and return an Intent that can launch the voice search activity, perform a specific
271fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * voice transcription, and forward the results to the searchable activity.
272fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     *
273fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * This code is copied from SearchDialog
274fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     *
275fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * @return A completely-configured intent ready to send to the voice search activity
276fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     */
277fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private Intent createVoiceAppSearchIntent(Bundle appData) {
278fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        ComponentName searchActivity = mSearchable.getSearchActivity();
279fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
280fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // create the necessary intent to set up a search-and-forward operation
281fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // in the voice search system.   We have to keep the bundle separate,
282fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // because it becomes immutable once it enters the PendingIntent
283fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
284fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        queryIntent.setComponent(searchActivity);
285fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        PendingIntent pending = PendingIntent.getActivity(
286fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
287fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
288fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Now set up the bundle that will be inserted into the pending intent
289fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // when it's time to do the search.  We always build it here (even if empty)
290fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // because the voice search activity will always need to insert "QUERY" into
291fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // it anyway.
292fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Bundle queryExtras = new Bundle();
293fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (appData != null) {
294fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            queryExtras.putBundle(SearchManager.APP_DATA, appData);
295fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
296fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
297fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Now build the intent to launch the voice search.  Add all necessary
298fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // extras to launch the voice recognizer, and then all the necessary extras
299fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // to forward the results to the searchable activity
300fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
301fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
302fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
303fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Add all of the configuration options supplied by the searchable's metadata
304fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String languageModel = getString(mSearchable.getVoiceLanguageModeId());
305fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (languageModel == null) {
306fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
307fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
308fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String prompt = getString(mSearchable.getVoicePromptTextId());
309fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String language = getString(mSearchable.getVoiceLanguageId());
310fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        int maxResults = mSearchable.getVoiceMaxResults();
311fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (maxResults <= 0) {
312fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            maxResults = 1;
313fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
314fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
315fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
316fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
317fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
318fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
319fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(EXTRA_CALLING_PACKAGE,
320fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                searchActivity == null ? null : searchActivity.toShortString());
321fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
322fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Add the values that configure forwarding the results
323fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
324fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
325fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
326fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return voiceIntent;
327fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
328fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
329fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public SourceResult getSuggestions(String query, int queryLimit) {
3303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        try {
3313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Cursor cursor = getSuggestions(mContext, mSearchable, query, queryLimit);
3323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (DBG) Log.d(TAG, toString() + "[" + query + "] returned.");
333fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return new CursorBackedSourceResult(query, cursor);
3343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        } catch (RuntimeException ex) {
3353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Log.e(TAG, toString() + "[" + query + "] failed", ex);
336fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return new CursorBackedSourceResult(query);
3373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
34094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    public SuggestionCursor refreshShortcut(String shortcutId, String extraData) {
34194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        Cursor cursor = null;
34294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        try {
34394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            cursor = getValidationCursor(mContext, mSearchable, shortcutId, extraData);
34494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            if (DBG) Log.d(TAG, toString() + "[" + shortcutId + "] returned.");
34594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            if (cursor != null && cursor.getCount() > 0) {
34694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                cursor.moveToFirst();
34794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            }
348fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return new CursorBackedSourceResult(null, cursor);
34994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        } catch (RuntimeException ex) {
35094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            Log.e(TAG, toString() + "[" + shortcutId + "] failed", ex);
35194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            if (cursor != null) {
35294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                cursor.close();
35394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            }
35494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            // TODO: Should we delete the shortcut even if the failure is temporary?
35594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            return null;
35694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        }
35794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    }
35894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
359fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private class CursorBackedSourceResult extends CursorBackedSuggestionCursor
360fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            implements SourceResult {
361fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
362fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        public CursorBackedSourceResult(String userQuery) {
363fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            this(userQuery, null);
364fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
365fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
366fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        public CursorBackedSourceResult(String userQuery, Cursor cursor) {
367fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            super(userQuery, cursor);
368fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
369fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
370fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        public Source getSource() {
371fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return SearchableSource.this;
372fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
373fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
374fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        @Override
375fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        public Source getSuggestionSource() {
376fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return SearchableSource.this;
377fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
378fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
379883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringert        public boolean isSuggestionShortcut() {
380883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringert            return false;
381883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringert        }
382883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringert
383bf61e445cbe423cc2554b722b6dd38675015c36dBjorn Bringert        @Override
384bf61e445cbe423cc2554b722b6dd38675015c36dBjorn Bringert        public String toString() {
385bf61e445cbe423cc2554b722b6dd38675015c36dBjorn Bringert            return SearchableSource.this + "[" + getUserQuery() + "]";
386bf61e445cbe423cc2554b722b6dd38675015c36dBjorn Bringert        }
387bf61e445cbe423cc2554b722b6dd38675015c36dBjorn Bringert
388fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
389fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
3903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
3913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * This is a copy of {@link SearchManager#getSuggestions(SearchableInfo, String)}.
3923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
3933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private static Cursor getSuggestions(Context context, SearchableInfo searchable, String query,
3943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            int queryLimit) {
3953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (searchable == null) {
3963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            return null;
3973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String authority = searchable.getSuggestAuthority();
4003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (authority == null) {
4013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            return null;
4023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        Uri.Builder uriBuilder = new Uri.Builder()
4053e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                .scheme(ContentResolver.SCHEME_CONTENT)
4063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                .authority(authority);
4073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // if content path provided, insert it now
4093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        final String contentPath = searchable.getSuggestPath();
4103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (contentPath != null) {
4113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            uriBuilder.appendEncodedPath(contentPath);
4123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // append standard suggestion query path
4153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
4163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // get the query selection, may be null
4183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String selection = searchable.getSuggestSelection();
4193e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // inject query, either as selection args or inline
4203e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String[] selArgs = null;
4213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (selection != null) {    // use selection if provided
4223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            selArgs = new String[] { query };
4233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        } else {                    // no selection, use REST pattern
4243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            uriBuilder.appendPath(query);
4253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4273e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        uriBuilder.appendQueryParameter("limit", String.valueOf(queryLimit));
4283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        Uri uri = uriBuilder.build();
4303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // finally, make the query
4323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) {
4333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Log.d(TAG, "query(" + uri + ",null," + selection + ","
4343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                    + Arrays.toString(selArgs) + ",null)");
4353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return context.getContentResolver().query(uri, null, selection, selArgs, null);
4373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
43994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    private static Cursor getValidationCursor(Context context, SearchableInfo searchable,
44094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            String shortcutId, String extraData) {
44194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        String authority = searchable.getSuggestAuthority();
44294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        if (authority == null) {
44394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            return null;
44494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        }
44594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
44694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        Uri.Builder uriBuilder = new Uri.Builder()
44794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .scheme(ContentResolver.SCHEME_CONTENT)
44894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .authority(authority);
44994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
45094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        // if content path provided, insert it now
45194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        final String contentPath = searchable.getSuggestPath();
45294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        if (contentPath != null) {
45394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            uriBuilder.appendEncodedPath(contentPath);
45494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        }
45594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
45694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        // append the shortcut path and id
45794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_SHORTCUT);
45894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        uriBuilder.appendPath(shortcutId);
45994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
46094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        Uri uri = uriBuilder
46194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .appendQueryParameter(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, extraData)
46294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .build();
46394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
46494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        if (DBG) Log.d(TAG, "Requesting refresh " + uri);
46594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        // finally, make the query
46694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        return context.getContentResolver().query(uri, null, null, null, null);
46794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    }
46894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
4693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public boolean isWebSuggestionSource() {
470ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        return false;
4713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public boolean queryAfterZeroResults() {
4743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.queryAfterZeroResults();
4753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public boolean shouldRewriteQueryFromData() {
4783e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.shouldRewriteQueryFromData();
4793e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4803e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4813e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public boolean shouldRewriteQueryFromText() {
4823e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.shouldRewriteQueryFromText();
4833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4853e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
486abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert    public boolean equals(Object o) {
487abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert        if (o != null && o.getClass().equals(this.getClass())) {
488abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert            SearchableSource s = (SearchableSource) o;
48962adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert            return s.mName.equals(mName);
490abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert        }
491abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert        return false;
492abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert    }
493abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert
494abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert    @Override
495abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert    public int hashCode() {
49662adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert        return mName.hashCode();
497abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert    }
498abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert
499abd33f7f8d8e56348a8a87bb9e7af491d83ee833Bjorn Bringert    @Override
5003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public String toString() {
501f252dc7a25ba08b973ecc1cfbbce58eb78d42167Bjorn Bringert        return "SearchableSource{component=" + getName() + "}";
5023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public String getDefaultIntentAction() {
5053e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.getSuggestIntentAction();
5063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public String getDefaultIntentData() {
5093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.getSuggestIntentData();
5103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
512782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    public String getSuggestActionMsg(int keyCode) {
513782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
514782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        if (actionKey == null) return null;
515782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return actionKey.getSuggestActionMsg();
516782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
517782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
518782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    public String getSuggestActionMsgColumn(int keyCode) {
519782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
520782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        if (actionKey == null) return null;
521782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return actionKey.getSuggestActionMsgColumn();
522782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
523782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
524fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private CharSequence getText(int id) {
525fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (id == 0) return null;
526fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return mContext.getPackageManager().getText(mActivityInfo.packageName, id,
527fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                mActivityInfo.applicationInfo);
528fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
529fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
530fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private String getString(int id) {
531fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        CharSequence text = getText(id);
532fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return text == null ? null : text.toString();
533fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
5343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert}
535