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