QsbApplication.java revision c953ef06f0fc1fb4157fe67aa145cf702ee204d0
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.google.SearchBaseUrlHelper; 22import com.android.quicksearchbox.preferences.PreferenceControllerFactory; 23import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory; 24import com.android.quicksearchbox.ui.SuggestionViewFactory; 25import com.android.quicksearchbox.util.Factory; 26import com.android.quicksearchbox.util.HttpHelper; 27import com.android.quicksearchbox.util.JavaNetHttpHelper; 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.app.Activity; 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; 42import android.view.ContextThemeWrapper; 43 44import java.util.concurrent.Executor; 45import java.util.concurrent.Executors; 46import java.util.concurrent.ThreadFactory; 47 48public 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 // TODO while Honeycomb is still under development, this doesn't work. When honeycomb is 84 // done, this needs to be changed, 85 //return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; 86 return true; 87 } 88 89 public static QsbApplication get(Context context) { 90 return ((QsbApplicationWrapper) context.getApplicationContext()).getApp(); 91 } 92 93 protected Context getContext() { 94 return mContext; 95 } 96 97 public int getVersionCode() { 98 if (mVersionCode == 0) { 99 try { 100 PackageManager pm = getContext().getPackageManager(); 101 PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0); 102 mVersionCode = pkgInfo.versionCode; 103 } catch (PackageManager.NameNotFoundException ex) { 104 // The current package should always exist, how else could we 105 // run code from it? 106 throw new RuntimeException(ex); 107 } 108 } 109 return mVersionCode; 110 } 111 112 protected void checkThread() { 113 if (Looper.myLooper() != Looper.getMainLooper()) { 114 throw new IllegalStateException("Accessed Application object from thread " 115 + Thread.currentThread().getName()); 116 } 117 } 118 119 protected void close() { 120 checkThread(); 121 if (mConfig != null) { 122 mConfig.close(); 123 mConfig = null; 124 } 125 if (mShortcutRepository != null) { 126 mShortcutRepository.close(); 127 mShortcutRepository = null; 128 } 129 if (mSourceTaskExecutor != null) { 130 mSourceTaskExecutor.close(); 131 mSourceTaskExecutor = null; 132 } 133 if (mSuggestionsProvider != null) { 134 mSuggestionsProvider.close(); 135 mSuggestionsProvider = null; 136 } 137 } 138 139 public synchronized Handler getMainThreadHandler() { 140 if (mUiThreadHandler == null) { 141 mUiThreadHandler = new Handler(Looper.getMainLooper()); 142 } 143 return mUiThreadHandler; 144 } 145 146 public void runOnUiThread(Runnable action) { 147 getMainThreadHandler().post(action); 148 } 149 150 public synchronized NamedTaskExecutor getIconLoaderExecutor() { 151 if (mIconLoaderExecutor == null) { 152 mIconLoaderExecutor = createIconLoaderExecutor(); 153 } 154 return mIconLoaderExecutor; 155 } 156 157 protected NamedTaskExecutor createIconLoaderExecutor() { 158 ThreadFactory iconThreadFactory = new PriorityThreadFactory( 159 Process.THREAD_PRIORITY_BACKGROUND); 160 return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory)); 161 } 162 163 /** 164 * Indicates that construction of the QSB UI is now complete. 165 */ 166 public void onStartupComplete() { 167 } 168 169 /** 170 * Gets the QSB configuration object. 171 * May be called from any thread. 172 */ 173 public synchronized Config getConfig() { 174 if (mConfig == null) { 175 mConfig = createConfig(); 176 } 177 return mConfig; 178 } 179 180 protected Config createConfig() { 181 return new Config(getContext()); 182 } 183 184 public synchronized SearchSettings getSettings() { 185 if (mSettings == null) { 186 mSettings = createSettings(); 187 mSettings.upgradeSettingsIfNeeded(); 188 } 189 return mSettings; 190 } 191 192 protected SearchSettings createSettings() { 193 return new SearchSettingsImpl(getContext(), getConfig()); 194 } 195 196 /** 197 * Gets all corpora. 198 * 199 * May only be called from the main thread. 200 */ 201 public Corpora getCorpora() { 202 checkThread(); 203 if (mCorpora == null) { 204 mCorpora = createCorpora(getSources()); 205 } 206 return mCorpora; 207 } 208 209 protected Corpora createCorpora(Sources sources) { 210 SearchableCorpora corpora = new SearchableCorpora(getContext(), getSettings(), sources, 211 createCorpusFactory()); 212 corpora.update(); 213 return corpora; 214 } 215 216 /** 217 * Updates the corpora, if they are loaded. 218 * May only be called from the main thread. 219 */ 220 public void updateCorpora() { 221 checkThread(); 222 if (mCorpora != null) { 223 mCorpora.update(); 224 } 225 } 226 227 protected Sources getSources() { 228 checkThread(); 229 if (mSources == null) { 230 mSources = createSources(); 231 } 232 return mSources; 233 } 234 235 protected Sources createSources() { 236 return new SearchableSources(getContext(), getMainThreadHandler(), 237 getIconLoaderExecutor(), getConfig()); 238 } 239 240 protected CorpusFactory createCorpusFactory() { 241 int numWebCorpusThreads = getConfig().getNumWebCorpusThreads(); 242 return new SearchableCorpusFactory(getContext(), getConfig(), getSettings(), 243 createExecutorFactory(numWebCorpusThreads)); 244 } 245 246 protected Factory<Executor> createExecutorFactory(final int numThreads) { 247 final ThreadFactory threadFactory = getQueryThreadFactory(); 248 return new Factory<Executor>() { 249 public Executor create() { 250 return Executors.newFixedThreadPool(numThreads, threadFactory); 251 } 252 }; 253 } 254 255 /** 256 * Gets the corpus ranker. 257 * May only be called from the main thread. 258 */ 259 public CorpusRanker getCorpusRanker() { 260 checkThread(); 261 if (mCorpusRanker == null) { 262 mCorpusRanker = createCorpusRanker(); 263 } 264 return mCorpusRanker; 265 } 266 267 protected CorpusRanker createCorpusRanker() { 268 return new DefaultCorpusRanker(getCorpora(), getShortcutRepository()); 269 } 270 271 /** 272 * Gets the shortcut repository. 273 * May only be called from the main thread. 274 */ 275 public ShortcutRepository getShortcutRepository() { 276 checkThread(); 277 if (mShortcutRepository == null) { 278 mShortcutRepository = createShortcutRepository(); 279 } 280 return mShortcutRepository; 281 } 282 283 protected ShortcutRepository createShortcutRepository() { 284 ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d", 285 new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND)); 286 Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory); 287 return ShortcutRepositoryImplLog.create(getContext(), getConfig(), getCorpora(), 288 getShortcutRefresher(), getMainThreadHandler(), logExecutor); 289 } 290 291 /** 292 * Gets the shortcut refresher. 293 * May only be called from the main thread. 294 */ 295 public ShortcutRefresher getShortcutRefresher() { 296 checkThread(); 297 if (mShortcutRefresher == null) { 298 mShortcutRefresher = createShortcutRefresher(); 299 } 300 return mShortcutRefresher; 301 } 302 303 protected ShortcutRefresher createShortcutRefresher() { 304 // For now, ShortcutRefresher gets its own SourceTaskExecutor 305 return new SourceShortcutRefresher(createSourceTaskExecutor()); 306 } 307 308 /** 309 * Gets the source task executor. 310 * May only be called from the main thread. 311 */ 312 public NamedTaskExecutor getSourceTaskExecutor() { 313 checkThread(); 314 if (mSourceTaskExecutor == null) { 315 mSourceTaskExecutor = createSourceTaskExecutor(); 316 } 317 return mSourceTaskExecutor; 318 } 319 320 protected NamedTaskExecutor createSourceTaskExecutor() { 321 ThreadFactory queryThreadFactory = getQueryThreadFactory(); 322 return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory)); 323 } 324 325 /** 326 * Gets the query thread factory. 327 * May only be called from the main thread. 328 */ 329 protected ThreadFactory getQueryThreadFactory() { 330 checkThread(); 331 if (mQueryThreadFactory == null) { 332 mQueryThreadFactory = createQueryThreadFactory(); 333 } 334 return mQueryThreadFactory; 335 } 336 337 protected ThreadFactory createQueryThreadFactory() { 338 String nameFormat = "QSB #%d"; 339 int priority = getConfig().getQueryThreadPriority(); 340 return new NamingThreadFactory(nameFormat, 341 new PriorityThreadFactory(priority)); 342 } 343 344 /** 345 * Gets the suggestion provider. 346 * 347 * May only be called from the main thread. 348 */ 349 protected SuggestionsProvider getSuggestionsProvider() { 350 checkThread(); 351 if (mSuggestionsProvider == null) { 352 mSuggestionsProvider = createSuggestionsProvider(); 353 } 354 return mSuggestionsProvider; 355 } 356 357 protected SuggestionsProvider createSuggestionsProvider() { 358 return new SuggestionsProviderImpl(getConfig(), 359 getSourceTaskExecutor(), 360 getMainThreadHandler(), 361 getLogger()); 362 } 363 364 /** 365 * Gets the default suggestion view factory. 366 * May only be called from the main thread. 367 */ 368 public SuggestionViewFactory getSuggestionViewFactory() { 369 checkThread(); 370 if (mSuggestionViewFactory == null) { 371 mSuggestionViewFactory = createSuggestionViewFactory(); 372 } 373 return mSuggestionViewFactory; 374 } 375 376 protected SuggestionViewFactory createSuggestionViewFactory() { 377 return new DefaultSuggestionViewFactory(getContext()); 378 } 379 380 public Promoter createBlendingPromoter() { 381 return new ShortcutPromoter(getConfig(), 382 new RankAwarePromoter(getConfig(), null, null), null); 383 } 384 385 public Promoter createSingleCorpusPromoter(Corpus corpus) { 386 return new SingleCorpusPromoter(corpus, Integer.MAX_VALUE); 387 } 388 389 public Promoter createSingleCorpusResultsPromoter(Corpus corpus) { 390 return new SingleCorpusResultsPromoter(corpus, Integer.MAX_VALUE); 391 } 392 393 public Promoter createWebPromoter() { 394 return new WebPromoter(getConfig().getMaxShortcutsPerWebSource()); 395 } 396 397 public Promoter createResultsPromoter() { 398 SuggestionFilter resultFilter = new ResultFilter(); 399 return new ShortcutPromoter(getConfig(), null, resultFilter); 400 } 401 402 /** 403 * Gets the Google source. 404 * May only be called from the main thread. 405 */ 406 public GoogleSource getGoogleSource() { 407 checkThread(); 408 if (mGoogleSource == null) { 409 mGoogleSource = createGoogleSource(); 410 } 411 return mGoogleSource; 412 } 413 414 protected GoogleSource createGoogleSource() { 415 return new GoogleSuggestClient(getContext(), getMainThreadHandler(), 416 getIconLoaderExecutor(), getConfig()); 417 } 418 419 /** 420 * Gets Voice Search utilities. 421 */ 422 public VoiceSearch getVoiceSearch() { 423 checkThread(); 424 if (mVoiceSearch == null) { 425 mVoiceSearch = createVoiceSearch(); 426 } 427 return mVoiceSearch; 428 } 429 430 protected VoiceSearch createVoiceSearch() { 431 return new VoiceSearch(getContext()); 432 } 433 434 /** 435 * Gets the event logger. 436 * May only be called from the main thread. 437 */ 438 public Logger getLogger() { 439 checkThread(); 440 if (mLogger == null) { 441 mLogger = createLogger(); 442 } 443 return mLogger; 444 } 445 446 protected Logger createLogger() { 447 return new EventLogLogger(getContext(), getConfig()); 448 } 449 450 public SuggestionFormatter getSuggestionFormatter() { 451 if (mSuggestionFormatter == null) { 452 mSuggestionFormatter = createSuggestionFormatter(); 453 } 454 return mSuggestionFormatter; 455 } 456 457 protected SuggestionFormatter createSuggestionFormatter() { 458 return new LevenshteinSuggestionFormatter(getTextAppearanceFactory()); 459 } 460 461 public TextAppearanceFactory getTextAppearanceFactory() { 462 if (mTextAppearanceFactory == null) { 463 mTextAppearanceFactory = createTextAppearanceFactory(); 464 } 465 return mTextAppearanceFactory; 466 } 467 468 protected TextAppearanceFactory createTextAppearanceFactory() { 469 return new TextAppearanceFactory(getContext()); 470 } 471 472 public PreferenceControllerFactory createPreferenceControllerFactory(Activity activity) { 473 return new PreferenceControllerFactory(getSettings(), activity); 474 } 475 476 public synchronized HttpHelper getHttpHelper() { 477 if (mHttpHelper == null) { 478 mHttpHelper = createHttpHelper(); 479 } 480 return mHttpHelper; 481 } 482 483 protected HttpHelper createHttpHelper() { 484 return new JavaNetHttpHelper( 485 new JavaNetHttpHelper.PassThroughRewriter(), 486 getConfig().getUserAgent()); 487 } 488 489 public synchronized SearchBaseUrlHelper getSearchBaseUrlHelper() { 490 if (mSearchBaseUrlHelper == null) { 491 mSearchBaseUrlHelper = createSearchBaseUrlHelper(); 492 } 493 494 return mSearchBaseUrlHelper; 495 } 496 497 protected SearchBaseUrlHelper createSearchBaseUrlHelper() { 498 return new SearchBaseUrlHelper(getContext(), getHttpHelper(), 499 getSettings()); 500 } 501} 502