blob: f53b481a8032e5077b44966e6b53f1f1da320102 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quicksearchbox
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.Process
import android.view.ContextThemeWrapper
import com.android.quicksearchbox.google.GoogleSource
import com.android.quicksearchbox.google.GoogleSuggestClient
import com.android.quicksearchbox.google.SearchBaseUrlHelper
import com.android.quicksearchbox.ui.DefaultSuggestionViewFactory
import com.android.quicksearchbox.ui.SuggestionViewFactory
import com.android.quicksearchbox.util.*
import com.google.common.util.concurrent.ThreadFactoryBuilder
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.ThreadFactory
class QsbApplication(context: Context?) {
private val mContext: Context?
private var mVersionCode: Long = 0
private var mUiThreadHandler: Handler? = null
private var mConfig: Config? = null
private var mSettings: SearchSettings? = null
private var mSourceTaskExecutor: NamedTaskExecutor? = null
private var mQueryThreadFactory: ThreadFactory? = null
private var mSuggestionsProvider: SuggestionsProvider? = null
private var mSuggestionViewFactory: SuggestionViewFactory? = null
private var mGoogleSource: GoogleSource? = null
private var mVoiceSearch: VoiceSearch? = null
private var mLogger: Logger? = null
private var mSuggestionFormatter: SuggestionFormatter? = null
private var mTextAppearanceFactory: TextAppearanceFactory? = null
private var mIconLoaderExecutor: NamedTaskExecutor? = null
private var mHttpHelper: HttpHelper? = null
private var mSearchBaseUrlHelper: SearchBaseUrlHelper? = null
protected val context: Context?
get() = mContext
// The current package should always exist, how else could we
// run code from it?
val versionCode: Long
@Suppress("DEPRECATION")
get() {
if (mVersionCode == 0L) {
mVersionCode =
try {
val pm: PackageManager? = context?.getPackageManager()
val pkgInfo: PackageInfo? = pm?.getPackageInfo(context!!.getPackageName(), 0)
pkgInfo!!.getLongVersionCode()
} catch (ex: PackageManager.NameNotFoundException) {
// The current package should always exist, how else could we
// run code from it?
throw RuntimeException(ex)
}
}
return mVersionCode
}
protected fun checkThread() {
if (Looper.myLooper() !== Looper.getMainLooper()) {
throw IllegalStateException(
"Accessed Application object from thread " + Thread.currentThread().getName()
)
}
}
fun close() {
checkThread()
if (mConfig != null) {
mConfig!!.close()
mConfig = null
}
if (mSuggestionsProvider != null) {
mSuggestionsProvider!!.close()
mSuggestionsProvider = null
}
}
@get:Synchronized
val mainThreadHandler: Handler?
get() {
if (mUiThreadHandler == null) {
mUiThreadHandler = Handler(Looper.getMainLooper())
}
return mUiThreadHandler
}
fun runOnUiThread(action: Runnable?) {
mainThreadHandler?.post(action!!)
}
@get:Synchronized
val iconLoaderExecutor: NamedTaskExecutor?
get() {
if (mIconLoaderExecutor == null) {
mIconLoaderExecutor = createIconLoaderExecutor()
}
return mIconLoaderExecutor
}
protected fun createIconLoaderExecutor(): NamedTaskExecutor {
val iconThreadFactory: ThreadFactory = PriorityThreadFactory(Process.THREAD_PRIORITY_BACKGROUND)
return PerNameExecutor(SingleThreadNamedTaskExecutor.factory(iconThreadFactory))
}
/** Indicates that construction of the QSB UI is now complete. */
fun onStartupComplete() {}
/** Gets the QSB configuration object. May be called from any thread. */
@get:Synchronized
val config: Config?
get() {
if (mConfig == null) {
mConfig = createConfig()
}
return mConfig
}
protected fun createConfig(): Config {
return Config(context)
}
@get:Synchronized
val settings: SearchSettings?
get() {
if (mSettings == null) {
mSettings = createSettings()
mSettings!!.upgradeSettingsIfNeeded()
}
return mSettings
}
protected fun createSettings(): SearchSettings {
return SearchSettingsImpl(context, config)
}
protected fun createExecutorFactory(numThreads: Int): Factory<Executor?> {
val threadFactory: ThreadFactory? = queryThreadFactory
return object : Factory<Executor?> {
@Override
override fun create(): Executor {
return Executors.newFixedThreadPool(numThreads, threadFactory)
}
}
}
/** Gets the source task executor. May only be called from the main thread. */
val sourceTaskExecutor: NamedTaskExecutor?
get() {
checkThread()
if (mSourceTaskExecutor == null) {
mSourceTaskExecutor = createSourceTaskExecutor()
}
return mSourceTaskExecutor
}
protected fun createSourceTaskExecutor(): NamedTaskExecutor {
val queryThreadFactory: ThreadFactory? = queryThreadFactory
return PerNameExecutor(SingleThreadNamedTaskExecutor.factory(queryThreadFactory))
}
/** Gets the query thread factory. May only be called from the main thread. */
protected val queryThreadFactory: ThreadFactory?
get() {
checkThread()
if (mQueryThreadFactory == null) {
mQueryThreadFactory = createQueryThreadFactory()
}
return mQueryThreadFactory
}
protected fun createQueryThreadFactory(): ThreadFactory {
val nameFormat = "QSB #%d"
val priority: Int = config!!.queryThreadPriority
return ThreadFactoryBuilder()
.setNameFormat(nameFormat)
.setThreadFactory(PriorityThreadFactory(priority))
.build()
}
/**
* Gets the suggestion provider.
*
* May only be called from the main thread.
*/
val suggestionsProvider: SuggestionsProvider?
get() {
checkThread()
if (mSuggestionsProvider == null) {
mSuggestionsProvider = createSuggestionsProvider()
}
return mSuggestionsProvider
}
protected fun createSuggestionsProvider(): SuggestionsProvider {
return SuggestionsProviderImpl(config!!, sourceTaskExecutor!!, mainThreadHandler, logger)
}
/** Gets the default suggestion view factory. May only be called from the main thread. */
val suggestionViewFactory: SuggestionViewFactory?
get() {
checkThread()
if (mSuggestionViewFactory == null) {
mSuggestionViewFactory = createSuggestionViewFactory()
}
return mSuggestionViewFactory
}
protected fun createSuggestionViewFactory(): SuggestionViewFactory {
return DefaultSuggestionViewFactory(context)
}
/** Gets the Google source. May only be called from the main thread. */
val googleSource: GoogleSource?
get() {
checkThread()
if (mGoogleSource == null) {
mGoogleSource = createGoogleSource()
}
return mGoogleSource
}
protected fun createGoogleSource(): GoogleSource {
return GoogleSuggestClient(context, mainThreadHandler, iconLoaderExecutor!!, config!!)
}
/** Gets Voice Search utilities. */
val voiceSearch: VoiceSearch?
get() {
checkThread()
if (mVoiceSearch == null) {
mVoiceSearch = createVoiceSearch()
}
return mVoiceSearch
}
protected fun createVoiceSearch(): VoiceSearch {
return VoiceSearch(context)
}
/** Gets the event logger. May only be called from the main thread. */
val logger: Logger?
get() {
checkThread()
if (mLogger == null) {
mLogger = createLogger()
}
return mLogger
}
protected fun createLogger(): Logger {
return EventLogLogger(context, config!!)
}
val suggestionFormatter: SuggestionFormatter?
get() {
if (mSuggestionFormatter == null) {
mSuggestionFormatter = createSuggestionFormatter()
}
return mSuggestionFormatter
}
protected fun createSuggestionFormatter(): SuggestionFormatter {
return LevenshteinSuggestionFormatter(textAppearanceFactory)
}
val textAppearanceFactory: TextAppearanceFactory?
get() {
if (mTextAppearanceFactory == null) {
mTextAppearanceFactory = createTextAppearanceFactory()
}
return mTextAppearanceFactory
}
protected fun createTextAppearanceFactory(): TextAppearanceFactory {
return TextAppearanceFactory(context)
}
@get:Synchronized
val httpHelper: HttpHelper?
get() {
if (mHttpHelper == null) {
mHttpHelper = createHttpHelper()
}
return mHttpHelper
}
protected fun createHttpHelper(): HttpHelper {
return JavaNetHttpHelper(JavaNetHttpHelper.PassThroughRewriter(), config!!.userAgent)
}
@get:Synchronized
val searchBaseUrlHelper: SearchBaseUrlHelper?
get() {
if (mSearchBaseUrlHelper == null) {
mSearchBaseUrlHelper = createSearchBaseUrlHelper()
}
return mSearchBaseUrlHelper
}
protected fun createSearchBaseUrlHelper(): SearchBaseUrlHelper {
// This cast to "SearchSettingsImpl" is somewhat ugly.
return SearchBaseUrlHelper(
context,
httpHelper!!,
settings!!,
(settings as SearchSettingsImpl?)!!.searchPreferences
)
}
// No point caching this, it's super cheap.
val help: Help
get() = // No point caching this, it's super cheap.
Help(context, config!!)
companion object {
val isFroyoOrLater: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO
val isHoneycombOrLater: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
@JvmStatic
operator fun get(context: Context?): QsbApplication {
return (context?.getApplicationContext() as QsbApplicationWrapper).app
}
}
init {
// the application context does not use the theme from the <application> tag
mContext = ContextThemeWrapper(context, R.style.Theme_QuickSearchBox)
}
}