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