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