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
19516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwoodimport com.android.quicksearchbox.util.NamedTaskExecutor;
202353e9c002c45c0adc8a979a49ff317cae5838b8Bjorn Bringertimport com.android.quicksearchbox.util.Util;
212353e9c002c45c0adc8a979a49ff317cae5838b8Bjorn Bringert
22fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.app.PendingIntent;
233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.app.SearchManager;
24227180196cf8d92172cbb62f8ddaacf864be28e7Bjorn Bringertimport android.app.SearchableInfo;
253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.ComponentName;
263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.ContentResolver;
273e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.Context;
28fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.Intent;
293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.pm.ActivityInfo;
306d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringertimport android.content.pm.PackageInfo;
313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.pm.PackageManager;
3209552c760ad82eb90795e98de96c65005d6ebafcMathew Inwoodimport android.content.pm.PackageManager.NameNotFoundException;
33cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringertimport android.content.pm.PathPermission;
34cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringertimport android.content.pm.ProviderInfo;
353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.database.Cursor;
363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.graphics.drawable.Drawable;
373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.net.Uri;
38fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.os.Bundle;
39e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwoodimport android.os.Handler;
40fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.speech.RecognizerIntent;
413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.util.Log;
423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
43227180196cf8d92172cbb62f8ddaacf864be28e7Bjorn Bringertimport java.util.Arrays;
44227180196cf8d92172cbb62f8ddaacf864be28e7Bjorn Bringert
453e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert/**
463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Represents a single suggestion source, e.g. Contacts.
473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert */
4849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringertpublic class SearchableSource extends AbstractSource {
493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
50b5fc08b7f16a32d3865f44b7f26d8aaa5304a2adBjorn Bringert    private static final boolean DBG = false;
510484fb4d652bfa9d5c7fb238a7cec1a6f2244e44Bjorn Bringert    private static final String TAG = "QSB.SearchableSource";
523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
53fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // TODO: This should be exposed or moved to android-common, see http://b/issue?id=2440614
54fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    // The extra key used in an intent to the speech recognizer for in-app voice search.
55fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private static final String EXTRA_CALLING_PACKAGE = "calling_package";
56fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private final SearchableInfo mSearchable;
583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5962adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert    private final String mName;
6062adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert
613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private final ActivityInfo mActivityInfo;
623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
636d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert    private final int mVersionCode;
646d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert
653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    // Cached label for the activity
663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private CharSequence mLabel = null;
673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    // Cached icon for the activity
693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private Drawable.ConstantState mSourceIcon = null;
703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
7109552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood    private Uri mSuggestUriBase;
7209552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood
73516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwood    public SearchableSource(Context context, SearchableInfo searchable, Handler uiThread,
74516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwood            NamedTaskExecutor iconLoader) throws NameNotFoundException {
75516781305d7427e79928c95c6ea2b7689a4bf6ceMathew Inwood        super(context, uiThread, iconLoader);
763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        ComponentName componentName = searchable.getSearchActivity();
775229b06f00d20aac76cd8519b37f2a579d61c54fMathew Inwood        if (DBG) Log.d(TAG, "created Searchable for " + componentName);
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    }
85e1fc4581ebab65220a82bcdb3272b4eb85453837Bjorn Bringert
8677909685887bd6db7454b73cf274afc3aca2f58dBjorn Bringert    public SearchableInfo getSearchableInfo() {
87fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return mSearchable;
88fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
89fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
90cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    /**
91cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     * Checks if the current process can read the suggestion provider in this source.
92cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     */
93cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    public boolean canRead() {
94cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String authority = mSearchable.getSuggestAuthority();
95cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (authority == null) {
9681a0897ff9685f3313c58294bf7973700c468b2bBjorn Bringert            // TODO: maybe we should have a way to distinguish between having suggestions
9781a0897ff9685f3313c58294bf7973700c468b2bBjorn Bringert            // and being readable.
9881a0897ff9685f3313c58294bf7973700c468b2bBjorn Bringert            return true;
99cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
100cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert
101cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        Uri.Builder uriBuilder = new Uri.Builder()
102cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                .scheme(ContentResolver.SCHEME_CONTENT)
103cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                .authority(authority);
104cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        // if content path provided, insert it now
105cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String contentPath = mSearchable.getSuggestPath();
106cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (contentPath != null) {
107cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            uriBuilder.appendEncodedPath(contentPath);
108cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
109cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        // append standard suggestion query path
110cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        uriBuilder.appendEncodedPath(SearchManager.SUGGEST_URI_PATH_QUERY);
111cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        Uri uri = uriBuilder.build();
112cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        return canRead(uri);
113cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    }
114cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert
115cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    /**
116cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     * Checks if the current process can read the given content URI.
117cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     *
118cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     * TODO: Shouldn't this be a PackageManager / Context / ContentResolver method?
119cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert     */
120cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    private boolean canRead(Uri uri) {
12149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        ProviderInfo provider = getContext().getPackageManager().resolveContentProvider(
122cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                uri.getAuthority(), 0);
123cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (provider == null) {
124cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            Log.w(TAG, getName() + " has bad suggestion authority " + uri.getAuthority());
125cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return false;
126cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
127cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String readPermission = provider.readPermission;
128cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (readPermission == null) {
129cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            // No permission required to read anything in the content provider
130cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return true;
131cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
132cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        int pid = android.os.Process.myPid();
133cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        int uid = android.os.Process.myUid();
13449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        if (getContext().checkPermission(readPermission, pid, uid)
135cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                == PackageManager.PERMISSION_GRANTED) {
136cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            // We have permission to read everything in the content provider
137cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return true;
138cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
139cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        PathPermission[] pathPermissions = provider.pathPermissions;
140cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (pathPermissions == null || pathPermissions.length == 0) {
141cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            // We don't have the readPermission, and there are no pathPermissions
142cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            if (DBG) Log.d(TAG, "Missing " + readPermission);
143cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            return false;
144cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
145cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        String path = uri.getPath();
146cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        for (PathPermission perm : pathPermissions) {
147cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            String pathReadPermission = perm.getReadPermission();
148cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            if (pathReadPermission != null
149cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                    && perm.match(path)
15049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert                    && getContext().checkPermission(pathReadPermission, pid, uid)
151cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                            == PackageManager.PERMISSION_GRANTED) {
152cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                // We have the path permission
153cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert                return true;
154cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert            }
155cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        }
156cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        if (DBG) Log.d(TAG, "Missing " + readPermission + " and no path permission applies");
157cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert        return false;
158cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert    }
159cc10dcf07c4e787abd66f4e2cfed31a09580a3b0Bjorn Bringert
16081a0897ff9685f3313c58294bf7973700c468b2bBjorn Bringert    public ComponentName getIntentComponent() {
1613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.getSearchActivity();
1623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1646d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert    public int getVersionCode() {
1656d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert        return mVersionCode;
1666d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert    }
1676d5cbd67f7a5f824babb5c892b0f30bfd9f4ff23Bjorn Bringert
168f252dc7a25ba08b973ecc1cfbbce58eb78d42167Bjorn Bringert    public String getName() {
16962adab88055fd0ef6779242245cdc8c3ae5f999cBjorn Bringert        return mName;
170d32d6753347e872aa5d9d6e9015ed7f24db3e362Bjorn Bringert    }
171d32d6753347e872aa5d9d6e9015ed7f24db3e362Bjorn Bringert
17249fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    @Override
17349fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert    protected String getIconPackage() {
17449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        // Get icons from the package containing the suggestion provider, if any
17549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        String iconPackage = mSearchable.getSuggestPackage();
17649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        if (iconPackage != null) {
17749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return iconPackage;
17849fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        } else {
17949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            // Fall back to the package containing the searchable activity
18049fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return mSearchable.getSearchActivity().getPackageName();
18149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        }
1823e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public CharSequence getLabel() {
1853e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (mLabel == null) {
1863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Load label lazily
18749fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            mLabel = mActivityInfo.loadLabel(getContext().getPackageManager());
1883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
1893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mLabel;
1903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1925691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert    public CharSequence getHint() {
1935691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert        return getText(mSearchable.getHintId());
1945691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert    }
1955691f9062b2a558ba39c700d65bc522d397ebc75Bjorn Bringert
1963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public int getQueryThreshold() {
1973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.getSuggestThreshold();
1983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2008d9d845323514c34bb07d6eb91146a975324fcedBjorn Bringert    public CharSequence getSettingsDescription() {
201fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return getText(mSearchable.getSettingsDescriptionId());
2023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public Drawable getSourceIcon() {
2053e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (mSourceIcon == null) {
206e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert            Drawable icon = loadSourceIcon();
207e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert            if (icon == null) {
208e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert                icon = getContext().getResources().getDrawable(R.drawable.corpus_icon_default);
209e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert            }
2103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Can't share Drawable instances, save constant state instead.
2113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mSourceIcon = (icon != null) ? icon.getConstantState() : null;
2123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Optimization, return the Drawable the first time
2133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            return icon;
2143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
2153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return (mSourceIcon != null) ? mSourceIcon.newDrawable() : null;
2163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
218e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert    private Drawable loadSourceIcon() {
219e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        int iconRes = getSourceIconResource();
220e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        if (iconRes == 0) return null;
221e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        PackageManager pm = getContext().getPackageManager();
222e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        return pm.getDrawable(mActivityInfo.packageName, iconRes,
223e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert                mActivityInfo.applicationInfo);
224e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert    }
225e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert
2263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public Uri getSourceIconUri() {
227d32d6753347e872aa5d9d6e9015ed7f24db3e362Bjorn Bringert        int resourceId = getSourceIconResource();
228e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        if (resourceId == 0) {
229e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert            return Util.getResourceUri(getContext(), R.drawable.corpus_icon_default);
230e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        } else {
231e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert            return Util.getResourceUri(getContext(), mActivityInfo.applicationInfo, resourceId);
232e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        }
2333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private int getSourceIconResource() {
236e46ac8349b012ec2c649d4e20bc835fec9ec681fBjorn Bringert        return mActivityInfo.getIconResource();
2373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
239fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public boolean voiceSearchEnabled() {
240fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return mSearchable.getVoiceSearchEnabled();
241fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
242fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
243fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public Intent createVoiceSearchIntent(Bundle appData) {
244fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (mSearchable.getVoiceSearchLaunchWebSearch()) {
24549fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return createVoiceWebSearchIntent(appData);
246fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
247fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return createVoiceAppSearchIntent(appData);
248fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
249fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return null;
250fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
251fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
252fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    /**
253fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * Create and return an Intent that can launch the voice search activity, perform a specific
254fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * voice transcription, and forward the results to the searchable activity.
255fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     *
256fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * This code is copied from SearchDialog
257fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     *
258fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     * @return A completely-configured intent ready to send to the voice search activity
259fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert     */
260fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private Intent createVoiceAppSearchIntent(Bundle appData) {
261fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        ComponentName searchActivity = mSearchable.getSearchActivity();
262fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
263fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // create the necessary intent to set up a search-and-forward operation
264fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // in the voice search system.   We have to keep the bundle separate,
265fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // because it becomes immutable once it enters the PendingIntent
266fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
267fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        queryIntent.setComponent(searchActivity);
268fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        PendingIntent pending = PendingIntent.getActivity(
269fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
270fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
271fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Now set up the bundle that will be inserted into the pending intent
272fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // when it's time to do the search.  We always build it here (even if empty)
273fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // because the voice search activity will always need to insert "QUERY" into
274fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // it anyway.
275fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Bundle queryExtras = new Bundle();
276fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (appData != null) {
277fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            queryExtras.putBundle(SearchManager.APP_DATA, appData);
278fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
279fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
280fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Now build the intent to launch the voice search.  Add all necessary
281fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // extras to launch the voice recognizer, and then all the necessary extras
282fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // to forward the results to the searchable activity
283fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
284fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
285fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
286fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Add all of the configuration options supplied by the searchable's metadata
287fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String languageModel = getString(mSearchable.getVoiceLanguageModeId());
288fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (languageModel == null) {
289fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
290fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
291fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String prompt = getString(mSearchable.getVoicePromptTextId());
292fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String language = getString(mSearchable.getVoiceLanguageId());
293fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        int maxResults = mSearchable.getVoiceMaxResults();
294fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (maxResults <= 0) {
295fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            maxResults = 1;
296fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
297fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
298fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
299fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
300fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
301fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
302fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(EXTRA_CALLING_PACKAGE,
303fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                searchActivity == null ? null : searchActivity.toShortString());
304fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
305fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        // Add the values that configure forwarding the results
306fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
307fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
308fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
309fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return voiceIntent;
310fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
311fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
312cd1e3ba5f7c3f5242345ff6f674281e3d6366e24Mathew Inwood    public SourceResult getSuggestions(String query, int queryLimit, boolean onlySource) {
3133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        try {
31449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            Cursor cursor = getSuggestions(getContext(), mSearchable, query, queryLimit);
3153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (DBG) Log.d(TAG, toString() + "[" + query + "] returned.");
31649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return new CursorBackedSourceResult(this, query, cursor);
3173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        } catch (RuntimeException ex) {
3183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Log.e(TAG, toString() + "[" + query + "] failed", ex);
31949fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return new CursorBackedSourceResult(this, query);
3203e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
32394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    public SuggestionCursor refreshShortcut(String shortcutId, String extraData) {
32494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        Cursor cursor = null;
32594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        try {
32649fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            cursor = getValidationCursor(getContext(), mSearchable, shortcutId, extraData);
32794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            if (DBG) Log.d(TAG, toString() + "[" + shortcutId + "] returned.");
32894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            if (cursor != null && cursor.getCount() > 0) {
32994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                cursor.moveToFirst();
33094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            }
33149fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert            return new CursorBackedSourceResult(this, null, cursor);
33294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        } catch (RuntimeException ex) {
33394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            Log.e(TAG, toString() + "[" + shortcutId + "] failed", ex);
33494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            if (cursor != null) {
33594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                cursor.close();
33694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            }
33794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            // TODO: Should we delete the shortcut even if the failure is temporary?
33894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            return null;
33994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        }
34094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    }
34194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
34209552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood    public String getSuggestUri() {
34309552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        Uri uri = getSuggestUriBase(mSearchable);
34409552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        if (uri == null) return null;
34509552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        return uri.toString();
34609552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood    }
34709552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood
34809552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood    private synchronized Uri getSuggestUriBase(SearchableInfo searchable) {
3493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (searchable == null) {
3503e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            return null;
3513e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
35209552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        if (mSuggestUriBase == null) {
3533e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
35409552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            String authority = searchable.getSuggestAuthority();
35509552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            if (authority == null) {
35609552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood                return null;
35709552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            }
3583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
35909552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            Uri.Builder uriBuilder = new Uri.Builder()
36009552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood                    .scheme(ContentResolver.SCHEME_CONTENT)
36109552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood                    .authority(authority);
3623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
36309552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            // if content path provided, insert it now
36409552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            final String contentPath = searchable.getSuggestPath();
36509552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            if (contentPath != null) {
36609552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood                uriBuilder.appendEncodedPath(contentPath);
36709552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            }
36809552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood
36909552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            // append standard suggestion query path
37009552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
37109552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            mSuggestUriBase = uriBuilder.build();
3723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
37309552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        return mSuggestUriBase;
37409552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood    }
3753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
37609552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood    /**
37709552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood     * This is a copy of {@link SearchManager#getSuggestions(SearchableInfo, String)}.
37809552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood     */
37909552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood    private Cursor getSuggestions(Context context, SearchableInfo searchable, String query,
38009552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood            int queryLimit) {
38109552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood
38209552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        Uri base = getSuggestUriBase(searchable);
38309552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        if (base == null) return null;
38409552c760ad82eb90795e98de96c65005d6ebafcMathew Inwood        Uri.Builder uriBuilder = base.buildUpon();
3853e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // get the query selection, may be null
3873e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String selection = searchable.getSuggestSelection();
3883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // inject query, either as selection args or inline
3893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String[] selArgs = null;
3903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (selection != null) {    // use selection if provided
3913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            selArgs = new String[] { query };
3923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        } else {                    // no selection, use REST pattern
3933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            uriBuilder.appendPath(query);
3943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        uriBuilder.appendQueryParameter("limit", String.valueOf(queryLimit));
3973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        Uri uri = uriBuilder.build();
3993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // finally, make the query
4013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) {
4023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Log.d(TAG, "query(" + uri + ",null," + selection + ","
4033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                    + Arrays.toString(selArgs) + ",null)");
4043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
405e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        Cursor c = context.getContentResolver().query(uri, null, selection, selArgs, null);
406e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        if (DBG) Log.d(TAG, "Got cursor from " + mName + ": " + c);
407e29d52aa72c96c3147fa91d83aeb8dafc6d1f578Mathew Inwood        return c;
4083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
41094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    private static Cursor getValidationCursor(Context context, SearchableInfo searchable,
41194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            String shortcutId, String extraData) {
41294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        String authority = searchable.getSuggestAuthority();
41394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        if (authority == null) {
41494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            return null;
41594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        }
41694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
41794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        Uri.Builder uriBuilder = new Uri.Builder()
41894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .scheme(ContentResolver.SCHEME_CONTENT)
41994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .authority(authority);
42094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
42194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        // if content path provided, insert it now
42294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        final String contentPath = searchable.getSuggestPath();
42394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        if (contentPath != null) {
42494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney            uriBuilder.appendEncodedPath(contentPath);
42594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        }
42694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
42794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        // append the shortcut path and id
42894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_SHORTCUT);
42994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        uriBuilder.appendPath(shortcutId);
43094e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
43194e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        Uri uri = uriBuilder
43294e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .appendQueryParameter(SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA, extraData)
43394e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney                .build();
43494e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
43594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        if (DBG) Log.d(TAG, "Requesting refresh " + uri);
43694e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        // finally, make the query
43794e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        return context.getContentResolver().query(uri, null, null, null, null);
43894e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney    }
43994e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney
44053aab8c4459f45664d04ec882d67094c52b78695Bjorn Bringert    public int getMaxShortcuts(Config config) {
44153aab8c4459f45664d04ec882d67094c52b78695Bjorn Bringert        return config.getMaxShortcuts(getName());
4423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
444f3f70e5ae88f06ff6dabdec9e7c71a19ca1e7108Bjorn Bringert    public boolean includeInAll() {
445f3f70e5ae88f06ff6dabdec9e7c71a19ca1e7108Bjorn Bringert        return true;
446f3f70e5ae88f06ff6dabdec9e7c71a19ca1e7108Bjorn Bringert    }
447f3f70e5ae88f06ff6dabdec9e7c71a19ca1e7108Bjorn Bringert
4483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public boolean queryAfterZeroResults() {
4493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.queryAfterZeroResults();
4503e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4513e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public String getDefaultIntentAction() {
45393bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        String action = mSearchable.getSuggestIntentAction();
45493bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        if (action != null) return action;
45593bd2e70b8b08da1ec37fd0e990dac05551d2e90Bjorn Bringert        return Intent.ACTION_SEARCH;
4563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public String getDefaultIntentData() {
4593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return mSearchable.getSuggestIntentData();
4603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
462fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private CharSequence getText(int id) {
463fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (id == 0) return null;
46449fd8e0994577badc6194c2c3b5f771f2b793fe4Bjorn Bringert        return getContext().getPackageManager().getText(mActivityInfo.packageName, id,
465fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                mActivityInfo.applicationInfo);
466fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
467fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
468fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private String getString(int id) {
469fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        CharSequence text = getText(id);
470fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return text == null ? null : text.toString();
471fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
4723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert}
473