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