Home | History | Annotate | Download | only in quicksearchbox
      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 
     17 package com.android.quicksearchbox;
     18 
     19 import com.android.quicksearchbox.google.GoogleSource;
     20 import com.android.quicksearchbox.google.GoogleSuggestClient;
     21 import com.android.quicksearchbox.google.SearchBaseUrlHelper;
     22 import com.android.quicksearchbox.preferences.PreferenceControllerFactory;
     23 import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory;
     24 import com.android.quicksearchbox.ui.SuggestionViewFactory;
     25 import com.android.quicksearchbox.util.Factory;
     26 import com.android.quicksearchbox.util.HttpHelper;
     27 import com.android.quicksearchbox.util.JavaNetHttpHelper;
     28 import com.android.quicksearchbox.util.NamedTaskExecutor;
     29 import com.android.quicksearchbox.util.PerNameExecutor;
     30 import com.android.quicksearchbox.util.PriorityThreadFactory;
     31 import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor;
     32 import com.google.common.util.concurrent.NamingThreadFactory;
     33 
     34 import android.app.Activity;
     35 import android.content.Context;
     36 import android.content.pm.PackageInfo;
     37 import android.content.pm.PackageManager;
     38 import android.os.Build;
     39 import android.os.Handler;
     40 import android.os.Looper;
     41 import android.os.Process;
     42 import android.view.ContextThemeWrapper;
     43 
     44 import java.util.concurrent.Executor;
     45 import java.util.concurrent.Executors;
     46 import java.util.concurrent.ThreadFactory;
     47 
     48 public 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