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