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