QsbApplication.java revision b83882b9efa37ec0f20a0f1c85cf5ccc93194aee
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.concurrent.Executor; 43import java.util.concurrent.Executors; 44import java.util.concurrent.ThreadFactory; 45 46public class QsbApplication { 47 private final Context mContext; 48 49 private int mVersionCode; 50 private Handler mUiThreadHandler; 51 private Config mConfig; 52 private Sources mSources; 53 private Corpora mCorpora; 54 private CorpusRanker mCorpusRanker; 55 private ShortcutRepository mShortcutRepository; 56 private ShortcutRefresher mShortcutRefresher; 57 private NamedTaskExecutor mSourceTaskExecutor; 58 private ThreadFactory mQueryThreadFactory; 59 private SuggestionsProvider mSuggestionsProvider; 60 private SuggestionViewFactory mSuggestionViewFactory; 61 private CorpusViewFactory mCorpusViewFactory; 62 private GoogleSource mGoogleSource; 63 private VoiceSearch mVoiceSearch; 64 private Logger mLogger; 65 private SuggestionFormatter mSuggestionFormatter; 66 private TextAppearanceFactory mTextAppearanceFactory; 67 68 public QsbApplication(Context context) { 69 mContext = context; 70 } 71 72 public static boolean isFroyoOrLater() { 73 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; 74 } 75 76 public static QsbApplication get(Context context) { 77 return ((QsbApplicationWrapper) context.getApplicationContext()).getApp(); 78 } 79 80 protected Context getContext() { 81 return mContext; 82 } 83 84 public int getVersionCode() { 85 if (mVersionCode == 0) { 86 try { 87 PackageManager pm = getContext().getPackageManager(); 88 PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(), 0); 89 mVersionCode = pkgInfo.versionCode; 90 } catch (PackageManager.NameNotFoundException ex) { 91 // The current package should always exist, how else could we 92 // run code from it? 93 throw new RuntimeException(ex); 94 } 95 } 96 return mVersionCode; 97 } 98 99 protected void checkThread() { 100 if (Looper.myLooper() != Looper.getMainLooper()) { 101 throw new IllegalStateException("Accessed Application object from thread " 102 + Thread.currentThread().getName()); 103 } 104 } 105 106 protected void close() { 107 checkThread(); 108 if (mConfig != null) { 109 mConfig.close(); 110 mConfig = null; 111 } 112 if (mShortcutRepository != null) { 113 mShortcutRepository.close(); 114 mShortcutRepository = null; 115 } 116 if (mSourceTaskExecutor != null) { 117 mSourceTaskExecutor.close(); 118 mSourceTaskExecutor = null; 119 } 120 if (mSuggestionsProvider != null) { 121 mSuggestionsProvider.close(); 122 mSuggestionsProvider = null; 123 } 124 } 125 126 public synchronized Handler getMainThreadHandler() { 127 if (mUiThreadHandler == null) { 128 mUiThreadHandler = new Handler(Looper.getMainLooper()); 129 } 130 return mUiThreadHandler; 131 } 132 133 public void runOnUiThread(Runnable action) { 134 getMainThreadHandler().post(action); 135 } 136 137 /** 138 * Indicates that construction of the QSB UI is now complete. 139 */ 140 public void onStartupComplete() { 141 } 142 143 /** 144 * Gets the QSB configuration object. 145 * May be called from any thread. 146 */ 147 public synchronized Config getConfig() { 148 if (mConfig == null) { 149 mConfig = createConfig(); 150 } 151 return mConfig; 152 } 153 154 protected Config createConfig() { 155 return new Config(getContext()); 156 } 157 158 /** 159 * Gets all corpora. 160 * 161 * May only be called from the main thread. 162 */ 163 public Corpora getCorpora() { 164 checkThread(); 165 if (mCorpora == null) { 166 mCorpora = createCorpora(getSources()); 167 } 168 return mCorpora; 169 } 170 171 protected Corpora createCorpora(Sources sources) { 172 SearchableCorpora corpora = new SearchableCorpora(getContext(), sources, 173 createCorpusFactory()); 174 corpora.update(); 175 return corpora; 176 } 177 178 /** 179 * Updates the corpora, if they are loaded. 180 * May only be called from the main thread. 181 */ 182 public void updateCorpora() { 183 checkThread(); 184 if (mCorpora != null) { 185 mCorpora.update(); 186 } 187 } 188 189 protected Sources getSources() { 190 checkThread(); 191 if (mSources == null) { 192 mSources = createSources(); 193 } 194 return mSources; 195 } 196 197 protected Sources createSources() { 198 return new SearchableSources(getContext()); 199 } 200 201 protected CorpusFactory createCorpusFactory() { 202 int numWebCorpusThreads = getConfig().getNumWebCorpusThreads(); 203 return new SearchableCorpusFactory(getContext(), getConfig(), 204 createExecutorFactory(numWebCorpusThreads)); 205 } 206 207 protected Factory<Executor> createExecutorFactory(final int numThreads) { 208 final ThreadFactory threadFactory = getQueryThreadFactory(); 209 return new Factory<Executor>() { 210 public Executor create() { 211 return Executors.newFixedThreadPool(numThreads, threadFactory); 212 } 213 }; 214 } 215 216 /** 217 * Gets the corpus ranker. 218 * May only be called from the main thread. 219 */ 220 public CorpusRanker getCorpusRanker() { 221 checkThread(); 222 if (mCorpusRanker == null) { 223 mCorpusRanker = createCorpusRanker(); 224 } 225 return mCorpusRanker; 226 } 227 228 protected CorpusRanker createCorpusRanker() { 229 return new DefaultCorpusRanker(getCorpora(), getShortcutRepository()); 230 } 231 232 /** 233 * Gets the shortcut repository. 234 * May only be called from the main thread. 235 */ 236 public ShortcutRepository getShortcutRepository() { 237 checkThread(); 238 if (mShortcutRepository == null) { 239 mShortcutRepository = createShortcutRepository(); 240 } 241 return mShortcutRepository; 242 } 243 244 protected ShortcutRepository createShortcutRepository() { 245 ThreadFactory logThreadFactory = new NamingThreadFactory("ShortcutRepositoryWriter #%d", 246 new PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND)); 247 Executor logExecutor = Executors.newSingleThreadExecutor(logThreadFactory); 248 return ShortcutRepositoryImplLog.create(getContext(), getConfig(), getCorpora(), 249 getShortcutRefresher(), getMainThreadHandler(), logExecutor); 250 } 251 252 /** 253 * Gets the shortcut refresher. 254 * May only be called from the main thread. 255 */ 256 public ShortcutRefresher getShortcutRefresher() { 257 checkThread(); 258 if (mShortcutRefresher == null) { 259 mShortcutRefresher = createShortcutRefresher(); 260 } 261 return mShortcutRefresher; 262 } 263 264 protected ShortcutRefresher createShortcutRefresher() { 265 // For now, ShortcutRefresher gets its own SourceTaskExecutor 266 return new SourceShortcutRefresher(createSourceTaskExecutor()); 267 } 268 269 /** 270 * Gets the source task executor. 271 * May only be called from the main thread. 272 */ 273 public NamedTaskExecutor getSourceTaskExecutor() { 274 checkThread(); 275 if (mSourceTaskExecutor == null) { 276 mSourceTaskExecutor = createSourceTaskExecutor(); 277 } 278 return mSourceTaskExecutor; 279 } 280 281 protected NamedTaskExecutor createSourceTaskExecutor() { 282 ThreadFactory queryThreadFactory = getQueryThreadFactory(); 283 return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory)); 284 } 285 286 /** 287 * Gets the query thread factory. 288 * May only be called from the main thread. 289 */ 290 protected ThreadFactory getQueryThreadFactory() { 291 checkThread(); 292 if (mQueryThreadFactory == null) { 293 mQueryThreadFactory = createQueryThreadFactory(); 294 } 295 return mQueryThreadFactory; 296 } 297 298 protected ThreadFactory createQueryThreadFactory() { 299 String nameFormat = "QSB #%d"; 300 int priority = getConfig().getQueryThreadPriority(); 301 return new NamingThreadFactory(nameFormat, 302 new PriorityThreadFactory(priority)); 303 } 304 305 /** 306 * Gets the suggestion provider. 307 * 308 * May only be called from the main thread. 309 */ 310 protected SuggestionsProvider getSuggestionsProvider() { 311 checkThread(); 312 if (mSuggestionsProvider == null) { 313 mSuggestionsProvider = createSuggestionsProvider(); 314 } 315 return mSuggestionsProvider; 316 } 317 318 protected SuggestionsProvider createSuggestionsProvider() { 319 return new SuggestionsProviderImpl(getConfig(), 320 getSourceTaskExecutor(), 321 getMainThreadHandler(), 322 getLogger()); 323 } 324 325 /** 326 * Gets the suggestion view factory. 327 * May only be called from the main thread. 328 */ 329 public SuggestionViewFactory getSuggestionViewFactory() { 330 checkThread(); 331 if (mSuggestionViewFactory == null) { 332 mSuggestionViewFactory = createSuggestionViewFactory(); 333 } 334 return mSuggestionViewFactory; 335 } 336 337 protected SuggestionViewFactory createSuggestionViewFactory() { 338 return new SuggestionViewInflater(getContext()); 339 } 340 341 /** 342 * Gets the corpus view factory. 343 * May only be called from the main thread. 344 */ 345 public CorpusViewFactory getCorpusViewFactory() { 346 checkThread(); 347 if (mCorpusViewFactory == null) { 348 mCorpusViewFactory = createCorpusViewFactory(); 349 } 350 return mCorpusViewFactory; 351 } 352 353 protected CorpusViewFactory createCorpusViewFactory() { 354 return new CorpusViewInflater(getContext()); 355 } 356 357 /** 358 * Creates a suggestions adapter that blends web suggestions and results. 359 * May only be called from the main thread. 360 */ 361 public SuggestionsAdapter createBlendingSuggestionsAdapter() { 362 SuggestionViewFactory viewFactory = getSuggestionViewFactory(); 363 return new DelayingSuggestionsAdapter(createBlendingPromoter(), viewFactory, 364 getConfig().getMaxPromotedSuggestions()); 365 } 366 367 /** 368 * Creates a suggestions adapter that shows web suggestions. 369 * May only be called from the main thread. 370 */ 371 public SuggestionsAdapter createWebSuggestionsAdapter() { 372 SuggestionViewFactory viewFactory = getSuggestionViewFactory(); 373 return new DelayingSuggestionsAdapter(createWebPromoter(), viewFactory, 374 getConfig().getMaxPromotedSuggestions()); 375 } 376 377 /** 378 * Creates a suggestions adapter that shows results. 379 * May only be called from the main thread. 380 */ 381 public SuggestionsAdapter createResultSuggestionsAdapter() { 382 SuggestionViewFactory viewFactory = getSuggestionViewFactory(); 383 return new DelayingSuggestionsAdapter(createResultsPromoter(), viewFactory, 384 getConfig().getMaxPromotedSuggestions()); 385 } 386 387 private Promoter createBlendingPromoter() { 388 return new BlendingPromoter(getConfig()); 389 } 390 391 private Promoter createWebPromoter() { 392 return new WebPromoter(getConfig().getMaxShortcutsPerWebSource()); 393 } 394 395 private Promoter createResultsPromoter() { 396 return new ResultPromoter(getConfig()); 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()); 413 } 414 415 /** 416 * Gets Voice Search utilities. 417 */ 418 public VoiceSearch getVoiceSearch() { 419 checkThread(); 420 if (mVoiceSearch == null) { 421 mVoiceSearch = createVoiceSearch(); 422 } 423 return mVoiceSearch; 424 } 425 426 protected VoiceSearch createVoiceSearch() { 427 return new VoiceSearch(getContext()); 428 } 429 430 /** 431 * Gets the event logger. 432 * May only be called from the main thread. 433 */ 434 public Logger getLogger() { 435 checkThread(); 436 if (mLogger == null) { 437 mLogger = createLogger(); 438 } 439 return mLogger; 440 } 441 442 protected Logger createLogger() { 443 return new EventLogLogger(getContext(), getConfig()); 444 } 445 446 public SuggestionFormatter getSuggestionFormatter() { 447 if (mSuggestionFormatter == null) { 448 mSuggestionFormatter = createSuggestionFormatter(); 449 } 450 return mSuggestionFormatter; 451 } 452 453 protected SuggestionFormatter createSuggestionFormatter() { 454 return new LevenshteinSuggestionFormatter(getTextAppearanceFactory()); 455 } 456 457 public TextAppearanceFactory getTextAppearanceFactory() { 458 if (mTextAppearanceFactory == null) { 459 mTextAppearanceFactory = createTextAppearanceFactory(); 460 } 461 return mTextAppearanceFactory; 462 } 463 464 protected TextAppearanceFactory createTextAppearanceFactory() { 465 return new TextAppearanceFactory(getContext()); 466 } 467 468} 469