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 17 package com.android.quicksearchbox; 18 19 import android.content.Context; 20 import android.content.pm.PackageInfo; 21 import android.content.pm.PackageManager; 22 import android.os.Build; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Process; 26 import android.view.ContextThemeWrapper; 27 28 import com.android.quicksearchbox.google.GoogleSource; 29 import com.android.quicksearchbox.google.GoogleSuggestClient; 30 import com.android.quicksearchbox.google.SearchBaseUrlHelper; 31 import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory; 32 import com.android.quicksearchbox.ui.SuggestionViewFactory; 33 import com.android.quicksearchbox.util.Factory; 34 import com.android.quicksearchbox.util.HttpHelper; 35 import com.android.quicksearchbox.util.JavaNetHttpHelper; 36 import com.android.quicksearchbox.util.NamedTaskExecutor; 37 import com.android.quicksearchbox.util.PerNameExecutor; 38 import com.android.quicksearchbox.util.PriorityThreadFactory; 39 import com.android.quicksearchbox.util.SingleThreadNamedTaskExecutor; 40 import com.google.common.util.concurrent.ThreadFactoryBuilder; 41 42 import java.util.concurrent.Executor; 43 import java.util.concurrent.Executors; 44 import java.util.concurrent.ThreadFactory; 45 46 public 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 NamedTaskExecutor mSourceTaskExecutor; 54 private ThreadFactory mQueryThreadFactory; 55 private SuggestionsProvider mSuggestionsProvider; 56 private SuggestionViewFactory mSuggestionViewFactory; 57 private GoogleSource mGoogleSource; 58 private VoiceSearch mVoiceSearch; 59 private Logger mLogger; 60 private SuggestionFormatter mSuggestionFormatter; 61 private TextAppearanceFactory mTextAppearanceFactory; 62 private NamedTaskExecutor mIconLoaderExecutor; 63 private HttpHelper mHttpHelper; 64 private SearchBaseUrlHelper mSearchBaseUrlHelper; 65 QsbApplication(Context context)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 isFroyoOrLater()71 public static boolean isFroyoOrLater() { 72 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; 73 } 74 isHoneycombOrLater()75 public static boolean isHoneycombOrLater() { 76 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; 77 } 78 get(Context context)79 public static QsbApplication get(Context context) { 80 return ((QsbApplicationWrapper) context.getApplicationContext()).getApp(); 81 } 82 getContext()83 protected Context getContext() { 84 return mContext; 85 } 86 getVersionCode()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 checkThread()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 close()109 protected void close() { 110 checkThread(); 111 if (mConfig != null) { 112 mConfig.close(); 113 mConfig = null; 114 } 115 if (mSuggestionsProvider != null) { 116 mSuggestionsProvider.close(); 117 mSuggestionsProvider = null; 118 } 119 } 120 getMainThreadHandler()121 public synchronized Handler getMainThreadHandler() { 122 if (mUiThreadHandler == null) { 123 mUiThreadHandler = new Handler(Looper.getMainLooper()); 124 } 125 return mUiThreadHandler; 126 } 127 runOnUiThread(Runnable action)128 public void runOnUiThread(Runnable action) { 129 getMainThreadHandler().post(action); 130 } 131 getIconLoaderExecutor()132 public synchronized NamedTaskExecutor getIconLoaderExecutor() { 133 if (mIconLoaderExecutor == null) { 134 mIconLoaderExecutor = createIconLoaderExecutor(); 135 } 136 return mIconLoaderExecutor; 137 } 138 createIconLoaderExecutor()139 protected NamedTaskExecutor createIconLoaderExecutor() { 140 ThreadFactory iconThreadFactory = new PriorityThreadFactory( 141 Process.THREAD_PRIORITY_BACKGROUND); 142 return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory)); 143 } 144 145 /** 146 * Indicates that construction of the QSB UI is now complete. 147 */ onStartupComplete()148 public void onStartupComplete() { 149 } 150 151 /** 152 * Gets the QSB configuration object. 153 * May be called from any thread. 154 */ getConfig()155 public synchronized Config getConfig() { 156 if (mConfig == null) { 157 mConfig = createConfig(); 158 } 159 return mConfig; 160 } 161 createConfig()162 protected Config createConfig() { 163 return new Config(getContext()); 164 } 165 getSettings()166 public synchronized SearchSettings getSettings() { 167 if (mSettings == null) { 168 mSettings = createSettings(); 169 mSettings.upgradeSettingsIfNeeded(); 170 } 171 return mSettings; 172 } 173 createSettings()174 protected SearchSettings createSettings() { 175 return new SearchSettingsImpl(getContext(), getConfig()); 176 } 177 createExecutorFactory(final int numThreads)178 protected Factory<Executor> createExecutorFactory(final int numThreads) { 179 final ThreadFactory threadFactory = getQueryThreadFactory(); 180 return new Factory<Executor>() { 181 @Override 182 public Executor create() { 183 return Executors.newFixedThreadPool(numThreads, threadFactory); 184 } 185 }; 186 } 187 188 /** 189 /** 190 * Gets the source task executor. 191 * May only be called from the main thread. 192 */ 193 public NamedTaskExecutor getSourceTaskExecutor() { 194 checkThread(); 195 if (mSourceTaskExecutor == null) { 196 mSourceTaskExecutor = createSourceTaskExecutor(); 197 } 198 return mSourceTaskExecutor; 199 } 200 201 protected NamedTaskExecutor createSourceTaskExecutor() { 202 ThreadFactory queryThreadFactory = getQueryThreadFactory(); 203 return new PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory)); 204 } 205 206 /** 207 * Gets the query thread factory. 208 * May only be called from the main thread. 209 */ 210 protected ThreadFactory getQueryThreadFactory() { 211 checkThread(); 212 if (mQueryThreadFactory == null) { 213 mQueryThreadFactory = createQueryThreadFactory(); 214 } 215 return mQueryThreadFactory; 216 } 217 218 protected ThreadFactory createQueryThreadFactory() { 219 String nameFormat = "QSB #%d"; 220 int priority = getConfig().getQueryThreadPriority(); 221 return new ThreadFactoryBuilder() 222 .setNameFormat(nameFormat) 223 .setThreadFactory(new PriorityThreadFactory(priority)) 224 .build(); 225 } 226 227 /** 228 * Gets the suggestion provider. 229 * 230 * May only be called from the main thread. 231 */ 232 protected SuggestionsProvider getSuggestionsProvider() { 233 checkThread(); 234 if (mSuggestionsProvider == null) { 235 mSuggestionsProvider = createSuggestionsProvider(); 236 } 237 return mSuggestionsProvider; 238 } 239 240 protected SuggestionsProvider createSuggestionsProvider() { 241 return new SuggestionsProviderImpl(getConfig(), 242 getSourceTaskExecutor(), 243 getMainThreadHandler(), 244 getLogger()); 245 } 246 247 /** 248 * Gets the default suggestion view factory. 249 * May only be called from the main thread. 250 */ 251 public SuggestionViewFactory getSuggestionViewFactory() { 252 checkThread(); 253 if (mSuggestionViewFactory == null) { 254 mSuggestionViewFactory = createSuggestionViewFactory(); 255 } 256 return mSuggestionViewFactory; 257 } 258 259 protected SuggestionViewFactory createSuggestionViewFactory() { 260 return new DefaultSuggestionViewFactory(getContext()); 261 } 262 263 /** 264 * Gets the Google source. 265 * May only be called from the main thread. 266 */ 267 public GoogleSource getGoogleSource() { 268 checkThread(); 269 if (mGoogleSource == null) { 270 mGoogleSource = createGoogleSource(); 271 } 272 return mGoogleSource; 273 } 274 275 protected GoogleSource createGoogleSource() { 276 return new GoogleSuggestClient(getContext(), getMainThreadHandler(), 277 getIconLoaderExecutor(), getConfig()); 278 } 279 280 /** 281 * Gets Voice Search utilities. 282 */ 283 public VoiceSearch getVoiceSearch() { 284 checkThread(); 285 if (mVoiceSearch == null) { 286 mVoiceSearch = createVoiceSearch(); 287 } 288 return mVoiceSearch; 289 } 290 291 protected VoiceSearch createVoiceSearch() { 292 return new VoiceSearch(getContext()); 293 } 294 295 /** 296 * Gets the event logger. 297 * May only be called from the main thread. 298 */ 299 public Logger getLogger() { 300 checkThread(); 301 if (mLogger == null) { 302 mLogger = createLogger(); 303 } 304 return mLogger; 305 } 306 307 protected Logger createLogger() { 308 return new EventLogLogger(getContext(), getConfig()); 309 } 310 311 public SuggestionFormatter getSuggestionFormatter() { 312 if (mSuggestionFormatter == null) { 313 mSuggestionFormatter = createSuggestionFormatter(); 314 } 315 return mSuggestionFormatter; 316 } 317 318 protected SuggestionFormatter createSuggestionFormatter() { 319 return new LevenshteinSuggestionFormatter(getTextAppearanceFactory()); 320 } 321 322 public TextAppearanceFactory getTextAppearanceFactory() { 323 if (mTextAppearanceFactory == null) { 324 mTextAppearanceFactory = createTextAppearanceFactory(); 325 } 326 return mTextAppearanceFactory; 327 } 328 329 protected TextAppearanceFactory createTextAppearanceFactory() { 330 return new TextAppearanceFactory(getContext()); 331 } 332 333 public synchronized HttpHelper getHttpHelper() { 334 if (mHttpHelper == null) { 335 mHttpHelper = createHttpHelper(); 336 } 337 return mHttpHelper; 338 } 339 340 protected HttpHelper createHttpHelper() { 341 return new JavaNetHttpHelper( 342 new JavaNetHttpHelper.PassThroughRewriter(), 343 getConfig().getUserAgent()); 344 } 345 346 public synchronized SearchBaseUrlHelper getSearchBaseUrlHelper() { 347 if (mSearchBaseUrlHelper == null) { 348 mSearchBaseUrlHelper = createSearchBaseUrlHelper(); 349 } 350 351 return mSearchBaseUrlHelper; 352 } 353 354 protected SearchBaseUrlHelper createSearchBaseUrlHelper() { 355 // This cast to "SearchSettingsImpl" is somewhat ugly. 356 return new SearchBaseUrlHelper(getContext(), getHttpHelper(), 357 getSettings(), ((SearchSettingsImpl)getSettings()).getSearchPreferences()); 358 } 359 360 public Help getHelp() { 361 // No point caching this, it's super cheap. 362 return new Help(getContext(), getConfig()); 363 } 364 } 365