QsbApplication.java revision 96fec862c3d494aebcb4e1d93589a241385a2ba7
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.SuggestionViewFactory;
25import com.android.quicksearchbox.ui.SuggestionViewInflater;
26import com.android.quicksearchbox.util.Factory;
27import com.android.quicksearchbox.util.NamedTaskExecutor;
28import com.android.quicksearchbox.util.PerNameExecutor;
29import com.android.quicksearchbox.util.PriorityThreadFactory;
30import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor;
31import com.google.common.util.concurrent.NamingThreadFactory;
32
33import android.content.Context;
34import android.content.pm.PackageInfo;
35import android.content.pm.PackageManager;
36import android.os.Build;
37import android.os.Handler;
38import android.os.Looper;
39import android.os.Process;
40
41import java.util.concurrent.Executor;
42import java.util.concurrent.Executors;
43import java.util.concurrent.ThreadFactory;
44
45public class QsbApplication {
46    private final Context mContext;
47
48    private int mVersionCode;
49    private Handler mUiThreadHandler;
50    private Config mConfig;
51    private SearchSettings mSettings;
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    public synchronized SearchSettings getSettings() {
159        if (mSettings == null) {
160            mSettings = createSettings();
161            mSettings.upgradeSettingsIfNeeded();
162        }
163        return mSettings;
164    }
165
166    protected SearchSettings createSettings() {
167        return new SearchSettingsImpl(getContext(), getConfig());
168    }
169
170    /**
171     * Gets all corpora.
172     *
173     * May only be called from the main thread.
174     */
175    public Corpora getCorpora() {
176        checkThread();
177        if (mCorpora == null) {
178            mCorpora = createCorpora(getSources());
179        }
180        return mCorpora;
181    }
182
183    protected Corpora createCorpora(Sources sources) {
184        SearchableCorpora corpora = new SearchableCorpora(getContext(), getSettings(), sources,
185                createCorpusFactory());
186        corpora.update();
187        return corpora;
188    }
189
190    /**
191     * Updates the corpora, if they are loaded.
192     * May only be called from the main thread.
193     */
194    public void updateCorpora() {
195        checkThread();
196        if (mCorpora != null) {
197            mCorpora.update();
198        }
199    }
200
201    protected Sources getSources() {
202        checkThread();
203        if (mSources == null) {
204            mSources = createSources();
205        }
206        return mSources;
207    }
208
209    protected Sources createSources() {
210        return new SearchableSources(getContext());
211    }
212
213    protected CorpusFactory createCorpusFactory() {
214        int numWebCorpusThreads = getConfig().getNumWebCorpusThreads();
215        return new SearchableCorpusFactory(getContext(), getConfig(), getSettings(),
216                createExecutorFactory(numWebCorpusThreads));
217    }
218
219    protected Factory<Executor> createExecutorFactory(final int numThreads) {
220        final ThreadFactory threadFactory = getQueryThreadFactory();
221        return new Factory<Executor>() {
222            public Executor create() {
223                return Executors.newFixedThreadPool(numThreads, threadFactory);
224            }
225        };
226    }
227
228    /**
229     * Gets the corpus ranker.
230     * May only be called from the main thread.
231     */
232    public CorpusRanker getCorpusRanker() {
233        checkThread();
234        if (mCorpusRanker == null) {
235            mCorpusRanker = createCorpusRanker();
236        }
237        return mCorpusRanker;
238    }
239
240    protected CorpusRanker createCorpusRanker() {
241        return new DefaultCorpusRanker(getCorpora(), getShortcutRepository(),
242                getMainThreadHandler());
243    }
244
245    /**
246     * Gets the shortcut repository.
247     * May only be called from the main thread.
248     */
249    public ShortcutRepository getShortcutRepository() {
250        checkThread();
251        if (mShortcutRepository == null) {
252            mShortcutRepository = createShortcutRepository();
253        }
254        return mShortcutRepository;
255    }
256
257    protected ShortcutRepository createShortcutRepository() {
258        ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d",
259                new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND));
260        Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory);
261        return ShortcutRepositoryImplLog.create(getContext(), getConfig(), getCorpora(),
262            getShortcutRefresher(), getMainThreadHandler(), logExecutor);
263    }
264
265    /**
266     * Gets the shortcut refresher.
267     * May only be called from the main thread.
268     */
269    public ShortcutRefresher getShortcutRefresher() {
270        checkThread();
271        if (mShortcutRefresher == null) {
272            mShortcutRefresher = createShortcutRefresher();
273        }
274        return mShortcutRefresher;
275    }
276
277    protected ShortcutRefresher createShortcutRefresher() {
278        // For now, ShortcutRefresher gets its own SourceTaskExecutor
279        return new SourceShortcutRefresher(createSourceTaskExecutor());
280    }
281
282    /**
283     * Gets the source task executor.
284     * May only be called from the main thread.
285     */
286    public NamedTaskExecutor getSourceTaskExecutor() {
287        checkThread();
288        if (mSourceTaskExecutor == null) {
289            mSourceTaskExecutor = createSourceTaskExecutor();
290        }
291        return mSourceTaskExecutor;
292    }
293
294    protected NamedTaskExecutor createSourceTaskExecutor() {
295        ThreadFactory queryThreadFactory = getQueryThreadFactory();
296        return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory));
297    }
298
299    /**
300     * Gets the query thread factory.
301     * May only be called from the main thread.
302     */
303    protected ThreadFactory getQueryThreadFactory() {
304        checkThread();
305        if (mQueryThreadFactory == null) {
306            mQueryThreadFactory = createQueryThreadFactory();
307        }
308        return mQueryThreadFactory;
309    }
310
311    protected ThreadFactory createQueryThreadFactory() {
312        String nameFormat = "QSB #%d";
313        int priority = getConfig().getQueryThreadPriority();
314        return new NamingThreadFactory(nameFormat,
315                new PriorityThreadFactory(priority));
316    }
317
318    /**
319     * Gets the suggestion provider.
320     *
321     * May only be called from the main thread.
322     */
323    protected SuggestionsProvider getSuggestionsProvider() {
324        checkThread();
325        if (mSuggestionsProvider == null) {
326            mSuggestionsProvider = createSuggestionsProvider();
327        }
328        return mSuggestionsProvider;
329    }
330
331    protected SuggestionsProvider createSuggestionsProvider() {
332        return new SuggestionsProviderImpl(getConfig(),
333              getSourceTaskExecutor(),
334              getMainThreadHandler(),
335              getLogger());
336    }
337
338    /**
339     * Gets the suggestion view factory.
340     * May only be called from the main thread.
341     */
342    public SuggestionViewFactory getSuggestionViewFactory() {
343        checkThread();
344        if (mSuggestionViewFactory == null) {
345            mSuggestionViewFactory = createSuggestionViewFactory();
346        }
347        return mSuggestionViewFactory;
348    }
349
350    protected SuggestionViewFactory createSuggestionViewFactory() {
351        return new SuggestionViewInflater(getContext());
352    }
353
354    /**
355     * Gets the corpus view factory.
356     * May only be called from the main thread.
357     */
358    public CorpusViewFactory getCorpusViewFactory() {
359        checkThread();
360        if (mCorpusViewFactory == null) {
361            mCorpusViewFactory = createCorpusViewFactory();
362        }
363        return mCorpusViewFactory;
364    }
365
366    protected CorpusViewFactory createCorpusViewFactory() {
367        return new CorpusViewInflater(getContext());
368    }
369
370    public Promoter createBlendingPromoter() {
371        return new BlendingPromoter(getConfig());
372    }
373
374    public Promoter createSingleCorpusPromoter() {
375        return new ConcatPromoter(Integer.MAX_VALUE);
376    }
377
378    public Promoter createWebPromoter() {
379        return new WebPromoter(getConfig().getMaxShortcutsPerWebSource());
380    }
381
382    public Promoter createResultsPromoter() {
383        return new ResultPromoter(getConfig());
384    }
385
386    /**
387     * Gets the Google source.
388     * May only be called from the main thread.
389     */
390    public GoogleSource getGoogleSource() {
391        checkThread();
392        if (mGoogleSource == null) {
393            mGoogleSource = createGoogleSource();
394        }
395        return mGoogleSource;
396    }
397
398    protected GoogleSource createGoogleSource() {
399        return new GoogleSuggestClient(getContext());
400    }
401
402    public CorporaAdapter createCorporaListAdapter() {
403        return new CorporaAdapter(getCorpusViewFactory(), getCorpusRanker(),
404                getMainThreadHandler(), false);
405    }
406
407    public CorporaAdapter createCorporaGridAdapter() {
408        return new CorporaAdapter(getCorpusViewFactory(), getCorpusRanker(),
409                getMainThreadHandler(), true);
410    }
411
412    /**
413     * Gets Voice Search utilities.
414     */
415    public VoiceSearch getVoiceSearch() {
416        checkThread();
417        if (mVoiceSearch == null) {
418            mVoiceSearch = createVoiceSearch();
419        }
420        return mVoiceSearch;
421    }
422
423    protected VoiceSearch createVoiceSearch() {
424        return new VoiceSearch(getContext());
425    }
426
427    /**
428     * Gets the event logger.
429     * May only be called from the main thread.
430     */
431    public Logger getLogger() {
432        checkThread();
433        if (mLogger == null) {
434            mLogger = createLogger();
435        }
436        return mLogger;
437    }
438
439    protected Logger createLogger() {
440        return new EventLogLogger(getContext(), getConfig());
441    }
442
443    public SuggestionFormatter getSuggestionFormatter() {
444        if (mSuggestionFormatter == null) {
445            mSuggestionFormatter = createSuggestionFormatter();
446        }
447        return mSuggestionFormatter;
448    }
449
450    protected SuggestionFormatter createSuggestionFormatter() {
451        return new LevenshteinSuggestionFormatter(getTextAppearanceFactory());
452    }
453
454    public TextAppearanceFactory getTextAppearanceFactory() {
455        if (mTextAppearanceFactory == null) {
456            mTextAppearanceFactory = createTextAppearanceFactory();
457        }
458        return mTextAppearanceFactory;
459    }
460
461    protected TextAppearanceFactory createTextAppearanceFactory() {
462        return new TextAppearanceFactory(getContext());
463    }
464
465}
466