Skip to content
This repository was archived by the owner on Nov 1, 2022. It is now read-only.

Commit 9fd678e

Browse files
committed
Issue #69: Add additional functionality for integration into Focus.
1 parent a671db7 commit 9fd678e

File tree

7 files changed

+104
-41
lines changed

7 files changed

+104
-41
lines changed

components/browser/search/src/main/java/mozilla/components/browser/search/SearchEngine.kt

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,15 @@ class SearchEngine internal constructor(
4444
var result = template
4545
val locale = Locale.getDefault().toString()
4646

47-
result = result.replace(MOZ_PARAM_LOCALE.toRegex(), locale)
48-
result = result.replace(MOZ_PARAM_DIST_ID.toRegex(), "")
49-
result = result.replace(MOZ_PARAM_OFFICIAL.toRegex(), "unofficial")
47+
result = result.replace(MOZ_PARAM_LOCALE, locale)
48+
result = result.replace(MOZ_PARAM_DIST_ID, "")
49+
result = result.replace(MOZ_PARAM_OFFICIAL, "unofficial")
5050

51-
result = result.replace(OS_PARAM_USER_DEFINED.toRegex(), query)
52-
result = result.replace(OS_PARAM_INPUT_ENCODING.toRegex(), "UTF-8")
51+
result = result.replace(OS_PARAM_USER_DEFINED, query)
52+
result = result.replace(OS_PARAM_INPUT_ENCODING, "UTF-8")
5353

54-
result = result.replace(OS_PARAM_LANGUAGE.toRegex(), locale)
55-
result = result.replace(OS_PARAM_OUTPUT_ENCODING.toRegex(), "UTF-8")
54+
result = result.replace(OS_PARAM_LANGUAGE, locale)
55+
result = result.replace(OS_PARAM_OUTPUT_ENCODING, "UTF-8")
5656

5757
// Replace any optional parameters
5858
result = result.replace(OS_PARAM_OPTIONAL.toRegex(), "")
@@ -61,18 +61,23 @@ class SearchEngine internal constructor(
6161
}
6262

6363
companion object {
64+
// We are using string concatenation here to avoid the Kotlin compiler interpreting this
65+
// as string templates. It is possible to escape the string accordingly. But this seems to
66+
// be inconsistent between Kotlin versions. So to be safe we avoid this completely by
67+
// constructing the strings manually.
68+
6469
// Parameters copied from nsSearchService.js
65-
private const val MOZ_PARAM_LOCALE = "\\{moz:locale}"
66-
private const val MOZ_PARAM_DIST_ID = "\\{moz:distributionID}"
67-
private const val MOZ_PARAM_OFFICIAL = "\\{moz:official}"
70+
private const val MOZ_PARAM_LOCALE = "{" + "moz:locale" + "}"
71+
private const val MOZ_PARAM_DIST_ID = "{" + "moz:distributionID" + "}"
72+
private const val MOZ_PARAM_OFFICIAL = "{" + "moz:official" + "}"
6873

6974
// Supported OpenSearch parameters
7075
// See http://opensearch.a9.com/spec/1.1/querysyntax/#core
71-
private const val OS_PARAM_USER_DEFINED = "\\{searchTerms\\??}"
72-
private const val OS_PARAM_INPUT_ENCODING = "\\{inputEncoding\\??}"
73-
private const val OS_PARAM_LANGUAGE = "\\{language\\??}"
74-
private const val OS_PARAM_OUTPUT_ENCODING = "\\{outputEncoding\\??}"
75-
private const val OS_PARAM_OPTIONAL = "\\{(?:\\w+:)?\\w+?}"
76+
private const val OS_PARAM_USER_DEFINED = "{" + "searchTerms" + "}"
77+
private const val OS_PARAM_INPUT_ENCODING = "{" + "inputEncoding" + "}"
78+
private const val OS_PARAM_LANGUAGE = "{" + "language" + "}"
79+
private const val OS_PARAM_OUTPUT_ENCODING = "{" + "outputEncoding" + "}"
80+
private const val OS_PARAM_OPTIONAL = "\\{" + "(?:\\w+:)?\\w+?" + "\\}"
7681

7782
private fun normalize(input: String): String {
7883
val trimmedInput = input.trim { it <= ' ' }

components/browser/search/src/main/java/mozilla/components/browser/search/SearchEngineManager.kt

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import kotlinx.coroutines.experimental.CommonPool
1212
import kotlinx.coroutines.experimental.Deferred
1313
import kotlinx.coroutines.experimental.async
1414
import kotlinx.coroutines.experimental.launch
15+
import kotlinx.coroutines.experimental.runBlocking
1516
import mozilla.components.browser.search.provider.AssetsSearchEngineProvider
1617
import mozilla.components.browser.search.provider.SearchEngineProvider
1718
import mozilla.components.browser.search.provider.localization.LocaleSearchLocalizationProvider
@@ -40,10 +41,10 @@ class SearchEngineManager(
4041
* will perform a load.
4142
*/
4243
@Synchronized
43-
suspend fun getSearchEngines(context: Context): List<SearchEngine> {
44-
deferredSearchEngines?.let { return it.await() }
44+
fun getSearchEngines(context: Context): List<SearchEngine> {
45+
deferredSearchEngines?.let { return runBlocking { it.await() } }
4546

46-
return load(context).await()
47+
return runBlocking { load(context).await() }
4748
}
4849

4950
/**
@@ -52,16 +53,16 @@ class SearchEngineManager(
5253
* The default engine is the first engine loaded by the first provider passed to the
5354
* constructor of SearchEngineManager.
5455
*
55-
* Optionally an identifier can be passed to this method (e.g. from the user's preferences). If
56+
* Optionally a name can be passed to this method (e.g. from the user's preferences). If
5657
* a matching search engine was loaded then this search engine will be returned instead.
5758
*/
5859
@Synchronized
59-
suspend fun getDefaultSearchEngine(context: Context, identifier: String = EMPTY): SearchEngine {
60+
fun getDefaultSearchEngine(context: Context, name: String = EMPTY): SearchEngine {
6061
val searchEngines = getSearchEngines(context)
6162

62-
return when (identifier) {
63+
return when (name) {
6364
EMPTY -> searchEngines[0]
64-
else -> searchEngines.find { it.identifier == identifier } ?: searchEngines[0]
65+
else -> searchEngines.find { it.name == name } ?: searchEngines[0]
6566
}
6667
}
6768

components/browser/search/src/main/java/mozilla/components/browser/search/SearchEngineParser.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import java.nio.charset.StandardCharsets
2222
/**
2323
* A very simple parser for search plugins.
2424
*/
25-
internal class SearchEngineParser {
25+
class SearchEngineParser {
2626

2727
private class SearchEngineBuilder(
2828
private val identifier: String
@@ -41,6 +41,10 @@ internal class SearchEngineParser {
4141
)
4242
}
4343

44+
/**
45+
* Loads a <code>SearchEngine</code> from the given <code>path</code> in assets and assigns
46+
* it the given <code>identifier</code>.
47+
*/
4448
@Throws(IOException::class)
4549
fun load(assetManager: AssetManager, identifier: String, path: String): SearchEngine {
4650
try {
@@ -50,8 +54,12 @@ internal class SearchEngineParser {
5054
}
5155
}
5256

57+
/**
58+
* Loads a <code>SearchEngine</code> from the given <code>stream</code> and assigns it the given
59+
* <code>identifier</code>.
60+
*/
5361
@Throws(IOException::class, XmlPullParserException::class)
54-
internal fun load(identifier: String, stream: InputStream): SearchEngine {
62+
fun load(identifier: String, stream: InputStream): SearchEngine {
5563
val builder = SearchEngineBuilder(identifier)
5664

5765
val parser = XmlPullParserFactory.newInstance().newPullParser()

components/browser/search/src/main/java/mozilla/components/browser/search/provider/AssetsSearchEngineProvider.kt

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,32 @@ import org.json.JSONObject
1818

1919
/**
2020
* SearchEngineProvider implementation to load the included search engines from assets.
21+
*
22+
* A SearchLocalizationProvider implementation is used to customize the returned search engines for
23+
* the language and country of the user/device.
24+
*
25+
* Optionally SearchEngineFilter instances can be provided to remove unwanted search engines from
26+
* the loaded list.
27+
*
28+
* Optionally <code>additionalIdentifiers</code> to be loaded can be specified. A search engine
29+
* identifier corresponds to the search plugin XML file name (e.g. duckduckgo -> duckduckgo.xml).
2130
*/
2231
class AssetsSearchEngineProvider(
2332
private val localizationProvider: SearchLocalizationProvider,
24-
private val filters: List<SearchEngineFilter> = emptyList()
33+
private val filters: List<SearchEngineFilter> = emptyList(),
34+
private val additionalIdentifiers: List<String> = emptyList()
2535
) : SearchEngineProvider {
2636

2737
/**
2838
* Load search engines from this provider.
2939
*/
3040
override suspend fun loadSearchEngines(context: Context): List<SearchEngine> {
31-
val searchEngineIdentifiers = loadAndFilterConfiguration(context)
32-
return loadSearchEnginesFromList(context, searchEngineIdentifiers)
41+
val searchEngineIdentifiers = mutableListOf<String>().apply {
42+
addAll(loadAndFilterConfiguration(context))
43+
addAll(additionalIdentifiers)
44+
}
45+
46+
return loadSearchEnginesFromList(context, searchEngineIdentifiers.distinct())
3347
}
3448

3549
private suspend fun loadSearchEnginesFromList(
@@ -51,17 +65,17 @@ class AssetsSearchEngineProvider(
5165

5266
deferredSearchEngines.forEach {
5367
val searchEngine = it.await()
54-
if (shouldBeFiltered(searchEngine)) {
68+
if (shouldBeFiltered(context, searchEngine)) {
5569
searchEngines.add(searchEngine)
5670
}
5771
}
5872

5973
return searchEngines
6074
}
6175

62-
private fun shouldBeFiltered(searchEngine: SearchEngine): Boolean {
76+
private fun shouldBeFiltered(context: Context, searchEngine: SearchEngine): Boolean {
6377
filters.forEach {
64-
if (!it.filter(searchEngine)) {
78+
if (!it.filter(context, searchEngine)) {
6579
return false
6680
}
6781
}
@@ -73,9 +87,7 @@ class AssetsSearchEngineProvider(
7387
assets: AssetManager,
7488
parser: SearchEngineParser,
7589
identifier: String
76-
): SearchEngine {
77-
return parser.load(assets, identifier, "searchplugins/$identifier.xml")
78-
}
90+
): SearchEngine = parser.load(assets, identifier, "searchplugins/$identifier.xml")
7991

8092
private fun loadAndFilterConfiguration(context: Context): List<String> {
8193
val config = context.assets.readJSONObject("search/list.json")

components/browser/search/src/main/java/mozilla/components/browser/search/provider/filter/SearchEngineFilter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package mozilla.components.browser.search.provider.filter
66

7+
import android.content.Context
78
import mozilla.components.browser.search.SearchEngine
89

910
/**
@@ -15,5 +16,5 @@ interface SearchEngineFilter {
1516
* Returns true if the given search engine should be returned by the provider or false if this
1617
* search engine should be ignored.
1718
*/
18-
fun filter(searchEngine: SearchEngine): Boolean
19+
fun filter(context: Context, searchEngine: SearchEngine): Boolean
1920
}

components/browser/search/src/test/java/mozilla/components/browser/search/SearchEngineManagerTest.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,16 @@ class SearchEngineManagerTest {
8181
fun `manager returns default engine with identifier if it exists`() {
8282
runBlocking {
8383
val provider = mockProvider(listOf(
84-
mockSearchEngine("mozsearch"),
85-
mockSearchEngine("google"),
86-
mockSearchEngine("bing")))
84+
mockSearchEngine("mozsearch", "Mozilla Search"),
85+
mockSearchEngine("google", "Google Search"),
86+
mockSearchEngine("bing", "Bing Search")))
8787

8888
val manager = SearchEngineManager(listOf(provider))
8989

90-
val default = manager.getDefaultSearchEngine(RuntimeEnvironment.application, "bing")
90+
val default = manager.getDefaultSearchEngine(
91+
RuntimeEnvironment.application,
92+
"Bing Search")
93+
9194
assertEquals("bing", default.identifier)
9295
}
9396
}
@@ -150,12 +153,15 @@ class SearchEngineManagerTest {
150153
}
151154
}
152155

153-
private fun mockSearchEngine(identifier: String): SearchEngine {
156+
private fun mockSearchEngine(
157+
identifier: String,
158+
name: String = UUID.randomUUID().toString()
159+
): SearchEngine {
154160
val uri = Uri.parse("https://${UUID.randomUUID()}.example.org")
155161

156162
return SearchEngine(
157163
identifier,
158-
UUID.randomUUID().toString(),
164+
name,
159165
mock(Bitmap::class.java),
160166
listOf(uri))
161167
}

components/browser/search/src/test/java/mozilla/components/browser/search/provider/AssetsSearchEngineProviderTest.kt

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package mozilla.components.browser.search.provider
66

7+
import android.content.Context
78
import kotlinx.coroutines.experimental.runBlocking
89
import mozilla.components.browser.search.SearchEngine
910
import mozilla.components.browser.search.provider.filter.SearchEngineFilter
@@ -42,7 +43,7 @@ class AssetsSearchEngineProviderTest {
4243
val filter = object : SearchEngineFilter {
4344
private val exclude = listOf("yahoo", "bing", "ddg")
4445

45-
override fun filter(searchEngine: SearchEngine): Boolean {
46+
override fun filter(cotext: Context, searchEngine: SearchEngine): Boolean {
4647
return !exclude.contains(searchEngine.identifier)
4748
}
4849
}
@@ -134,6 +135,35 @@ class AssetsSearchEngineProviderTest {
134135
assertEquals(6, searchEngines.size)
135136
}
136137

138+
@Test
139+
fun `provider loads additional identifiers`() {
140+
val usProvider = object : SearchLocalizationProvider() {
141+
override val country: String = "US"
142+
override val language = "en"
143+
override val region: String? = null
144+
}
145+
146+
// Loading "en-US" without additional identifiers
147+
runBlocking {
148+
val provider = AssetsSearchEngineProvider(usProvider)
149+
val searchEngines = provider.loadSearchEngines(RuntimeEnvironment.application)
150+
151+
assertEquals(6, searchEngines.size)
152+
assertContainsNotSearchEngine("duckduckgo", searchEngines)
153+
}
154+
155+
// Load "en-US" with "duckduckgo" added
156+
runBlocking {
157+
val provider = AssetsSearchEngineProvider(
158+
usProvider,
159+
additionalIdentifiers = listOf("duckduckgo"))
160+
val searchEngines = provider.loadSearchEngines(RuntimeEnvironment.application)
161+
162+
assertEquals(7, searchEngines.size)
163+
assertContainsSearchEngine("duckduckgo", searchEngines)
164+
}
165+
}
166+
137167
private fun assertContainsSearchEngine(identifier: String, searchEngines: List<SearchEngine>) {
138168
searchEngines.forEach {
139169
if (identifier == it.identifier) {

0 commit comments

Comments
 (0)