QsbApplication.java revision d5cd9612d6937dfb174bfe6f4e486f283ef557e9
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.ui.CorpusViewFactory; 22import com.android.quicksearchbox.ui.CorpusViewInflater; 23import com.android.quicksearchbox.ui.DelayingSuggestionsAdapter; 24import com.android.quicksearchbox.ui.SuggestionViewFactory; 25import com.android.quicksearchbox.ui.SuggestionViewInflater; 26import com.android.quicksearchbox.ui.SuggestionsAdapter; 27import com.android.quicksearchbox.util.Factory; 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.content.Context; 35import android.content.pm.PackageInfo; 36import android.content.pm.PackageManager; 37import android.os.Build; 38import android.os.Handler; 39import android.os.Looper; 40import android.os.Process; 41 42import java.util.HashMap; 43import java.util.concurrent.Executor; 44import java.util.concurrent.Executors; 45import java.util.concurrent.ThreadFactory; 46 47public class QsbApplication { 48 private final Context mContext; 49 50 private int mVersionCode; 51 private Handler mUiThreadHandler; 52 private Config mConfig; 53 private Sources mSources; 54 private Corpora mAllCorpora; 55 private Corpora mResultsCorpora; 56 private final HashMap<Corpora, CorpusRanker> mCorpusRankers; 57 private ShortcutRepository mShortcutRepository; 58 private ShortcutRefresher mShortcutRefresher; 59 private NamedTaskExecutor mSourceTaskExecutor; 60 private ThreadFactory mQueryThreadFactory; 61 private SuggestionsProvider mUnifiedProvider; 62 private SuggestionsProvider mWebSuggestionProvider; 63 private SuggestionsProvider mResultsProvider; 64 private SuggestionViewFactory mSuggestionViewFactory; 65 private CorpusViewFactory mCorpusViewFactory; 66 private GoogleSource mGoogleSource; 67 private VoiceSearch mVoiceSearch; 68 private Logger mLogger; 69 private SuggestionFormatter mSuggestionFormatter; 70 private TextAppearanceFactory mTextAppearanceFactory; 71 72 public QsbApplication(Context context) { 73 mContext = context; 74 mCorpusRankers = new HashMap<Corpora, CorpusRanker>(); 75 } 76 77 public static boolean isFroyoOrLater() { 78 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; 79 } 80 81 public static QsbApplication get(Context context) { 82 return ((QsbApplicationWrapper) context.getApplicationContext()).getApp(); 83 } 84 85 protected Context getContext() { 86 return mContext; 87 } 88 89 public int getVersionCode() { 90 if (mVersionCode == 0) { 91 try { 92 PackageManager pm = getContext().getPackageManager(); 93 PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0); 94 mVersionCode = pkgInfo.versionCode; 95 } catch (PackageManager.NameNotFoundException ex) { 96 // The current package should always exist, how else could we 97 // run code from it? 98 throw new RuntimeException(ex); 99 } 100 } 101 return mVersionCode; 102 } 103 104 protected void checkThread() { 105 if (Looper.myLooper() != Looper.getMainLooper()) { 106 throw new IllegalStateException("Accessed Application object from thread " 107 + Thread.currentThread().getName()); 108 } 109 } 110 111 protected void close() { 112 checkThread(); 113 if (mConfig != null) { 114 mConfig.close(); 115 mConfig = null; 116 } 117 if (mShortcutRepository != null) { 118 mShortcutRepository.close(); 119 mShortcutRepository = null; 120 } 121 if (mSourceTaskExecutor != null) { 122 mSourceTaskExecutor.close(); 123 mSourceTaskExecutor = null; 124 } 125 if (mUnifiedProvider != null) { 126 mUnifiedProvider.close(); 127 mUnifiedProvider = null; 128 } 129 if (mWebSuggestionProvider != null) { 130 mWebSuggestionProvider.close(); 131 mWebSuggestionProvider = null; 132 } 133 if (mResultsProvider != null) { 134 mResultsProvider.close(); 135 mResultsProvider = 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 /** 151 * Indicates that construction of the QSB UI is now complete. 152 */ 153 public void onStartupComplete() { 154 } 155 156 /** 157 * Gets the QSB configuration object. 158 * May be called from any thread. 159 */ 160 public synchronized Config getConfig() { 161 if (mConfig == null) { 162 mConfig = createConfig(); 163 } 164 return mConfig; 165 } 166 167 protected Config createConfig() { 168 return new Config(getContext()); 169 } 170 171 /** 172 * Gets the 'all' corpora providing results and web suggestions. 173 * May only be called from the main thread. 174 */ 175 public Corpora getAllCorpora() { 176 checkThread(); 177 if (mAllCorpora == null) { 178 mAllCorpora = createAllCorpora(getSources()); 179 } 180 return mAllCorpora; 181 } 182 183 protected Corpora createAllCorpora(Sources sources) { 184 SearchableCorpora corpora = new SearchableCorpora(getContext(), sources, 185 createAllCorpusFactory()); 186 corpora.update(); 187 return corpora; 188 } 189 190 /** 191 * Gets the corpora providing results only. 192 * May only be called from the main thread. 193 */ 194 public Corpora getResultsCorpora() { 195 checkThread(); 196 if (mResultsCorpora == null) { 197 mResultsCorpora = createResultsCorpora(getSources()); 198 } 199 return mResultsCorpora; 200 } 201 202 protected Corpora createResultsCorpora(Sources sources) { 203 SearchableCorpora corpora = new SearchableCorpora(getContext(), sources, 204 createResultsCorpusFactory()); 205 corpora.update(); 206 return corpora; 207 } 208 209 /** 210 * Updates the corpora, if they are loaded. 211 * May only be called from the main thread. 212 */ 213 public void updateCorpora() { 214 checkThread(); 215 if (mAllCorpora != null) { 216 mAllCorpora.update(); 217 } 218 if (mResultsCorpora != null) { 219 mResultsCorpora.update(); 220 } 221 } 222 223 protected Sources getSources() { 224 checkThread(); 225 if (mSources == null) { 226 mSources = createSources(); 227 } 228 return mSources; 229 } 230 231 protected Sources createSources() { 232 return new SearchableSources(getContext()); 233 } 234 235 protected CorpusFactory createAllCorpusFactory() { 236 int numWebCorpusThreads = getConfig().getNumWebCorpusThreads(); 237 return new SearchableCorpusFactory(getContext(), getConfig(), 238 createExecutorFactory(numWebCorpusThreads)); 239 } 240 241 protected CorpusFactory createResultsCorpusFactory() { 242 int numWebCorpusThreads = getConfig().getNumWebCorpusThreads(); 243 return new ResultsCorpusFactory(getContext(), getConfig(), 244 createExecutorFactory(numWebCorpusThreads)); 245 } 246 247 protected Factory<Executor> createExecutorFactory(final int numThreads) { 248 final ThreadFactory threadFactory = getQueryThreadFactory(); 249 return new Factory<Executor>() { 250 public Executor create() { 251 return Executors.newFixedThreadPool(numThreads, threadFactory); 252 } 253 }; 254 } 255 256 /** 257 * Gets the corpus ranker. 258 * May only be called from the main thread. 259 */ 260 public CorpusRanker getCorpusRanker(Corpora corpora) { 261 checkThread(); 262 if (mCorpusRankers.get(corpora) == null) { 263 mCorpusRankers.put(corpora, createCorpusRanker(corpora)); 264 } 265 return mCorpusRankers.get(corpora); 266 } 267 268 protected CorpusRanker createCorpusRanker(Corpora corpora) { 269 return new DefaultCorpusRanker(corpora, getShortcutRepository(corpora)); 270 } 271 272 /** 273 * Gets the shortcut repository. 274 * May only be called from the main thread. 275 */ 276 public ShortcutRepository getShortcutRepository(Corpora corpora) { 277 checkThread(); 278 if (mShortcutRepository == null) { 279 mShortcutRepository = createShortcutRepository(corpora); 280 } 281 return mShortcutRepository; 282 } 283 284 @Deprecated 285 public ShortcutRepository getShortcutRepository() { 286 return getShortcutRepository(getAllCorpora()); 287 } 288 289 protected ShortcutRepository createShortcutRepository(Corpora corpora) { 290 ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d", 291 new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND)); 292 Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory); 293 return ShortcutRepositoryImplLog.create(getContext(), getConfig(), corpora, 294 getShortcutRefresher(), getMainThreadHandler(), logExecutor); 295 } 296 297 /** 298 * Gets the shortcut refresher. 299 * May only be called from the main thread. 300 */ 301 public ShortcutRefresher getShortcutRefresher() { 302 checkThread(); 303 if (mShortcutRefresher == null) { 304 mShortcutRefresher = createShortcutRefresher(); 305 } 306 return mShortcutRefresher; 307 } 308 309 protected ShortcutRefresher createShortcutRefresher() { 310 // For now, ShortcutRefresher gets its own SourceTaskExecutor 311 return new SourceShortcutRefresher(createSourceTaskExecutor()); 312 } 313 314 /** 315 * Gets the source task executor. 316 * May only be called from the main thread. 317 */ 318 public NamedTaskExecutor getSourceTaskExecutor() { 319 checkThread(); 320 if (mSourceTaskExecutor == null) { 321 mSourceTaskExecutor = createSourceTaskExecutor(); 322 } 323 return mSourceTaskExecutor; 324 } 325 326 protected NamedTaskExecutor createSourceTaskExecutor() { 327 ThreadFactory queryThreadFactory = getQueryThreadFactory(); 328 return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory)); 329 } 330 331 /** 332 * Gets the query thread factory. 333 * May only be called from the main thread. 334 */ 335 protected ThreadFactory getQueryThreadFactory() { 336 checkThread(); 337 if (mQueryThreadFactory == null) { 338 mQueryThreadFactory = createQueryThreadFactory(); 339 } 340 return mQueryThreadFactory; 341 } 342 343 protected ThreadFactory createQueryThreadFactory() { 344 String nameFormat = "QSB #%d"; 345 int priority = getConfig().getQueryThreadPriority(); 346 return new NamingThreadFactory(nameFormat, 347 new PriorityThreadFactory(priority)); 348 } 349 350 /** 351 * Gets the suggestion provider which provides suggestions from all sources blended together. 352 * Used when all suggestions are presented in a single list. 353 * 354 * May only be called from the main thread. 355 */ 356 protected SuggestionsProvider getUnifiedProvider() { 357 checkThread(); 358 if (mUnifiedProvider == null) { 359 mUnifiedProvider = createUnifiedProvider(); 360 } 361 return mUnifiedProvider; 362 } 363 364 /** 365 * Gets the suggestion provider which provides web query suggestions only. 366 * 367 * May only be called from the main thread. 368 */ 369 protected SuggestionsProvider getWebSuggestionsProvider() { 370 checkThread(); 371 if (mWebSuggestionProvider == null) { 372 mWebSuggestionProvider = createWebSuggestionsProvider(); 373 } 374 return mWebSuggestionProvider; 375 } 376 377 /** 378 * Gets the suggestion provider which provides all results for a query except web query 379 * suggestions. 380 * 381 * May only be called from the main thread. 382 */ 383 protected SuggestionsProvider getResultsProvider() { 384 checkThread(); 385 if (mResultsProvider == null) { 386 mResultsProvider = createResultsProvider(); 387 } 388 return mResultsProvider; 389 } 390 391 protected SuggestionsProvider createBlendingProvider(Corpora corpora) { 392 int maxShortcutsPerWebSource = getConfig().getMaxShortcutsPerWebSource(); 393 int maxShortcutsPerNonWebSource = getConfig().getMaxShortcutsPerNonWebSource(); 394 Promoter allPromoter = new ShortcutLimitingPromoter( 395 maxShortcutsPerWebSource, 396 maxShortcutsPerNonWebSource, 397 new ShortcutPromoter( 398 new RankAwarePromoter(getConfig(), corpora))); 399 Promoter singleCorpusPromoter = new ShortcutPromoter(new ConcatPromoter()); 400 BlendingSuggestionsProvider provider = new BlendingSuggestionsProvider(getConfig(), 401 getSourceTaskExecutor(), 402 getMainThreadHandler(), 403 getShortcutRepository(corpora), 404 corpora, 405 getCorpusRanker(corpora), 406 getLogger()); 407 provider.setAllPromoter(allPromoter); 408 provider.setSingleCorpusPromoter(singleCorpusPromoter); 409 return provider; 410 } 411 412 protected SuggestionsProvider createUnifiedProvider() { 413 return createBlendingProvider(getAllCorpora()); 414 } 415 416 protected SuggestionsProvider createWebSuggestionsProvider() { 417 return new WebSuggestionsProvider(getGoogleSource(), getMainThreadHandler()); 418 } 419 420 protected SuggestionsProvider createResultsProvider() { 421 return createBlendingProvider(getResultsCorpora()); 422 } 423 424 /** 425 * Gets the suggestion view factory. 426 * May only be called from the main thread. 427 */ 428 public SuggestionViewFactory getSuggestionViewFactory() { 429 checkThread(); 430 if (mSuggestionViewFactory == null) { 431 mSuggestionViewFactory = createSuggestionViewFactory(); 432 } 433 return mSuggestionViewFactory; 434 } 435 436 protected SuggestionViewFactory createSuggestionViewFactory() { 437 return new SuggestionViewInflater(getContext()); 438 } 439 440 /** 441 * Gets the corpus view factory. 442 * May only be called from the main thread. 443 */ 444 public CorpusViewFactory getCorpusViewFactory() { 445 checkThread(); 446 if (mCorpusViewFactory == null) { 447 mCorpusViewFactory = createCorpusViewFactory(); 448 } 449 return mCorpusViewFactory; 450 } 451 452 protected CorpusViewFactory createCorpusViewFactory() { 453 return new CorpusViewInflater(getContext()); 454 } 455 456 /** 457 * Creates a suggestions adapter. 458 * May only be called from the main thread. 459 */ 460 public SuggestionsAdapter createSuggestionsAdapter() { 461 SuggestionViewFactory viewFactory = getSuggestionViewFactory(); 462 DelayingSuggestionsAdapter adapter = new DelayingSuggestionsAdapter(viewFactory); 463 return adapter; 464 } 465 466 /** 467 * Gets the Google source. 468 * May only be called from the main thread. 469 */ 470 public GoogleSource getGoogleSource() { 471 checkThread(); 472 if (mGoogleSource == null) { 473 mGoogleSource = createGoogleSource(); 474 } 475 return mGoogleSource; 476 } 477 478 protected GoogleSource createGoogleSource() { 479 return new GoogleSuggestClient(getContext()); 480 } 481 482 /** 483 * Gets Voice Search utilities. 484 */ 485 public VoiceSearch getVoiceSearch() { 486 checkThread(); 487 if (mVoiceSearch == null) { 488 mVoiceSearch = createVoiceSearch(); 489 } 490 return mVoiceSearch; 491 } 492 493 protected VoiceSearch createVoiceSearch() { 494 return new VoiceSearch(getContext()); 495 } 496 497 /** 498 * Gets the event logger. 499 * May only be called from the main thread. 500 */ 501 public Logger getLogger() { 502 checkThread(); 503 if (mLogger == null) { 504 mLogger = createLogger(); 505 } 506 return mLogger; 507 } 508 509 protected Logger createLogger() { 510 return new EventLogLogger(getContext(), getConfig()); 511 } 512 513 public SuggestionFormatter getSuggestionFormatter() { 514 if (mSuggestionFormatter == null) { 515 mSuggestionFormatter = createSuggestionFormatter(); 516 } 517 return mSuggestionFormatter; 518 } 519 520 protected SuggestionFormatter createSuggestionFormatter() { 521 return new LevenshteinSuggestionFormatter(getTextAppearanceFactory()); 522 } 523 524 public TextAppearanceFactory getTextAppearanceFactory() { 525 if (mTextAppearanceFactory == null) { 526 mTextAppearanceFactory = createTextAppearanceFactory(); 527 } 528 return mTextAppearanceFactory; 529 } 530 531 protected TextAppearanceFactory createTextAppearanceFactory() { 532 return new TextAppearanceFactory(getContext()); 533 } 534 535} 536