/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.quicksearchbox; import com.android.quicksearchbox.google.GoogleSource; import com.android.quicksearchbox.google.GoogleSuggestClient; import com.android.quicksearchbox.google.SearchBaseUrlHelper; import com.android.quicksearchbox.preferences.PreferenceControllerFactory; import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory; import com.android.quicksearchbox.ui.SuggestionViewFactory; import com.android.quicksearchbox.util.Factory; import com.android.quicksearchbox.util.HttpHelper; import com.android.quicksearchbox.util.JavaNetHttpHelper; import com.android.quicksearchbox.util.NamedTaskExecutor; import com.android.quicksearchbox.util.PerNameExecutor; import com.android.quicksearchbox.util.PriorityThreadFactory; import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor; import com.google.common.util.concurrent.NamingThreadFactory; import android.app.Activity; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Process; import android.view.ContextThemeWrapper; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; public class QsbApplication { private final Context mContext; private int mVersionCode; private Handler mUiThreadHandler; private Config mConfig; private SearchSettings mSettings; private Sources mSources; private Corpora mCorpora; private CorpusRanker mCorpusRanker; private ShortcutRepository mShortcutRepository; private ShortcutRefresher mShortcutRefresher; private NamedTaskExecutor mSourceTaskExecutor; private ThreadFactory mQueryThreadFactory; private SuggestionsProvider mSuggestionsProvider; private SuggestionViewFactory mSuggestionViewFactory; private GoogleSource mGoogleSource; private VoiceSearch mVoiceSearch; private Logger mLogger; private SuggestionFormatter mSuggestionFormatter; private TextAppearanceFactory mTextAppearanceFactory; private NamedTaskExecutor mIconLoaderExecutor; private HttpHelper mHttpHelper; private SearchBaseUrlHelper mSearchBaseUrlHelper; public QsbApplication(Context context) { // the application context does not use the theme from the <application> tag mContext = new ContextThemeWrapper(context, R.style.Theme_QuickSearchBox); } public static boolean isFroyoOrLater() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; } public static boolean isHoneycombOrLater() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; } public static QsbApplication get(Context context) { return ((QsbApplicationWrapper) context.getApplicationContext()).getApp(); } protected Context getContext() { return mContext; } public int getVersionCode() { if (mVersionCode == 0) { try { PackageManager pm = getContext().getPackageManager(); PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0); mVersionCode = pkgInfo.versionCode; } catch (PackageManager.NameNotFoundException ex) { // The current package should always exist, how else could we // run code from it? throw new RuntimeException(ex); } } return mVersionCode; } protected void checkThread() { if (Looper.myLooper() != Looper.getMainLooper()) { throw new IllegalStateException("Accessed Application object from thread " + Thread.currentThread().getName()); } } protected void close() { checkThread(); if (mConfig != null) { mConfig.close(); mConfig = null; } if (mShortcutRepository != null) { mShortcutRepository.close(); mShortcutRepository = null; } if (mSourceTaskExecutor != null) { mSourceTaskExecutor.close(); mSourceTaskExecutor = null; } if (mSuggestionsProvider != null) { mSuggestionsProvider.close(); mSuggestionsProvider = null; } } public synchronized Handler getMainThreadHandler() { if (mUiThreadHandler == null) { mUiThreadHandler = new Handler(Looper.getMainLooper()); } return mUiThreadHandler; } public void runOnUiThread(Runnable action) { getMainThreadHandler().post(action); } public synchronized NamedTaskExecutor getIconLoaderExecutor() { if (mIconLoaderExecutor == null) { mIconLoaderExecutor = createIconLoaderExecutor(); } return mIconLoaderExecutor; } protected NamedTaskExecutor createIconLoaderExecutor() { ThreadFactory iconThreadFactory = new PriorityThreadFactory( Process.THREAD_PRIORITY_BACKGROUND); return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory)); } /** * Indicates that construction of the QSB UI is now complete. */ public void onStartupComplete() { } /** * Gets the QSB configuration object. * May be called from any thread. */ public synchronized Config getConfig() { if (mConfig == null) { mConfig = createConfig(); } return mConfig; } protected Config createConfig() { return new Config(getContext()); } public synchronized SearchSettings getSettings() { if (mSettings == null) { mSettings = createSettings(); mSettings.upgradeSettingsIfNeeded(); } return mSettings; } protected SearchSettings createSettings() { return new SearchSettingsImpl(getContext(), getConfig()); } /** * Gets all corpora. * * May only be called from the main thread. */ public Corpora getCorpora() { checkThread(); if (mCorpora == null) { mCorpora = createCorpora(getSources()); } return mCorpora; } protected Corpora createCorpora(Sources sources) { SearchableCorpora corpora = new SearchableCorpora(getContext(), getSettings(), sources, createCorpusFactory()); corpora.update(); return corpora; } /** * Updates the corpora, if they are loaded. * May only be called from the main thread. */ public void updateCorpora() { checkThread(); if (mCorpora != null) { mCorpora.update(); } } protected Sources getSources() { checkThread(); if (mSources == null) { mSources = createSources(); } return mSources; } protected Sources createSources() { return new SearchableSources(getContext(), getMainThreadHandler(), getIconLoaderExecutor(), getConfig()); } protected CorpusFactory createCorpusFactory() { int numWebCorpusThreads = getConfig().getNumWebCorpusThreads(); return new SearchableCorpusFactory(getContext(), getConfig(), getSettings(), createExecutorFactory(numWebCorpusThreads)); } protected Factory<Executor> createExecutorFactory(final int numThreads) { final ThreadFactory threadFactory = getQueryThreadFactory(); return new Factory<Executor>() { public Executor create() { return Executors.newFixedThreadPool(numThreads, threadFactory); } }; } /** * Gets the corpus ranker. * May only be called from the main thread. */ public CorpusRanker getCorpusRanker() { checkThread(); if (mCorpusRanker == null) { mCorpusRanker = createCorpusRanker(); } return mCorpusRanker; } protected CorpusRanker createCorpusRanker() { return new DefaultCorpusRanker(getCorpora(), getShortcutRepository()); } /** * Gets the shortcut repository. * May only be called from the main thread. */ public ShortcutRepository getShortcutRepository() { checkThread(); if (mShortcutRepository == null) { mShortcutRepository = createShortcutRepository(); } return mShortcutRepository; } protected ShortcutRepository createShortcutRepository() { ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d", new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND)); Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory); return ShortcutRepositoryImplLog.create(getContext(), getConfig(), getCorpora(), getShortcutRefresher(), getMainThreadHandler(), logExecutor); } /** * Gets the shortcut refresher. * May only be called from the main thread. */ public ShortcutRefresher getShortcutRefresher() { checkThread(); if (mShortcutRefresher == null) { mShortcutRefresher = createShortcutRefresher(); } return mShortcutRefresher; } protected ShortcutRefresher createShortcutRefresher() { // For now, ShortcutRefresher gets its own SourceTaskExecutor return new SourceShortcutRefresher(createSourceTaskExecutor()); } /** * Gets the source task executor. * May only be called from the main thread. */ public NamedTaskExecutor getSourceTaskExecutor() { checkThread(); if (mSourceTaskExecutor == null) { mSourceTaskExecutor = createSourceTaskExecutor(); } return mSourceTaskExecutor; } protected NamedTaskExecutor createSourceTaskExecutor() { ThreadFactory queryThreadFactory = getQueryThreadFactory(); return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory)); } /** * Gets the query thread factory. * May only be called from the main thread. */ protected ThreadFactory getQueryThreadFactory() { checkThread(); if (mQueryThreadFactory == null) { mQueryThreadFactory = createQueryThreadFactory(); } return mQueryThreadFactory; } protected ThreadFactory createQueryThreadFactory() { String nameFormat = "QSB #%d"; int priority = getConfig().getQueryThreadPriority(); return new NamingThreadFactory(nameFormat, new PriorityThreadFactory(priority)); } /** * Gets the suggestion provider. * * May only be called from the main thread. */ protected SuggestionsProvider getSuggestionsProvider() { checkThread(); if (mSuggestionsProvider == null) { mSuggestionsProvider = createSuggestionsProvider(); } return mSuggestionsProvider; } protected SuggestionsProvider createSuggestionsProvider() { return new SuggestionsProviderImpl(getConfig(), getSourceTaskExecutor(), getMainThreadHandler(), getLogger()); } /** * Gets the default suggestion view factory. * May only be called from the main thread. */ public SuggestionViewFactory getSuggestionViewFactory() { checkThread(); if (mSuggestionViewFactory == null) { mSuggestionViewFactory = createSuggestionViewFactory(); } return mSuggestionViewFactory; } protected SuggestionViewFactory createSuggestionViewFactory() { return new DefaultSuggestionViewFactory(getContext()); } public Promoter createBlendingPromoter() { return new ShortcutPromoter(getConfig(), new RankAwarePromoter(getConfig(), null, null), null); } public Promoter createSingleCorpusPromoter(Corpus corpus) { return new SingleCorpusPromoter(corpus, Integer.MAX_VALUE); } public Promoter createSingleCorpusResultsPromoter(Corpus corpus) { return new SingleCorpusResultsPromoter(corpus, Integer.MAX_VALUE); } public Promoter createWebPromoter() { return new WebPromoter(getConfig().getMaxShortcutsPerWebSource()); } public Promoter createResultsPromoter() { SuggestionFilter resultFilter = new ResultFilter(); return new ShortcutPromoter(getConfig(), null, resultFilter); } /** * Gets the Google source. * May only be called from the main thread. */ public GoogleSource getGoogleSource() { checkThread(); if (mGoogleSource == null) { mGoogleSource = createGoogleSource(); } return mGoogleSource; } protected GoogleSource createGoogleSource() { return new GoogleSuggestClient(getContext(), getMainThreadHandler(), getIconLoaderExecutor(), getConfig()); } /** * Gets Voice Search utilities. */ public VoiceSearch getVoiceSearch() { checkThread(); if (mVoiceSearch == null) { mVoiceSearch = createVoiceSearch(); } return mVoiceSearch; } protected VoiceSearch createVoiceSearch() { return new VoiceSearch(getContext()); } /** * Gets the event logger. * May only be called from the main thread. */ public Logger getLogger() { checkThread(); if (mLogger == null) { mLogger = createLogger(); } return mLogger; } protected Logger createLogger() { return new EventLogLogger(getContext(), getConfig()); } public SuggestionFormatter getSuggestionFormatter() { if (mSuggestionFormatter == null) { mSuggestionFormatter = createSuggestionFormatter(); } return mSuggestionFormatter; } protected SuggestionFormatter createSuggestionFormatter() { return new LevenshteinSuggestionFormatter(getTextAppearanceFactory()); } public TextAppearanceFactory getTextAppearanceFactory() { if (mTextAppearanceFactory == null) { mTextAppearanceFactory = createTextAppearanceFactory(); } return mTextAppearanceFactory; } protected TextAppearanceFactory createTextAppearanceFactory() { return new TextAppearanceFactory(getContext()); } public PreferenceControllerFactory createPreferenceControllerFactory(Activity activity) { return new PreferenceControllerFactory(getSettings(), activity); } public synchronized HttpHelper getHttpHelper() { if (mHttpHelper == null) { mHttpHelper = createHttpHelper(); } return mHttpHelper; } protected HttpHelper createHttpHelper() { return new JavaNetHttpHelper( new JavaNetHttpHelper.PassThroughRewriter(), getConfig().getUserAgent()); } public synchronized SearchBaseUrlHelper getSearchBaseUrlHelper() { if (mSearchBaseUrlHelper == null) { mSearchBaseUrlHelper = createSearchBaseUrlHelper(); } return mSearchBaseUrlHelper; } protected SearchBaseUrlHelper createSearchBaseUrlHelper() { // This cast to "SearchSettingsImpl" is somewhat ugly. return new SearchBaseUrlHelper(getContext(), getHttpHelper(), getSettings(), ((SearchSettingsImpl)getSettings()).getSearchPreferences()); } public Help getHelp() { // No point caching this, it's super cheap. return new Help(getContext(), getConfig()); } }