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