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