2019-11-20 01:30:56 +01:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
package org.mozilla.fenix.components.searchengine
|
|
|
|
|
|
|
|
import android.content.Context
|
2019-12-05 02:51:15 +01:00
|
|
|
import androidx.annotation.VisibleForTesting
|
2019-11-20 01:30:56 +01:00
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import kotlinx.coroutines.runBlocking
|
|
|
|
import mozilla.components.browser.search.SearchEngine
|
|
|
|
import mozilla.components.browser.search.provider.AssetsSearchEngineProvider
|
|
|
|
import mozilla.components.browser.search.provider.SearchEngineList
|
|
|
|
import mozilla.components.browser.search.provider.SearchEngineProvider
|
|
|
|
import mozilla.components.browser.search.provider.filter.SearchEngineFilter
|
2020-01-16 22:12:54 +01:00
|
|
|
import mozilla.components.service.location.MozillaLocationService
|
|
|
|
import mozilla.components.service.location.search.RegionSearchLocalizationProvider
|
|
|
|
import org.mozilla.fenix.BuildConfig
|
|
|
|
import org.mozilla.fenix.ext.components
|
2019-11-20 01:30:56 +01:00
|
|
|
import org.mozilla.fenix.ext.settings
|
2019-12-05 00:00:14 +01:00
|
|
|
import java.util.Locale
|
2019-11-20 01:30:56 +01:00
|
|
|
|
|
|
|
@SuppressWarnings("TooManyFunctions")
|
2019-12-05 02:51:15 +01:00
|
|
|
open class FenixSearchEngineProvider(
|
2019-11-20 01:30:56 +01:00
|
|
|
private val context: Context
|
|
|
|
) : SearchEngineProvider, CoroutineScope by CoroutineScope(Job() + Dispatchers.IO) {
|
2019-12-07 02:13:50 +01:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
2020-01-16 22:12:54 +01:00
|
|
|
|
|
|
|
private val localizationProvider =
|
|
|
|
RegionSearchLocalizationProvider(
|
|
|
|
MozillaLocationService(
|
|
|
|
context,
|
|
|
|
context.components.core.client,
|
|
|
|
BuildConfig.MLS_TOKEN
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2020-02-19 01:37:36 +01:00
|
|
|
open var baseSearchEngines = async {
|
2020-01-16 22:12:54 +01:00
|
|
|
AssetsSearchEngineProvider(localizationProvider).loadSearchEngines(context)
|
2019-11-20 01:30:56 +01:00
|
|
|
}
|
|
|
|
|
2019-12-07 02:13:50 +01:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
2019-12-05 02:51:15 +01:00
|
|
|
open val bundledSearchEngines = async {
|
2019-12-05 01:34:10 +01:00
|
|
|
val defaultEngineIdentifiers = baseSearchEngines.await().list.map { it.identifier }.toSet()
|
2019-11-20 01:30:56 +01:00
|
|
|
AssetsSearchEngineProvider(
|
2020-01-16 22:12:54 +01:00
|
|
|
localizationProvider,
|
2019-11-20 01:30:56 +01:00
|
|
|
filters = listOf(object : SearchEngineFilter {
|
|
|
|
override fun filter(context: Context, searchEngine: SearchEngine): Boolean {
|
2019-12-05 01:34:10 +01:00
|
|
|
return BUNDLED_SEARCH_ENGINES.contains(searchEngine.identifier) &&
|
|
|
|
!defaultEngineIdentifiers.contains(searchEngine.identifier)
|
2019-11-20 01:30:56 +01:00
|
|
|
}
|
|
|
|
}),
|
|
|
|
additionalIdentifiers = BUNDLED_SEARCH_ENGINES
|
|
|
|
).loadSearchEngines(context)
|
|
|
|
}
|
|
|
|
|
2019-12-07 02:13:50 +01:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
2019-12-05 02:51:15 +01:00
|
|
|
open var customSearchEngines = async {
|
2019-11-20 01:30:56 +01:00
|
|
|
CustomSearchEngineProvider().loadSearchEngines(context)
|
|
|
|
}
|
|
|
|
|
|
|
|
private var loadedSearchEngines = refreshAsync()
|
|
|
|
|
|
|
|
fun getDefaultEngine(context: Context): SearchEngine {
|
|
|
|
val engines = installedSearchEngines(context)
|
|
|
|
val selectedName = context.settings().defaultSearchEngineName
|
|
|
|
|
2019-12-05 00:00:14 +01:00
|
|
|
return engines.list.find { it.name == selectedName } ?: engines.default ?: engines.list.first()
|
2019-11-20 01:30:56 +01:00
|
|
|
}
|
|
|
|
|
2019-12-05 02:51:15 +01:00
|
|
|
/**
|
|
|
|
* @return a list of all SearchEngines that are currently active. These are the engines that
|
|
|
|
* are readily available throughout the app.
|
|
|
|
*/
|
2019-11-20 01:30:56 +01:00
|
|
|
fun installedSearchEngines(context: Context): SearchEngineList = runBlocking {
|
|
|
|
val installedIdentifiers = installedSearchEngineIdentifiers(context)
|
2020-02-19 01:37:36 +01:00
|
|
|
val engineList = loadedSearchEngines.await()
|
2019-11-20 01:30:56 +01:00
|
|
|
|
|
|
|
engineList.copy(
|
|
|
|
list = engineList.list.filter {
|
|
|
|
installedIdentifiers.contains(it.identifier)
|
2019-12-05 00:00:14 +01:00
|
|
|
}.sortedBy { it.name.toLowerCase(Locale.getDefault()) },
|
2019-11-20 01:30:56 +01:00
|
|
|
default = engineList.default?.let {
|
|
|
|
if (installedIdentifiers.contains(it.identifier)) {
|
|
|
|
it
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun allSearchEngineIdentifiers() = runBlocking {
|
|
|
|
loadedSearchEngines.await().list.map { it.identifier }
|
|
|
|
}
|
|
|
|
|
|
|
|
fun uninstalledSearchEngines(context: Context): SearchEngineList = runBlocking {
|
|
|
|
val installedIdentifiers = installedSearchEngineIdentifiers(context)
|
2020-02-19 01:37:36 +01:00
|
|
|
val engineList = loadedSearchEngines.await()
|
2019-11-20 01:30:56 +01:00
|
|
|
|
|
|
|
engineList.copy(list = engineList.list.filterNot { installedIdentifiers.contains(it.identifier) })
|
|
|
|
}
|
|
|
|
|
|
|
|
override suspend fun loadSearchEngines(context: Context): SearchEngineList {
|
|
|
|
return installedSearchEngines(context)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun installSearchEngine(context: Context, searchEngine: SearchEngine) = runBlocking {
|
|
|
|
val installedIdentifiers = installedSearchEngineIdentifiers(context).toMutableSet()
|
|
|
|
installedIdentifiers.add(searchEngine.identifier)
|
|
|
|
prefs(context).edit().putStringSet(INSTALLED_ENGINES_KEY, installedIdentifiers).apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun uninstallSearchEngine(context: Context, searchEngine: SearchEngine) = runBlocking {
|
|
|
|
val isCustom = CustomSearchEngineStore.isCustomSearchEngine(context, searchEngine.identifier)
|
|
|
|
|
|
|
|
if (isCustom) {
|
|
|
|
CustomSearchEngineStore.removeSearchEngine(context, searchEngine.identifier)
|
|
|
|
} else {
|
|
|
|
val installedIdentifiers = installedSearchEngineIdentifiers(context).toMutableSet()
|
|
|
|
installedIdentifiers.remove(searchEngine.identifier)
|
|
|
|
prefs(context).edit().putStringSet(INSTALLED_ENGINES_KEY, installedIdentifiers).apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
reload()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun reload() {
|
|
|
|
launch {
|
2019-12-05 01:34:10 +01:00
|
|
|
customSearchEngines = async { CustomSearchEngineProvider().loadSearchEngines(context) }
|
2019-11-20 01:30:56 +01:00
|
|
|
loadedSearchEngines = refreshAsync()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-19 01:37:36 +01:00
|
|
|
// When we change the locale we need to update the baseSearchEngines list
|
|
|
|
private fun updateBaseSearchEngines() {
|
|
|
|
baseSearchEngines = async {
|
|
|
|
AssetsSearchEngineProvider(localizationProvider).loadSearchEngines(context)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-20 01:30:56 +01:00
|
|
|
private fun refreshAsync() = async {
|
2019-12-05 01:34:10 +01:00
|
|
|
val engineList = baseSearchEngines.await()
|
|
|
|
val bundledList = bundledSearchEngines.await().list
|
|
|
|
val customList = customSearchEngines.await().list
|
2019-11-20 01:30:56 +01:00
|
|
|
|
|
|
|
engineList.copy(list = engineList.list + bundledList + customList)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun prefs(context: Context) = context.getSharedPreferences(
|
|
|
|
PREF_FILE,
|
|
|
|
Context.MODE_PRIVATE
|
|
|
|
)
|
|
|
|
|
2019-12-07 02:13:50 +01:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
|
|
|
suspend fun installedSearchEngineIdentifiers(context: Context): Set<String> {
|
2019-11-20 01:30:56 +01:00
|
|
|
val prefs = prefs(context)
|
2020-02-19 01:37:36 +01:00
|
|
|
val installedEnginesKey = localeAwareInstalledEnginesKey()
|
|
|
|
|
|
|
|
if (installedEnginesKey != prefs.getString(CURRENT_LOCALE_KEY, "")) {
|
|
|
|
updateBaseSearchEngines()
|
|
|
|
reload()
|
|
|
|
prefs.edit().putString(CURRENT_LOCALE_KEY, installedEnginesKey).apply()
|
|
|
|
}
|
2019-11-20 01:30:56 +01:00
|
|
|
|
2020-02-19 01:37:36 +01:00
|
|
|
if (!prefs.contains(installedEnginesKey)) {
|
2019-12-05 01:34:10 +01:00
|
|
|
val defaultSet = baseSearchEngines.await()
|
2019-11-20 01:30:56 +01:00
|
|
|
.list
|
|
|
|
.map { it.identifier }
|
|
|
|
.toSet()
|
|
|
|
|
2020-02-19 01:37:36 +01:00
|
|
|
prefs.edit().putStringSet(installedEnginesKey, defaultSet).apply()
|
2019-11-20 01:30:56 +01:00
|
|
|
}
|
|
|
|
|
2020-02-19 01:37:36 +01:00
|
|
|
val installedIdentifiers = prefs(context).getStringSet(installedEnginesKey, setOf()) ?: setOf()
|
|
|
|
|
2019-12-05 01:34:10 +01:00
|
|
|
val customEngineIdentifiers = customSearchEngines.await().list.map { it.identifier }.toSet()
|
2019-12-07 02:22:37 +01:00
|
|
|
return installedIdentifiers + customEngineIdentifiers
|
2019-11-20 01:30:56 +01:00
|
|
|
}
|
|
|
|
|
2020-02-19 01:37:36 +01:00
|
|
|
private suspend fun localeAwareInstalledEnginesKey(): String {
|
|
|
|
val tag = localizationProvider.determineRegion().let {
|
|
|
|
val region = it.region?.let { region ->
|
|
|
|
if (region.isEmpty()) "" else "-$region"
|
|
|
|
}
|
|
|
|
|
|
|
|
"${it.languageTag}$region"
|
|
|
|
}
|
|
|
|
|
|
|
|
return "$INSTALLED_ENGINES_KEY-$tag"
|
|
|
|
}
|
|
|
|
|
2019-12-05 02:51:15 +01:00
|
|
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
2019-11-20 01:30:56 +01:00
|
|
|
companion object {
|
2019-12-16 18:36:49 +01:00
|
|
|
val BUNDLED_SEARCH_ENGINES = listOf("reddit", "youtube")
|
2019-12-05 02:51:15 +01:00
|
|
|
const val PREF_FILE = "fenix-search-engine-provider"
|
2019-12-07 02:13:50 +01:00
|
|
|
const val INSTALLED_ENGINES_KEY = "fenix-installed-search-engines"
|
2020-02-19 01:37:36 +01:00
|
|
|
const val CURRENT_LOCALE_KEY = "fenix-current-locale"
|
2019-11-20 01:30:56 +01:00
|
|
|
}
|
|
|
|
}
|