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