1 /* 2 * Copyright (C) 2022 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 package com.android.quicksearchbox 17 18 import android.content.Context 19 import android.content.pm.PackageInfo 20 import android.content.pm.PackageManager 21 import android.os.Build 22 import android.os.Handler 23 import android.os.Looper 24 import android.os.Process 25 import android.view.ContextThemeWrapper 26 import com.android.quicksearchbox.google.GoogleSource 27 import com.android.quicksearchbox.google.GoogleSuggestClient 28 import com.android.quicksearchbox.google.SearchBaseUrlHelper 29 import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory 30 import com.android.quicksearchbox.ui.SuggestionViewFactory 31 import com.android.quicksearchbox.util.* 32 import com.google.common.util.concurrent.ThreadFactoryBuilder 33 import java.util.concurrent.Executor 34 import java.util.concurrent.Executors 35 import java.util.concurrent.ThreadFactory 36 37 class QsbApplication(context: Context?) { 38 private val mContext: Context? 39 40 private var mVersionCode: Long = 0 41 private var mUiThreadHandler: Handler? = null 42 private var mConfig: Config? = null 43 private var mSettings: SearchSettings? = null 44 private var mSourceTaskExecutor: NamedTaskExecutor? = null 45 private var mQueryThreadFactory: ThreadFactory? = null 46 private var mSuggestionsProvider: SuggestionsProvider? = null 47 private var mSuggestionViewFactory: SuggestionViewFactory? = null 48 private var mGoogleSource: GoogleSource? = null 49 private var mVoiceSearch: VoiceSearch? = null 50 private var mLogger: Logger? = null 51 private var mSuggestionFormatter: SuggestionFormatter? = null 52 private var mTextAppearanceFactory: TextAppearanceFactory? = null 53 private var mIconLoaderExecutor: NamedTaskExecutor? = null 54 private var mHttpHelper: HttpHelper? = null 55 private var mSearchBaseUrlHelper: SearchBaseUrlHelper? = null 56 protected val context: Context? 57 get() = mContext 58 59 // The current package should always exist, how else could we 60 // run code from it? 61 val versionCode: Long 62 @Suppress("DEPRECATION") 63 get() { 64 if (mVersionCode == 0L) { 65 mVersionCode = 66 try { 67 val pm: PackageManager? = context?.getPackageManager() 68 val pkgInfo: PackageInfo? = pm?.getPackageInfo(context!!.getPackageName(), 0) 69 pkgInfo!!.getLongVersionCode() 70 } catch (ex: PackageManager.NameNotFoundException) { 71 // The current package should always exist, how else could we 72 // run code from it? 73 throw RuntimeException(ex) 74 } 75 } 76 return mVersionCode 77 } 78 checkThreadnull79 protected fun checkThread() { 80 if (Looper.myLooper() !== Looper.getMainLooper()) { 81 throw IllegalStateException( 82 "Accessed Application object from thread " + Thread.currentThread().getName() 83 ) 84 } 85 } 86 closenull87 fun close() { 88 checkThread() 89 if (mConfig != null) { 90 mConfig!!.close() 91 mConfig = null 92 } 93 if (mSuggestionsProvider != null) { 94 mSuggestionsProvider!!.close() 95 mSuggestionsProvider = null 96 } 97 } 98 99 @get:Synchronized 100 val mainThreadHandler: Handler? 101 get() { 102 if (mUiThreadHandler == null) { 103 mUiThreadHandler = Handler(Looper.getMainLooper()) 104 } 105 return mUiThreadHandler 106 } 107 runOnUiThreadnull108 fun runOnUiThread(action: Runnable?) { 109 mainThreadHandler?.post(action!!) 110 } 111 112 @get:Synchronized 113 val iconLoaderExecutor: NamedTaskExecutor? 114 get() { 115 if (mIconLoaderExecutor == null) { 116 mIconLoaderExecutor = createIconLoaderExecutor() 117 } 118 return mIconLoaderExecutor 119 } 120 createIconLoaderExecutornull121 protected fun createIconLoaderExecutor(): NamedTaskExecutor { 122 val iconThreadFactory: ThreadFactory = PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND) 123 return PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory)) 124 } 125 126 /** Indicates that construction of the QSB UI is now complete. */ onStartupCompletenull127 fun onStartupComplete() {} 128 129 /** Gets the QSB configuration object. May be called from any thread. */ 130 @get:Synchronized 131 val config: Config? 132 get() { 133 if (mConfig == null) { 134 mConfig = createConfig() 135 } 136 return mConfig 137 } 138 createConfignull139 protected fun createConfig(): Config { 140 return Config(context) 141 } 142 143 @get:Synchronized 144 val settings: SearchSettings? 145 get() { 146 if (mSettings == null) { 147 mSettings = createSettings() 148 mSettings!!.upgradeSettingsIfNeeded() 149 } 150 return mSettings 151 } 152 createSettingsnull153 protected fun createSettings(): SearchSettings { 154 return SearchSettingsImpl(context, config) 155 } 156 createExecutorFactorynull157 protected fun createExecutorFactory(numThreads: Int): Factory<Executor?> { 158 val threadFactory: ThreadFactory? = queryThreadFactory 159 return object : Factory<Executor?> { 160 @Override 161 override fun create(): Executor { 162 return Executors.newFixedThreadPool(numThreads, threadFactory) 163 } 164 } 165 } 166 167 /** Gets the source task executor. May only be called from the main thread. */ 168 val sourceTaskExecutor: NamedTaskExecutor? 169 get() { 170 checkThread() 171 if (mSourceTaskExecutor == null) { 172 mSourceTaskExecutor = createSourceTaskExecutor() 173 } 174 return mSourceTaskExecutor 175 } 176 createSourceTaskExecutornull177 protected fun createSourceTaskExecutor(): NamedTaskExecutor { 178 val queryThreadFactory: ThreadFactory? = queryThreadFactory 179 return PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory)) 180 } 181 182 /** Gets the query thread factory. May only be called from the main thread. */ 183 protected val queryThreadFactory: ThreadFactory? 184 get() { 185 checkThread() 186 if (mQueryThreadFactory == null) { 187 mQueryThreadFactory = createQueryThreadFactory() 188 } 189 return mQueryThreadFactory 190 } 191 createQueryThreadFactorynull192 protected fun createQueryThreadFactory(): ThreadFactory { 193 val nameFormat = "QSB #%d" 194 val priority: Int = config!!.queryThreadPriority 195 return ThreadFactoryBuilder() 196 .setNameFormat(nameFormat) 197 .setThreadFactory(PriorityThreadFactory(priority)) 198 .build() 199 } 200 201 /** 202 * Gets the suggestion provider. 203 * 204 * May only be called from the main thread. 205 */ 206 val suggestionsProvider: SuggestionsProvider? 207 get() { 208 checkThread() 209 if (mSuggestionsProvider == null) { 210 mSuggestionsProvider = createSuggestionsProvider() 211 } 212 return mSuggestionsProvider 213 } 214 createSuggestionsProvidernull215 protected fun createSuggestionsProvider(): SuggestionsProvider { 216 return SuggestionsProviderImpl(config!!, sourceTaskExecutor!!, mainThreadHandler, logger) 217 } 218 219 /** Gets the default suggestion view factory. May only be called from the main thread. */ 220 val suggestionViewFactory: SuggestionViewFactory? 221 get() { 222 checkThread() 223 if (mSuggestionViewFactory == null) { 224 mSuggestionViewFactory = createSuggestionViewFactory() 225 } 226 return mSuggestionViewFactory 227 } 228 createSuggestionViewFactorynull229 protected fun createSuggestionViewFactory(): SuggestionViewFactory { 230 return DefaultSuggestionViewFactory(context) 231 } 232 233 /** Gets the Google source. May only be called from the main thread. */ 234 val googleSource: GoogleSource? 235 get() { 236 checkThread() 237 if (mGoogleSource == null) { 238 mGoogleSource = createGoogleSource() 239 } 240 return mGoogleSource 241 } 242 createGoogleSourcenull243 protected fun createGoogleSource(): GoogleSource { 244 return GoogleSuggestClient(context, mainThreadHandler, iconLoaderExecutor!!, config!!) 245 } 246 247 /** Gets Voice Search utilities. */ 248 val voiceSearch: VoiceSearch? 249 get() { 250 checkThread() 251 if (mVoiceSearch == null) { 252 mVoiceSearch = createVoiceSearch() 253 } 254 return mVoiceSearch 255 } 256 createVoiceSearchnull257 protected fun createVoiceSearch(): VoiceSearch { 258 return VoiceSearch(context) 259 } 260 261 /** Gets the event logger. May only be called from the main thread. */ 262 val logger: Logger? 263 get() { 264 checkThread() 265 if (mLogger == null) { 266 mLogger = createLogger() 267 } 268 return mLogger 269 } 270 createLoggernull271 protected fun createLogger(): Logger { 272 return EventLogLogger(context, config!!) 273 } 274 275 val suggestionFormatter: SuggestionFormatter? 276 get() { 277 if (mSuggestionFormatter == null) { 278 mSuggestionFormatter = createSuggestionFormatter() 279 } 280 return mSuggestionFormatter 281 } 282 createSuggestionFormatternull283 protected fun createSuggestionFormatter(): SuggestionFormatter { 284 return LevenshteinSuggestionFormatter(textAppearanceFactory) 285 } 286 287 val textAppearanceFactory: TextAppearanceFactory? 288 get() { 289 if (mTextAppearanceFactory == null) { 290 mTextAppearanceFactory = createTextAppearanceFactory() 291 } 292 return mTextAppearanceFactory 293 } 294 createTextAppearanceFactorynull295 protected fun createTextAppearanceFactory(): TextAppearanceFactory { 296 return TextAppearanceFactory(context) 297 } 298 299 @get:Synchronized 300 val httpHelper: HttpHelper? 301 get() { 302 if (mHttpHelper == null) { 303 mHttpHelper = createHttpHelper() 304 } 305 return mHttpHelper 306 } 307 createHttpHelpernull308 protected fun createHttpHelper(): HttpHelper { 309 return JavaNetHttpHelper(JavaNetHttpHelper.PassThroughRewriter(), config!!.userAgent) 310 } 311 312 @get:Synchronized 313 val searchBaseUrlHelper: SearchBaseUrlHelper? 314 get() { 315 if (mSearchBaseUrlHelper == null) { 316 mSearchBaseUrlHelper = createSearchBaseUrlHelper() 317 } 318 return mSearchBaseUrlHelper 319 } 320 createSearchBaseUrlHelpernull321 protected fun createSearchBaseUrlHelper(): SearchBaseUrlHelper { 322 // This cast to "SearchSettingsImpl" is somewhat ugly. 323 return SearchBaseUrlHelper( 324 context, 325 httpHelper!!, 326 settings!!, 327 (settings as SearchSettingsImpl?)!!.searchPreferences 328 ) 329 } 330 331 // No point caching this, it's super cheap. 332 val help: Help 333 get() = // No point caching this, it's super cheap. 334 Help(context, config!!) 335 336 companion object { 337 val isFroyoOrLater: Boolean 338 get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO 339 val isHoneycombOrLater: Boolean 340 get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB 341 342 @JvmStatic getnull343 operator fun get(context: Context?): QsbApplication { 344 return (context?.getApplicationContext() as QsbApplicationWrapper).app 345 } 346 } 347 348 init { 349 // the application context does not use the theme from the <application> tag 350 mContext = ContextThemeWrapper(context, R.style.Theme_QuickSearchBox) 351 } 352 } 353