QsbApplication.java revision 22da5e41bec9a5d36388c1e6f99bca392c3ac63e
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.quicksearchbox;
18
19import com.android.quicksearchbox.google.GoogleSource;
20import com.android.quicksearchbox.google.GoogleSuggestClient;
21import com.android.quicksearchbox.ui.CorporaAdapter;
22import com.android.quicksearchbox.ui.CorpusViewFactory;
23import com.android.quicksearchbox.ui.CorpusViewInflater;
24import com.android.quicksearchbox.ui.SuggestionViewFactory;
25import com.android.quicksearchbox.ui.SuggestionViewInflater;
26import com.android.quicksearchbox.util.Factory;
27import com.android.quicksearchbox.util.NamedTaskExecutor;
28import com.android.quicksearchbox.util.PerNameExecutor;
29import com.android.quicksearchbox.util.PriorityThreadFactory;
30import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor;
31import com.google.common.util.concurrent.NamingThreadFactory;
32
33import android.content.Context;
34import android.content.pm.PackageInfo;
35import android.content.pm.PackageManager;
36import android.os.Build;
37import android.os.Handler;
38import android.os.Looper;
39import android.os.Process;
40import android.view.ContextThemeWrapper;
41
42import java.util.concurrent.Executor;
43import java.util.concurrent.Executors;
44import java.util.concurrent.ThreadFactory;
45
46public class QsbApplication {
47    private final Context mContext;
48
49    private int mVersionCode;
50    private Handler mUiThreadHandler;
51    private Config mConfig;
52    private SearchSettings mSettings;
53    private Sources mSources;
54    private Corpora mCorpora;
55    private CorpusRanker mCorpusRanker;
56    private ShortcutRepository mShortcutRepository;
57    private ShortcutRefresher mShortcutRefresher;
58    private NamedTaskExecutor mSourceTaskExecutor;
59    private ThreadFactory mQueryThreadFactory;
60    private SuggestionsProvider mSuggestionsProvider;
61    private SuggestionViewFactory mSuggestionViewFactory;
62    private CorpusViewFactory mCorpusViewFactory;
63    private GoogleSource mGoogleSource;
64    private VoiceSearch mVoiceSearch;
65    private Logger mLogger;
66    private SuggestionFormatter mSuggestionFormatter;
67    private TextAppearanceFactory mTextAppearanceFactory;
68
69    public QsbApplication(Context context) {
70        // the application context does not use the theme from the <application> tag
71        mContext = new ContextThemeWrapper(context, R.style.Theme_QuickSearchBox);
72    }
73
74    public static boolean isFroyoOrLater() {
75        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
76    }
77
78    public static QsbApplication get(Context context) {
79        return ((QsbApplicationWrapper) context.getApplicationContext()).getApp();
80    }
81
82    protected Context getContext() {
83        return mContext;
84    }
85
86    public int getVersionCode() {
87        if (mVersionCode == 0) {
88            try {
89                PackageManager pm = getContext().getPackageManager();
90                PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0);
91                mVersionCode = pkgInfo.versionCode;
92            } catch (PackageManager.NameNotFoundException ex) {
93                // The current package should always exist, how else could we
94                // run code from it?
95                throw new RuntimeException(ex);
96            }
97        }
98        return mVersionCode;
99    }
100
101    protected void checkThread() {
102        if (Looper.myLooper() != Looper.getMainLooper()) {
103            throw new IllegalStateException("Accessed Application object from thread "
104                    + Thread.currentThread().getName());
105        }
106    }
107
108    protected void close() {
109        checkThread();
110        if (mConfig != null) {
111            mConfig.close();
112            mConfig = null;
113        }
114        if (mShortcutRepository != null) {
115            mShortcutRepository.close();
116            mShortcutRepository = null;
117        }
118        if (mSourceTaskExecutor != null) {
119            mSourceTaskExecutor.close();
120            mSourceTaskExecutor = null;
121        }
122        if (mSuggestionsProvider != null) {
123            mSuggestionsProvider.close();
124            mSuggestionsProvider = null;
125        }
126    }
127
128    public synchronized Handler getMainThreadHandler() {
129        if (mUiThreadHandler == null) {
130            mUiThreadHandler = new Handler(Looper.getMainLooper());
131        }
132        return mUiThreadHandler;
133    }
134
135    public void runOnUiThread(Runnable action) {
136        getMainThreadHandler().post(action);
137    }
138
139    /**
140     * Indicates that construction of the QSB UI is now complete.
141     */
142    public void onStartupComplete() {
143    }
144
145    /**
146     * Gets the QSB configuration object.
147     * May be called from any thread.
148     */
149    public synchronized Config getConfig() {
150        if (mConfig == null) {
151            mConfig = createConfig();
152        }
153        return mConfig;
154    }
155
156    protected Config createConfig() {
157        return new Config(getContext());
158    }
159
160    public synchronized SearchSettings getSettings() {
161        if (mSettings == null) {
162            mSettings = createSettings();
163            mSettings.upgradeSettingsIfNeeded();
164        }
165        return mSettings;
166    }
167
168    protected SearchSettings createSettings() {
169        return new SearchSettingsImpl(getContext(), getConfig());
170    }
171
172    /**
173     * Gets all corpora.
174     *
175     * May only be called from the main thread.
176     */
177    public Corpora getCorpora() {
178        checkThread();
179        if (mCorpora == null) {
180            mCorpora = createCorpora(getSources());
181        }
182        return mCorpora;
183    }
184
185    protected Corpora createCorpora(Sources sources) {
186        SearchableCorpora corpora = new SearchableCorpora(getContext(), getSettings(), sources,
187                createCorpusFactory());
188        corpora.update();
189        return corpora;
190    }
191
192    /**
193     * Updates the corpora, if they are loaded.
194     * May only be called from the main thread.
195     */
196    public void updateCorpora() {
197        checkThread();
198        if (mCorpora != null) {
199            mCorpora.update();
200        }
201    }
202
203    protected Sources getSources() {
204        checkThread();
205        if (mSources == null) {
206            mSources = createSources();
207        }
208        return mSources;
209    }
210
211    protected Sources createSources() {
212        return new SearchableSources(getContext());
213    }
214
215    protected CorpusFactory createCorpusFactory() {
216        int numWebCorpusThreads = getConfig().getNumWebCorpusThreads();
217        return new SearchableCorpusFactory(getContext(), getConfig(), getSettings(),
218                createExecutorFactory(numWebCorpusThreads));
219    }
220
221    protected Factory<Executor> createExecutorFactory(final int numThreads) {
222        final ThreadFactory threadFactory = getQueryThreadFactory();
223        return new Factory<Executor>() {
224            public Executor create() {
225                return Executors.newFixedThreadPool(numThreads, threadFactory);
226            }
227        };
228    }
229
230    /**
231     * Gets the corpus ranker.
232     * May only be called from the main thread.
233     */
234    public CorpusRanker getCorpusRanker() {
235        checkThread();
236        if (mCorpusRanker == null) {
237            mCorpusRanker = createCorpusRanker();
238        }
239        return mCorpusRanker;
240    }
241
242    protected CorpusRanker createCorpusRanker() {
243        return new DefaultCorpusRanker(getCorpora(), getShortcutRepository());
244    }
245
246    /**
247     * Gets the shortcut repository.
248     * May only be called from the main thread.
249     */
250    public ShortcutRepository getShortcutRepository() {
251        checkThread();
252        if (mShortcutRepository == null) {
253            mShortcutRepository = createShortcutRepository();
254        }
255        return mShortcutRepository;
256    }
257
258    protected ShortcutRepository createShortcutRepository() {
259        ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d",
260                new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND));
261        Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory);
262        return ShortcutRepositoryImplLog.create(getContext(), getConfig(), getCorpora(),
263            getShortcutRefresher(), getMainThreadHandler(), logExecutor);
264    }
265
266    /**
267     * Gets the shortcut refresher.
268     * May only be called from the main thread.
269     */
270    public ShortcutRefresher getShortcutRefresher() {
271        checkThread();
272        if (mShortcutRefresher == null) {
273            mShortcutRefresher = createShortcutRefresher();
274        }
275        return mShortcutRefresher;
276    }
277
278    protected ShortcutRefresher createShortcutRefresher() {
279        // For now, ShortcutRefresher gets its own SourceTaskExecutor
280        return new SourceShortcutRefresher(createSourceTaskExecutor());
281    }
282
283    /**
284     * Gets the source task executor.
285     * May only be called from the main thread.
286     */
287    public NamedTaskExecutor getSourceTaskExecutor() {
288        checkThread();
289        if (mSourceTaskExecutor == null) {
290            mSourceTaskExecutor = createSourceTaskExecutor();
291        }
292        return mSourceTaskExecutor;
293    }
294
295    protected NamedTaskExecutor createSourceTaskExecutor() {
296        ThreadFactory queryThreadFactory = getQueryThreadFactory();
297        return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory));
298    }
299
300    /**
301     * Gets the query thread factory.
302     * May only be called from the main thread.
303     */
304    protected ThreadFactory getQueryThreadFactory() {
305        checkThread();
306        if (mQueryThreadFactory == null) {
307            mQueryThreadFactory = createQueryThreadFactory();
308        }
309        return mQueryThreadFactory;
310    }
311
312    protected ThreadFactory createQueryThreadFactory() {
313        String nameFormat = "QSB #%d";
314        int priority = getConfig().getQueryThreadPriority();
315        return new NamingThreadFactory(nameFormat,
316                new PriorityThreadFactory(priority));
317    }
318
319    /**
320     * Gets the suggestion provider.
321     *
322     * May only be called from the main thread.
323     */
324    protected SuggestionsProvider getSuggestionsProvider() {
325        checkThread();
326        if (mSuggestionsProvider == null) {
327            mSuggestionsProvider = createSuggestionsProvider();
328        }
329        return mSuggestionsProvider;
330    }
331
332    protected SuggestionsProvider createSuggestionsProvider() {
333        return new SuggestionsProviderImpl(getConfig(),
334              getSourceTaskExecutor(),
335              getMainThreadHandler(),
336              getLogger());
337    }
338
339    /**
340     * Gets the suggestion view factory.
341     * May only be called from the main thread.
342     */
343    public SuggestionViewFactory getSuggestionViewFactory() {
344        checkThread();
345        if (mSuggestionViewFactory == null) {
346            mSuggestionViewFactory = createSuggestionViewFactory();
347        }
348        return mSuggestionViewFactory;
349    }
350
351    protected SuggestionViewFactory createSuggestionViewFactory() {
352        return new SuggestionViewInflater(getContext());
353    }
354
355    /**
356     * Gets the corpus view factory.
357     * May only be called from the main thread.
358     */
359    public CorpusViewFactory getCorpusViewFactory() {
360        checkThread();
361        if (mCorpusViewFactory == null) {
362            mCorpusViewFactory = createCorpusViewFactory();
363        }
364        return mCorpusViewFactory;
365    }
366
367    protected CorpusViewFactory createCorpusViewFactory() {
368        return new CorpusViewInflater(getContext());
369    }
370
371    public Promoter createBlendingPromoter() {
372        return new BlendingPromoter(getConfig());
373    }
374
375    public Promoter createSingleCorpusPromoter() {
376        return new ConcatPromoter(Integer.MAX_VALUE);
377    }
378
379    public Promoter createWebPromoter() {
380        return new WebPromoter(getConfig().getMaxShortcutsPerWebSource());
381    }
382
383    public Promoter createResultsPromoter() {
384        return new ResultPromoter(getConfig());
385    }
386
387    /**
388     * Gets the Google source.
389     * May only be called from the main thread.
390     */
391    public GoogleSource getGoogleSource() {
392        checkThread();
393        if (mGoogleSource == null) {
394            mGoogleSource = createGoogleSource();
395        }
396        return mGoogleSource;
397    }
398
399    protected GoogleSource createGoogleSource() {
400        return new GoogleSuggestClient(getContext());
401    }
402
403    public CorporaAdapter createCorporaListAdapter() {
404        return new CorporaAdapter(getCorpusViewFactory(), getCorpora(), false);
405    }
406
407    public CorporaAdapter createCorporaGridAdapter() {
408        return new CorporaAdapter(getCorpusViewFactory(), getCorpora(), true);
409    }
410
411    /**
412     * Gets Voice Search utilities.
413     */
414    public VoiceSearch getVoiceSearch() {
415        checkThread();
416        if (mVoiceSearch == null) {
417            mVoiceSearch = createVoiceSearch();
418        }
419        return mVoiceSearch;
420    }
421
422    protected VoiceSearch createVoiceSearch() {
423        return new VoiceSearch(getContext());
424    }
425
426    /**
427     * Gets the event logger.
428     * May only be called from the main thread.
429     */
430    public Logger getLogger() {
431        checkThread();
432        if (mLogger == null) {
433            mLogger = createLogger();
434        }
435        return mLogger;
436    }
437
438    protected Logger createLogger() {
439        return new EventLogLogger(getContext(), getConfig());
440    }
441
442    public SuggestionFormatter getSuggestionFormatter() {
443        if (mSuggestionFormatter == null) {
444            mSuggestionFormatter = createSuggestionFormatter();
445        }
446        return mSuggestionFormatter;
447    }
448
449    protected SuggestionFormatter createSuggestionFormatter() {
450        return new LevenshteinSuggestionFormatter(getTextAppearanceFactory());
451    }
452
453    public TextAppearanceFactory getTextAppearanceFactory() {
454        if (mTextAppearanceFactory == null) {
455            mTextAppearanceFactory = createTextAppearanceFactory();
456        }
457        return mTextAppearanceFactory;
458    }
459
460    protected TextAppearanceFactory createTextAppearanceFactory() {
461        return new TextAppearanceFactory(getContext());
462    }
463
464}
465