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