QsbApplication.java revision cd4accc7899fa7b756e1c430d6b196525abd5c3c
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(),
232                getIconLoaderExecutor(), getConfig());
233    }
234
235    protected CorpusFactory createCorpusFactory() {
236        int numWebCorpusThreads = getConfig().getNumWebCorpusThreads();
237        return new SearchableCorpusFactory(getContext(), getConfig(), getSettings(),
238                createExecutorFactory(numWebCorpusThreads));
239    }
240
241    protected Factory<Executor> createExecutorFactory(final int numThreads) {
242        final ThreadFactory threadFactory = getQueryThreadFactory();
243        return new Factory<Executor>() {
244            public Executor create() {
245                return Executors.newFixedThreadPool(numThreads, threadFactory);
246            }
247        };
248    }
249
250    /**
251     * Gets the corpus ranker.
252     * May only be called from the main thread.
253     */
254    public CorpusRanker getCorpusRanker() {
255        checkThread();
256        if (mCorpusRanker == null) {
257            mCorpusRanker = createCorpusRanker();
258        }
259        return mCorpusRanker;
260    }
261
262    protected CorpusRanker createCorpusRanker() {
263        return new DefaultCorpusRanker(getCorpora(), getShortcutRepository());
264    }
265
266    /**
267     * Gets the shortcut repository.
268     * May only be called from the main thread.
269     */
270    public ShortcutRepository getShortcutRepository() {
271        checkThread();
272        if (mShortcutRepository == null) {
273            mShortcutRepository = createShortcutRepository();
274        }
275        return mShortcutRepository;
276    }
277
278    protected ShortcutRepository createShortcutRepository() {
279        ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d",
280                new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND));
281        Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory);
282        return ShortcutRepositoryImplLog.create(getContext(), getConfig(), getCorpora(),
283            getShortcutRefresher(), getMainThreadHandler(), logExecutor);
284    }
285
286    /**
287     * Gets the shortcut refresher.
288     * May only be called from the main thread.
289     */
290    public ShortcutRefresher getShortcutRefresher() {
291        checkThread();
292        if (mShortcutRefresher == null) {
293            mShortcutRefresher = createShortcutRefresher();
294        }
295        return mShortcutRefresher;
296    }
297
298    protected ShortcutRefresher createShortcutRefresher() {
299        // For now, ShortcutRefresher gets its own SourceTaskExecutor
300        return new SourceShortcutRefresher(createSourceTaskExecutor());
301    }
302
303    /**
304     * Gets the source task executor.
305     * May only be called from the main thread.
306     */
307    public NamedTaskExecutor getSourceTaskExecutor() {
308        checkThread();
309        if (mSourceTaskExecutor == null) {
310            mSourceTaskExecutor = createSourceTaskExecutor();
311        }
312        return mSourceTaskExecutor;
313    }
314
315    protected NamedTaskExecutor createSourceTaskExecutor() {
316        ThreadFactory queryThreadFactory = getQueryThreadFactory();
317        return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory));
318    }
319
320    /**
321     * Gets the query thread factory.
322     * May only be called from the main thread.
323     */
324    protected ThreadFactory getQueryThreadFactory() {
325        checkThread();
326        if (mQueryThreadFactory == null) {
327            mQueryThreadFactory = createQueryThreadFactory();
328        }
329        return mQueryThreadFactory;
330    }
331
332    protected ThreadFactory createQueryThreadFactory() {
333        String nameFormat = "QSB #%d";
334        int priority = getConfig().getQueryThreadPriority();
335        return new NamingThreadFactory(nameFormat,
336                new PriorityThreadFactory(priority));
337    }
338
339    /**
340     * Gets the suggestion provider.
341     *
342     * May only be called from the main thread.
343     */
344    protected SuggestionsProvider getSuggestionsProvider() {
345        checkThread();
346        if (mSuggestionsProvider == null) {
347            mSuggestionsProvider = createSuggestionsProvider();
348        }
349        return mSuggestionsProvider;
350    }
351
352    protected SuggestionsProvider createSuggestionsProvider() {
353        return new SuggestionsProviderImpl(getConfig(),
354              getSourceTaskExecutor(),
355              getMainThreadHandler(),
356              getLogger());
357    }
358
359    /**
360     * Gets the default suggestion view factory.
361     * May only be called from the main thread.
362     */
363    public SuggestionViewFactory getSuggestionViewFactory() {
364        checkThread();
365        if (mSuggestionViewFactory == null) {
366            mSuggestionViewFactory = createSuggestionViewFactory();
367        }
368        return mSuggestionViewFactory;
369    }
370
371    protected SuggestionViewFactory createSuggestionViewFactory() {
372        return new DefaultSuggestionViewFactory(getContext());
373    }
374
375    public Promoter createBlendingPromoter() {
376        return new ShortcutPromoter(getConfig(),
377                new RankAwarePromoter(getConfig(), null, null), null);
378    }
379
380    public Promoter createSingleCorpusPromoter(Corpus corpus) {
381        return new SingleCorpusPromoter(corpus, Integer.MAX_VALUE);
382    }
383
384    public Promoter createSingleCorpusResultsPromoter(Corpus corpus) {
385        return new SingleCorpusResultsPromoter(corpus, Integer.MAX_VALUE);
386    }
387
388    public Promoter createWebPromoter() {
389        return new WebPromoter(getConfig().getMaxShortcutsPerWebSource());
390    }
391
392    public Promoter createResultsPromoter() {
393        SuggestionFilter resultFilter = new ResultFilter();
394        return new ShortcutPromoter(getConfig(), null, resultFilter);
395    }
396
397    /**
398     * Gets the Google source.
399     * May only be called from the main thread.
400     */
401    public GoogleSource getGoogleSource() {
402        checkThread();
403        if (mGoogleSource == null) {
404            mGoogleSource = createGoogleSource();
405        }
406        return mGoogleSource;
407    }
408
409    protected GoogleSource createGoogleSource() {
410        return new GoogleSuggestClient(getContext(), getMainThreadHandler(),
411                getIconLoaderExecutor(), getConfig());
412    }
413
414    /**
415     * Gets Voice Search utilities.
416     */
417    public VoiceSearch getVoiceSearch() {
418        checkThread();
419        if (mVoiceSearch == null) {
420            mVoiceSearch = createVoiceSearch();
421        }
422        return mVoiceSearch;
423    }
424
425    protected VoiceSearch createVoiceSearch() {
426        return new VoiceSearch(getContext());
427    }
428
429    /**
430     * Gets the event logger.
431     * May only be called from the main thread.
432     */
433    public Logger getLogger() {
434        checkThread();
435        if (mLogger == null) {
436            mLogger = createLogger();
437        }
438        return mLogger;
439    }
440
441    protected Logger createLogger() {
442        return new EventLogLogger(getContext(), getConfig());
443    }
444
445    public SuggestionFormatter getSuggestionFormatter() {
446        if (mSuggestionFormatter == null) {
447            mSuggestionFormatter = createSuggestionFormatter();
448        }
449        return mSuggestionFormatter;
450    }
451
452    protected SuggestionFormatter createSuggestionFormatter() {
453        return new LevenshteinSuggestionFormatter(getTextAppearanceFactory());
454    }
455
456    public TextAppearanceFactory getTextAppearanceFactory() {
457        if (mTextAppearanceFactory == null) {
458            mTextAppearanceFactory = createTextAppearanceFactory();
459        }
460        return mTextAppearanceFactory;
461    }
462
463    protected TextAppearanceFactory createTextAppearanceFactory() {
464        return new TextAppearanceFactory(getContext());
465    }
466
467    public PreferenceControllerFactory createPreferenceControllerFactory(Activity activity) {
468        return new PreferenceControllerFactory(getSettings(), activity);
469    }
470
471}
472