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