SearchableSources.java revision 883c1bf364e38c5b133afb55f8493a14b65f4dd4
1fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert/* 2fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * Copyright (C) 2009 The Android Open Source Project 3fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * 4fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License"); 5fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * you may not use this file except in compliance with the License. 6fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * You may obtain a copy of the License at 7fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * 8fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * http://www.apache.org/licenses/LICENSE-2.0 9fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * 10fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * Unless required by applicable law or agreed to in writing, software 11fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS, 12fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * See the License for the specific language governing permissions and 14fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * limitations under the License. 15fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert */ 16fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 17fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertpackage com.android.quicksearchbox; 18fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 19fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.app.SearchManager; 20fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.app.SearchableInfo; 21fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.BroadcastReceiver; 22fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.ComponentName; 23fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.Context; 24fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.Intent; 25fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.IntentFilter; 26fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.pm.PackageManager; 27fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.content.pm.PackageManager.NameNotFoundException; 28fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.database.DataSetObservable; 29fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.database.DataSetObserver; 30fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.os.Handler; 31fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.util.Log; 32fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 33fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport java.util.Collection; 34fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport java.util.HashMap; 35fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 36fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert/** 37fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * Maintains a list of search sources. 38fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert */ 39883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringertpublic class SearchableSources implements Sources { 40fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 41fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // set to true to enable the more verbose debug logging for this file 42fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private static final boolean DBG = false; 43fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private static final String TAG = "QSB.SearchableSources"; 44fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 45fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // The number of milliseconds that source update requests are delayed to 46fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // allow grouping multiple requests. 47fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private static final long UPDATE_SOURCES_DELAY_MILLIS = 200; 48fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 49fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private final DataSetObservable mDataSetObservable = new DataSetObservable(); 50fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 51fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private final Context mContext; 52fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private final SearchManager mSearchManager; 53fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private boolean mLoaded; 54fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 55fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // All suggestion sources. 56fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private HashMap<ComponentName, Source> mSources; 57fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 58fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // The web search source to use. 59fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private Source mWebSearchSource; 60fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 61fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private final Handler mUiThread; 62fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 63fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private Runnable mUpdateSources = new Runnable() { 64fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public void run() { 65fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mUiThread.removeCallbacks(this); 66fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert updateSources(); 67fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert notifyDataSetChanged(); 68fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 69fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert }; 70fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 71fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert /** 72fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * 73fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * @param context Used for looking up source information etc. 74fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert */ 75fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public SearchableSources(Context context, Handler uiThread) { 76fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mContext = context; 77fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mUiThread = uiThread; 78fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mSearchManager = (SearchManager) context.getSystemService(Context.SEARCH_SERVICE); 79fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mLoaded = false; 80fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 81fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 82fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public Collection<Source> getSources() { 83fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (!mLoaded) { 84fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert throw new IllegalStateException("getSources(): sources not loaded."); 85fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 86fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return mSources.values(); 87fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 88fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 89fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public Source getSource(ComponentName name) { 90fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return mSources.get(name); 91fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 92fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 93fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public Source getWebSearchSource() { 94fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (!mLoaded) { 95fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert throw new IllegalStateException("getWebSearchSource(): sources not loaded."); 96fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 97fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return mWebSearchSource; 98fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 99fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 100fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // Broadcast receiver for package change notifications 101fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 102fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert @Override 103fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public void onReceive(Context context, Intent intent) { 104fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert String action = intent.getAction(); 105fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED.equals(action) 106fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert || SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED.equals(action)) { 107fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (DBG) Log.d(TAG, "onReceive(" + intent + ")"); 108fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // TODO: Instead of rebuilding the whole list on every change, 109fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // just add, remove or update the application that has changed. 110fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // Adding and updating seem tricky, since I can't see an easy way to list the 111fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // launchable activities in a given package. 112fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mUiThread.postDelayed(mUpdateSources, UPDATE_SOURCES_DELAY_MILLIS); 113fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 114fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 115fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert }; 116fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 117fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public void load() { 118fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (mLoaded) { 119fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert throw new IllegalStateException("load(): Already loaded."); 120fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 121fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 122fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // Listen for searchables changes. 123fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert IntentFilter intentFilter = new IntentFilter(); 124fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert intentFilter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED); 125fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert intentFilter.addAction(SearchManager.INTENT_ACTION_SEARCH_SETTINGS_CHANGED); 126fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mContext.registerReceiver(mBroadcastReceiver, intentFilter); 127fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 128fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // update list of sources 129fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert updateSources(); 130fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 131fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mLoaded = true; 132fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 133fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert notifyDataSetChanged(); 134fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 135fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 136fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public void close() { 137fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mContext.unregisterReceiver(mBroadcastReceiver); 138fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 139fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mDataSetObservable.unregisterAll(); 140fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 141fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mSources = null; 142fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mLoaded = false; 143fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 144fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 145fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert /** 146fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert * Loads the list of suggestion sources. 147fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert */ 148fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private void updateSources() { 149fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (DBG) Log.d(TAG, "updateSources()"); 150fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mSources = new HashMap<ComponentName,Source>(); 151fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert for (SearchableInfo searchable : mSearchManager.getSearchablesInGlobalSearch()) { 152fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert Source source = createSearchableSource(searchable); 153fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (source != null) { 154fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (DBG) Log.d(TAG, "Created source " + source); 155fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert addSource(source); 156fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 157fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 158fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 159fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mWebSearchSource = createWebSearchSource(); 160fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert addSource(mWebSearchSource); 161fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 162fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 163fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private void addSource(Source source) { 164fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mSources.put(source.getComponentName(), source); 165fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 166fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 167fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private Source createWebSearchSource() { 168fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert ComponentName name = getWebSearchComponent(); 169fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert SearchableInfo webSearchable = mSearchManager.getSearchableInfo(name); 170fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (webSearchable == null) { 171fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert Log.e(TAG, "Web search source " + name + " is not searchable."); 172fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return null; 173fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 174fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return createSearchableSource(webSearchable); 175fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 176fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 177fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private ComponentName getWebSearchComponent() { 178fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // Looks for an activity in the current package that handles ACTION_WEB_SEARCH. 179fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // This indirect method is used to allow easy replacement of the web 180fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert // search activity when extending this package. 181fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH); 182fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert webSearchIntent.setPackage(mContext.getPackageName()); 183fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert PackageManager pm = mContext.getPackageManager(); 184fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return webSearchIntent.resolveActivity(pm); 185fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 186fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 187fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert private Source createSearchableSource(SearchableInfo searchable) { 188fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert if (searchable == null) return null; 189fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert try { 190fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return new SearchableSource(mContext, searchable); 191fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } catch (NameNotFoundException ex) { 192fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert Log.e(TAG, "Source not found: " + ex); 193fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert return null; 194fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 195fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 196fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 197fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public void registerDataSetObserver(DataSetObserver observer) { 198fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mDataSetObservable.registerObserver(observer); 199fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 200fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 201fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert public void unregisterDataSetObserver(DataSetObserver observer) { 202fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mDataSetObservable.unregisterObserver(observer); 203fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 204fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert 205fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert protected void notifyDataSetChanged() { 206fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert mDataSetObservable.notifyChanged(); 207fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert } 208fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert} 209