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