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