QsbApplication.java revision d5cd9612d6937dfb174bfe6f4e486f283ef557e9
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.HashMap;
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 Sources mSources;
54    private Corpora mAllCorpora;
55    private Corpora mResultsCorpora;
56    private final HashMap<Corpora, CorpusRanker> mCorpusRankers;
57    private ShortcutRepository mShortcutRepository;
58    private ShortcutRefresher mShortcutRefresher;
59    private NamedTaskExecutor mSourceTaskExecutor;
60    private ThreadFactory mQueryThreadFactory;
61    private SuggestionsProvider mUnifiedProvider;
62    private SuggestionsProvider mWebSuggestionProvider;
63    private SuggestionsProvider mResultsProvider;
64    private SuggestionViewFactory mSuggestionViewFactory;
65    private CorpusViewFactory mCorpusViewFactory;
66    private GoogleSource mGoogleSource;
67    private VoiceSearch mVoiceSearch;
68    private Logger mLogger;
69    private SuggestionFormatter mSuggestionFormatter;
70    private TextAppearanceFactory mTextAppearanceFactory;
71
72    public QsbApplication(Context context) {
73        mContext = context;
74        mCorpusRankers = new HashMap<Corpora, CorpusRanker>();
75    }
76
77    public static boolean isFroyoOrLater() {
78        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;
79    }
80
81    public static QsbApplication get(Context context) {
82        return ((QsbApplicationWrapper) context.getApplicationContext()).getApp();
83    }
84
85    protected Context getContext() {
86        return mContext;
87    }
88
89    public int getVersionCode() {
90        if (mVersionCode == 0) {
91            try {
92                PackageManager pm = getContext().getPackageManager();
93                PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0);
94                mVersionCode = pkgInfo.versionCode;
95            } catch (PackageManager.NameNotFoundException ex) {
96                // The current package should always exist, how else could we
97                // run code from it?
98                throw new RuntimeException(ex);
99            }
100        }
101        return mVersionCode;
102    }
103
104    protected void checkThread() {
105        if (Looper.myLooper() != Looper.getMainLooper()) {
106            throw new IllegalStateException("Accessed Application object from thread "
107                    + Thread.currentThread().getName());
108        }
109    }
110
111    protected void close() {
112        checkThread();
113        if (mConfig != null) {
114            mConfig.close();
115            mConfig = null;
116        }
117        if (mShortcutRepository != null) {
118            mShortcutRepository.close();
119            mShortcutRepository = null;
120        }
121        if (mSourceTaskExecutor != null) {
122            mSourceTaskExecutor.close();
123            mSourceTaskExecutor = null;
124        }
125        if (mUnifiedProvider != null) {
126            mUnifiedProvider.close();
127            mUnifiedProvider = null;
128        }
129        if (mWebSuggestionProvider != null) {
130            mWebSuggestionProvider.close();
131            mWebSuggestionProvider = null;
132        }
133        if (mResultsProvider != null) {
134            mResultsProvider.close();
135            mResultsProvider = 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    /**
151     * Indicates that construction of the QSB UI is now complete.
152     */
153    public void onStartupComplete() {
154    }
155
156    /**
157     * Gets the QSB configuration object.
158     * May be called from any thread.
159     */
160    public synchronized Config getConfig() {
161        if (mConfig == null) {
162            mConfig = createConfig();
163        }
164        return mConfig;
165    }
166
167    protected Config createConfig() {
168        return new Config(getContext());
169    }
170
171    /**
172     * Gets the 'all' corpora providing results and web suggestions.
173     * May only be called from the main thread.
174     */
175    public Corpora getAllCorpora() {
176        checkThread();
177        if (mAllCorpora == null) {
178            mAllCorpora = createAllCorpora(getSources());
179        }
180        return mAllCorpora;
181    }
182
183    protected Corpora createAllCorpora(Sources sources) {
184        SearchableCorpora corpora = new SearchableCorpora(getContext(), sources,
185                createAllCorpusFactory());
186        corpora.update();
187        return corpora;
188    }
189
190    /**
191     * Gets the corpora providing results only.
192     * May only be called from the main thread.
193     */
194    public Corpora getResultsCorpora() {
195        checkThread();
196        if (mResultsCorpora == null) {
197            mResultsCorpora = createResultsCorpora(getSources());
198        }
199        return mResultsCorpora;
200    }
201
202    protected Corpora createResultsCorpora(Sources sources) {
203        SearchableCorpora corpora = new SearchableCorpora(getContext(), sources,
204                createResultsCorpusFactory());
205        corpora.update();
206        return corpora;
207    }
208
209    /**
210     * Updates the corpora, if they are loaded.
211     * May only be called from the main thread.
212     */
213    public void updateCorpora() {
214        checkThread();
215        if (mAllCorpora != null) {
216            mAllCorpora.update();
217        }
218        if (mResultsCorpora != null) {
219            mResultsCorpora.update();
220        }
221    }
222
223    protected Sources getSources() {
224        checkThread();
225        if (mSources == null) {
226            mSources = createSources();
227        }
228        return mSources;
229    }
230
231    protected Sources createSources() {
232        return new SearchableSources(getContext());
233    }
234
235    protected CorpusFactory createAllCorpusFactory() {
236        int numWebCorpusThreads = getConfig().getNumWebCorpusThreads();
237        return new SearchableCorpusFactory(getContext(), getConfig(),
238                createExecutorFactory(numWebCorpusThreads));
239    }
240
241    protected CorpusFactory createResultsCorpusFactory() {
242        int numWebCorpusThreads = getConfig().getNumWebCorpusThreads();
243        return new ResultsCorpusFactory(getContext(), getConfig(),
244                createExecutorFactory(numWebCorpusThreads));
245    }
246
247    protected Factory<Executor> createExecutorFactory(final int numThreads) {
248        final ThreadFactory threadFactory = getQueryThreadFactory();
249        return new Factory<Executor>() {
250            public Executor create() {
251                return Executors.newFixedThreadPool(numThreads, threadFactory);
252            }
253        };
254    }
255
256    /**
257     * Gets the corpus ranker.
258     * May only be called from the main thread.
259     */
260    public CorpusRanker getCorpusRanker(Corpora corpora) {
261        checkThread();
262        if (mCorpusRankers.get(corpora) == null) {
263            mCorpusRankers.put(corpora, createCorpusRanker(corpora));
264        }
265        return mCorpusRankers.get(corpora);
266    }
267
268    protected CorpusRanker createCorpusRanker(Corpora corpora) {
269        return new DefaultCorpusRanker(corpora, getShortcutRepository(corpora));
270    }
271
272    /**
273     * Gets the shortcut repository.
274     * May only be called from the main thread.
275     */
276    public ShortcutRepository getShortcutRepository(Corpora corpora) {
277        checkThread();
278        if (mShortcutRepository == null) {
279            mShortcutRepository = createShortcutRepository(corpora);
280        }
281        return mShortcutRepository;
282    }
283
284    @Deprecated
285    public ShortcutRepository getShortcutRepository() {
286        return getShortcutRepository(getAllCorpora());
287    }
288
289    protected ShortcutRepository createShortcutRepository(Corpora corpora) {
290        ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d",
291                new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND));
292        Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory);
293        return ShortcutRepositoryImplLog.create(getContext(), getConfig(), corpora,
294            getShortcutRefresher(), getMainThreadHandler(), logExecutor);
295    }
296
297    /**
298     * Gets the shortcut refresher.
299     * May only be called from the main thread.
300     */
301    public ShortcutRefresher getShortcutRefresher() {
302        checkThread();
303        if (mShortcutRefresher == null) {
304            mShortcutRefresher = createShortcutRefresher();
305        }
306        return mShortcutRefresher;
307    }
308
309    protected ShortcutRefresher createShortcutRefresher() {
310        // For now, ShortcutRefresher gets its own SourceTaskExecutor
311        return new SourceShortcutRefresher(createSourceTaskExecutor());
312    }
313
314    /**
315     * Gets the source task executor.
316     * May only be called from the main thread.
317     */
318    public NamedTaskExecutor getSourceTaskExecutor() {
319        checkThread();
320        if (mSourceTaskExecutor == null) {
321            mSourceTaskExecutor = createSourceTaskExecutor();
322        }
323        return mSourceTaskExecutor;
324    }
325
326    protected NamedTaskExecutor createSourceTaskExecutor() {
327        ThreadFactory queryThreadFactory = getQueryThreadFactory();
328        return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory));
329    }
330
331    /**
332     * Gets the query thread factory.
333     * May only be called from the main thread.
334     */
335    protected ThreadFactory getQueryThreadFactory() {
336        checkThread();
337        if (mQueryThreadFactory == null) {
338            mQueryThreadFactory = createQueryThreadFactory();
339        }
340        return mQueryThreadFactory;
341    }
342
343    protected ThreadFactory createQueryThreadFactory() {
344        String nameFormat = "QSB #%d";
345        int priority = getConfig().getQueryThreadPriority();
346        return new NamingThreadFactory(nameFormat,
347                new PriorityThreadFactory(priority));
348    }
349
350    /**
351     * Gets the suggestion provider which provides suggestions from all sources blended together.
352     * Used when all suggestions are presented in a single list.
353     *
354     * May only be called from the main thread.
355     */
356    protected SuggestionsProvider getUnifiedProvider() {
357        checkThread();
358        if (mUnifiedProvider == null) {
359            mUnifiedProvider = createUnifiedProvider();
360        }
361        return mUnifiedProvider;
362    }
363
364    /**
365     * Gets the suggestion provider which provides web query suggestions only.
366     *
367     * May only be called from the main thread.
368     */
369    protected SuggestionsProvider getWebSuggestionsProvider() {
370        checkThread();
371        if (mWebSuggestionProvider == null) {
372            mWebSuggestionProvider = createWebSuggestionsProvider();
373        }
374        return mWebSuggestionProvider;
375    }
376
377    /**
378     * Gets the suggestion provider which provides all results for a query except web query
379     * suggestions.
380     *
381     * May only be called from the main thread.
382     */
383    protected SuggestionsProvider getResultsProvider() {
384        checkThread();
385        if (mResultsProvider == null) {
386            mResultsProvider = createResultsProvider();
387        }
388        return mResultsProvider;
389    }
390
391    protected SuggestionsProvider createBlendingProvider(Corpora corpora) {
392        int maxShortcutsPerWebSource = getConfig().getMaxShortcutsPerWebSource();
393        int maxShortcutsPerNonWebSource = getConfig().getMaxShortcutsPerNonWebSource();
394        Promoter allPromoter = new ShortcutLimitingPromoter(
395                maxShortcutsPerWebSource,
396                maxShortcutsPerNonWebSource,
397                new ShortcutPromoter(
398                        new RankAwarePromoter(getConfig(), corpora)));
399        Promoter singleCorpusPromoter = new ShortcutPromoter(new ConcatPromoter());
400        BlendingSuggestionsProvider provider = new BlendingSuggestionsProvider(getConfig(),
401                getSourceTaskExecutor(),
402                getMainThreadHandler(),
403                getShortcutRepository(corpora),
404                corpora,
405                getCorpusRanker(corpora),
406                getLogger());
407        provider.setAllPromoter(allPromoter);
408        provider.setSingleCorpusPromoter(singleCorpusPromoter);
409        return provider;
410    }
411
412    protected SuggestionsProvider createUnifiedProvider() {
413        return createBlendingProvider(getAllCorpora());
414    }
415
416    protected SuggestionsProvider createWebSuggestionsProvider() {
417        return new WebSuggestionsProvider(getGoogleSource(), getMainThreadHandler());
418    }
419
420    protected SuggestionsProvider createResultsProvider() {
421        return createBlendingProvider(getResultsCorpora());
422    }
423
424    /**
425     * Gets the suggestion view factory.
426     * May only be called from the main thread.
427     */
428    public SuggestionViewFactory getSuggestionViewFactory() {
429        checkThread();
430        if (mSuggestionViewFactory == null) {
431            mSuggestionViewFactory = createSuggestionViewFactory();
432        }
433        return mSuggestionViewFactory;
434    }
435
436    protected SuggestionViewFactory createSuggestionViewFactory() {
437        return new SuggestionViewInflater(getContext());
438    }
439
440    /**
441     * Gets the corpus view factory.
442     * May only be called from the main thread.
443     */
444    public CorpusViewFactory getCorpusViewFactory() {
445        checkThread();
446        if (mCorpusViewFactory == null) {
447            mCorpusViewFactory = createCorpusViewFactory();
448        }
449        return mCorpusViewFactory;
450    }
451
452    protected CorpusViewFactory createCorpusViewFactory() {
453        return new CorpusViewInflater(getContext());
454    }
455
456    /**
457     * Creates a suggestions adapter.
458     * May only be called from the main thread.
459     */
460    public SuggestionsAdapter createSuggestionsAdapter() {
461        SuggestionViewFactory viewFactory = getSuggestionViewFactory();
462        DelayingSuggestionsAdapter adapter = new DelayingSuggestionsAdapter(viewFactory);
463        return adapter;
464    }
465
466    /**
467     * Gets the Google source.
468     * May only be called from the main thread.
469     */
470    public GoogleSource getGoogleSource() {
471        checkThread();
472        if (mGoogleSource == null) {
473            mGoogleSource = createGoogleSource();
474        }
475        return mGoogleSource;
476    }
477
478    protected GoogleSource createGoogleSource() {
479        return new GoogleSuggestClient(getContext());
480    }
481
482    /**
483     * Gets Voice Search utilities.
484     */
485    public VoiceSearch getVoiceSearch() {
486        checkThread();
487        if (mVoiceSearch == null) {
488            mVoiceSearch = createVoiceSearch();
489        }
490        return mVoiceSearch;
491    }
492
493    protected VoiceSearch createVoiceSearch() {
494        return new VoiceSearch(getContext());
495    }
496
497    /**
498     * Gets the event logger.
499     * May only be called from the main thread.
500     */
501    public Logger getLogger() {
502        checkThread();
503        if (mLogger == null) {
504            mLogger = createLogger();
505        }
506        return mLogger;
507    }
508
509    protected Logger createLogger() {
510        return new EventLogLogger(getContext(), getConfig());
511    }
512
513    public SuggestionFormatter getSuggestionFormatter() {
514        if (mSuggestionFormatter == null) {
515            mSuggestionFormatter = createSuggestionFormatter();
516        }
517        return mSuggestionFormatter;
518    }
519
520    protected SuggestionFormatter createSuggestionFormatter() {
521        return new LevenshteinSuggestionFormatter(getTextAppearanceFactory());
522    }
523
524    public TextAppearanceFactory getTextAppearanceFactory() {
525        if (mTextAppearanceFactory == null) {
526            mTextAppearanceFactory = createTextAppearanceFactory();
527        }
528        return mTextAppearanceFactory;
529    }
530
531    protected TextAppearanceFactory createTextAppearanceFactory() {
532        return new TextAppearanceFactory(getContext());
533    }
534
535}
536