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