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