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.ThreadFactoryBuilder;
     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 =
    282                 new ThreadFactoryBuilder()
    283                 .setNameFormat("ShortcutRepository #%d")
    284                 .setThreadFactory(new PriorityThreadFactory(
    285                         Process.THREAD_PRIORITY_BACKGROUND))
    286                 .build();
    287         Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory);
    288         return ShortcutRepositoryImplLog.create(getContext(), getConfig(), getCorpora(),
    289             getShortcutRefresher(), getMainThreadHandler(), logExecutor);
    290     }
    291 
    292     /**
    293      * Gets the shortcut refresher.
    294      * May only be called from the main thread.
    295      */
    296     public ShortcutRefresher getShortcutRefresher() {
    297         checkThread();
    298         if (mShortcutRefresher == null) {
    299             mShortcutRefresher = createShortcutRefresher();
    300         }
    301         return mShortcutRefresher;
    302     }
    303 
    304     protected ShortcutRefresher createShortcutRefresher() {
    305         // For now, ShortcutRefresher gets its own SourceTaskExecutor
    306         return new SourceShortcutRefresher(createSourceTaskExecutor());
    307     }
    308 
    309     /**
    310      * Gets the source task executor.
    311      * May only be called from the main thread.
    312      */
    313     public NamedTaskExecutor getSourceTaskExecutor() {
    314         checkThread();
    315         if (mSourceTaskExecutor == null) {
    316             mSourceTaskExecutor = createSourceTaskExecutor();
    317         }
    318         return mSourceTaskExecutor;
    319     }
    320 
    321     protected NamedTaskExecutor createSourceTaskExecutor() {
    322         ThreadFactory queryThreadFactory = getQueryThreadFactory();
    323         return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory));
    324     }
    325 
    326     /**
    327      * Gets the query thread factory.
    328      * May only be called from the main thread.
    329      */
    330     protected ThreadFactory getQueryThreadFactory() {
    331         checkThread();
    332         if (mQueryThreadFactory == null) {
    333             mQueryThreadFactory = createQueryThreadFactory();
    334         }
    335         return mQueryThreadFactory;
    336     }
    337 
    338     protected ThreadFactory createQueryThreadFactory() {
    339         String nameFormat = "QSB #%d";
    340         int priority = getConfig().getQueryThreadPriority();
    341         return new ThreadFactoryBuilder()
    342                 .setNameFormat(nameFormat)
    343                 .setThreadFactory(new PriorityThreadFactory(priority))
    344                 .build();
    345     }
    346 
    347     /**
    348      * Gets the suggestion provider.
    349      *
    350      * May only be called from the main thread.
    351      */
    352     protected SuggestionsProvider getSuggestionsProvider() {
    353         checkThread();
    354         if (mSuggestionsProvider == null) {
    355             mSuggestionsProvider = createSuggestionsProvider();
    356         }
    357         return mSuggestionsProvider;
    358     }
    359 
    360     protected SuggestionsProvider createSuggestionsProvider() {
    361         return new SuggestionsProviderImpl(getConfig(),
    362               getSourceTaskExecutor(),
    363               getMainThreadHandler(),
    364               getLogger());
    365     }
    366 
    367     /**
    368      * Gets the default suggestion view factory.
    369      * May only be called from the main thread.
    370      */
    371     public SuggestionViewFactory getSuggestionViewFactory() {
    372         checkThread();
    373         if (mSuggestionViewFactory == null) {
    374             mSuggestionViewFactory = createSuggestionViewFactory();
    375         }
    376         return mSuggestionViewFactory;
    377     }
    378 
    379     protected SuggestionViewFactory createSuggestionViewFactory() {
    380         return new DefaultSuggestionViewFactory(getContext());
    381     }
    382 
    383     public Promoter createBlendingPromoter() {
    384         return new ShortcutPromoter(getConfig(),
    385                 new RankAwarePromoter(getConfig(), null, null), null);
    386     }
    387 
    388     public Promoter createSingleCorpusPromoter(Corpus corpus) {
    389         return new SingleCorpusPromoter(corpus, Integer.MAX_VALUE);
    390     }
    391 
    392     public Promoter createSingleCorpusResultsPromoter(Corpus corpus) {
    393         return new SingleCorpusResultsPromoter(corpus, Integer.MAX_VALUE);
    394     }
    395 
    396     public Promoter createWebPromoter() {
    397         return new WebPromoter(getConfig().getMaxShortcutsPerWebSource());
    398     }
    399 
    400     public Promoter createResultsPromoter() {
    401         SuggestionFilter resultFilter = new ResultFilter();
    402         return new ShortcutPromoter(getConfig(), null, resultFilter);
    403     }
    404 
    405     /**
    406      * Gets the Google source.
    407      * May only be called from the main thread.
    408      */
    409     public GoogleSource getGoogleSource() {
    410         checkThread();
    411         if (mGoogleSource == null) {
    412             mGoogleSource = createGoogleSource();
    413         }
    414         return mGoogleSource;
    415     }
    416 
    417     protected GoogleSource createGoogleSource() {
    418         return new GoogleSuggestClient(getContext(), getMainThreadHandler(),
    419                 getIconLoaderExecutor(), getConfig());
    420     }
    421 
    422     /**
    423      * Gets Voice Search utilities.
    424      */
    425     public VoiceSearch getVoiceSearch() {
    426         checkThread();
    427         if (mVoiceSearch == null) {
    428             mVoiceSearch = createVoiceSearch();
    429         }
    430         return mVoiceSearch;
    431     }
    432 
    433     protected VoiceSearch createVoiceSearch() {
    434         return new VoiceSearch(getContext());
    435     }
    436 
    437     /**
    438      * Gets the event logger.
    439      * May only be called from the main thread.
    440      */
    441     public Logger getLogger() {
    442         checkThread();
    443         if (mLogger == null) {
    444             mLogger = createLogger();
    445         }
    446         return mLogger;
    447     }
    448 
    449     protected Logger createLogger() {
    450         return new EventLogLogger(getContext(), getConfig());
    451     }
    452 
    453     public SuggestionFormatter getSuggestionFormatter() {
    454         if (mSuggestionFormatter == null) {
    455             mSuggestionFormatter = createSuggestionFormatter();
    456         }
    457         return mSuggestionFormatter;
    458     }
    459 
    460     protected SuggestionFormatter createSuggestionFormatter() {
    461         return new LevenshteinSuggestionFormatter(getTextAppearanceFactory());
    462     }
    463 
    464     public TextAppearanceFactory getTextAppearanceFactory() {
    465         if (mTextAppearanceFactory == null) {
    466             mTextAppearanceFactory = createTextAppearanceFactory();
    467         }
    468         return mTextAppearanceFactory;
    469     }
    470 
    471     protected TextAppearanceFactory createTextAppearanceFactory() {
    472         return new TextAppearanceFactory(getContext());
    473     }
    474 
    475     public PreferenceControllerFactory createPreferenceControllerFactory(Activity activity) {
    476         return new PreferenceControllerFactory(getSettings(), activity);
    477     }
    478 
    479     public synchronized HttpHelper getHttpHelper() {
    480         if (mHttpHelper == null) {
    481             mHttpHelper = createHttpHelper();
    482         }
    483         return mHttpHelper;
    484     }
    485 
    486     protected HttpHelper createHttpHelper() {
    487         return new JavaNetHttpHelper(
    488                 new JavaNetHttpHelper.PassThroughRewriter(),
    489                 getConfig().getUserAgent());
    490     }
    491 
    492     public synchronized SearchBaseUrlHelper getSearchBaseUrlHelper() {
    493         if (mSearchBaseUrlHelper == null) {
    494             mSearchBaseUrlHelper = createSearchBaseUrlHelper();
    495         }
    496 
    497         return mSearchBaseUrlHelper;
    498     }
    499 
    500     protected SearchBaseUrlHelper createSearchBaseUrlHelper() {
    501         // This cast to "SearchSettingsImpl" is somewhat ugly.
    502         return new SearchBaseUrlHelper(getContext(), getHttpHelper(),
    503                 getSettings(), ((SearchSettingsImpl)getSettings()).getSearchPreferences());
    504     }
    505 
    506     public Help getHelp() {
    507         // No point caching this, it's super cheap.
    508         return new Help(getContext(), getConfig());
    509     }
    510 }
    511