/* * 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 { val threadFactory: ThreadFactory? = queryThreadFactory return object : Factory { @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 tag mContext = ContextThemeWrapper(context, R.style.Theme_QuickSearchBox) } }