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