summaryrefslogtreecommitdiffstats
path: root/mobile/android/android-components/components/feature/fxsuggest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:34:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:34:42 +0000
commitda4c7e7ed675c3bf405668739c3012d140856109 (patch)
treecdd868dba063fecba609a1d819de271f0d51b23e /mobile/android/android-components/components/feature/fxsuggest
parentAdding upstream version 125.0.3. (diff)
downloadfirefox-da4c7e7ed675c3bf405668739c3012d140856109.tar.xz
firefox-da4c7e7ed675c3bf405668739c3012d140856109.zip
Adding upstream version 126.0.upstream/126.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/android-components/components/feature/fxsuggest')
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/README.md49
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/build.gradle87
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/fxsuggest.fml.yaml36
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/proguard-rules.pro21
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/AndroidManifest.xml4
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionScheduler.kt68
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorker.kt40
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestStorage.kt119
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProvider.kt185
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/GlobalFxSuggestDependencyProvider.kt30
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFacts.kt89
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFactsMiddleware.kt84
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-am/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-azb/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-be/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bg/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-br/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bs/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ca/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cak/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-co/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cs/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cy/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-da/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-de/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-dsb/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-el/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rCA/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rGB/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eo/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rAR/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rCL/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rES/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rMX/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-et/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eu/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fi/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fr/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fur/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fy-rNL/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gl/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gn/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hr/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hsb/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hu/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hy-rAM/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ia/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-in/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-is/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-it/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-iw/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ja/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ka/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kab/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kk/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kmr/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ko/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-lo/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nb-rNO/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nl/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nn-rNO/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-oc/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rIN/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rPK/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pl/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rBR/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rPT/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-rm/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ru/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sat/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sc/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-si/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sk/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-skr/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sl/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sq/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sr/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-su/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sv-rSE/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tg/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-th/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tr/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ug/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-uk/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-vi/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rCN/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rTW/strings.xml5
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/main/res/values/strings.xml8
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsMiddlewareTest.kt914
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsTest.kt191
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionSchedulerTest.kt80
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorkerTest.kt61
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProviderTest.kt394
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker2
-rw-r--r--mobile/android/android-components/components/feature/fxsuggest/src/test/resources/robolectric.properties1
96 files changed, 2843 insertions, 0 deletions
diff --git a/mobile/android/android-components/components/feature/fxsuggest/README.md b/mobile/android/android-components/components/feature/fxsuggest/README.md
new file mode 100644
index 0000000000..1c1099dd6f
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/README.md
@@ -0,0 +1,49 @@
+# [Android Components](../../../README.md) > Feature > Firefox Suggest
+
+A component for accessing Firefox Suggest search suggestions.
+
+[Firefox Suggest](https://support.mozilla.org/en-US/kb/firefox-suggest-faq) provides suggestions for sponsored and web content in the address bar. Suggestions are downloaded, stored, and matched on-device.
+
+## Usage
+
+### Setting up the dependency
+
+Use Gradle to download the library from [maven.mozilla.org](https://maven.mozilla.org/) ([Setup repository](../../../README.md#maven-repository)):
+
+```Groovy
+implementation "org.mozilla.components:feature-fxsuggest:{latest-version}"
+```
+
+## Facts
+
+This component emits the following [Facts](../../support/base/README.md#Facts):
+
+| Action | Item | Extras | Description |
+|---------------|----------------------------------|-------------------------------|---------------------------------------------------------------------------------------------------------------|
+| `INTERACTION` | `amp_suggestion_clicked` | `suggestion_clicked_extras` | The user clicked on a Firefox Suggestion from adMarketplace. |
+| `DISPLAY` | `amp_suggestion_impressed` | `suggestion_impressed_extras` | A Firefox Suggestion from adMarketplace was visible when the user finished interacting with the awesomebar. |
+| `INTERACTION` | `wikipedia_suggestion_clicked` | `suggestion_clicked_extras` | The user clicked on a Firefox Suggestion for a Wikipedia page. |
+| `DISPLAY` | `wikipedia_suggestion_impressed` | `suggestion_impressed_extras` | A Firefox Suggestion for a Wikipedia page was visible when the user finished interacting with the awesomebar. |
+
+#### `suggestion_clicked_extras`
+
+| Key | Type | Value |
+|--------------------|----------------------------|------------------------------------------------------------|
+| `interaction_info` | `FxSuggestInteractionInfo` | Type-specific information to record for this suggestion. |
+| `position` | `Long` | The 1-based position of this suggestion in the awesomebar. |
+
+
+#### `suggestion_impressed_extras`
+
+| Key | Type | Value |
+|------------------------|----------------------------|----------------------------------------------------------------------------------------------------------------|
+| `interaction_info` | `FxSuggestInteractionInfo` | Type-specific information to record for this suggestion. |
+| `position` | `Long` | The 1-based position of this suggestion in the awesomebar. |
+| `is_clicked` | `Boolean` | Whether the user clicked on this suggestion after it was shown. |
+| `engagement_abandoned` | `Boolean` | Whether the user dismissed the awesomebar without navigating to a destination after this suggestion was shown. |
+
+## License
+
+ 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/
diff --git a/mobile/android/android-components/components/feature/fxsuggest/build.gradle b/mobile/android/android-components/components/feature/fxsuggest/build.gradle
new file mode 100644
index 0000000000..2acd8a99c7
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/build.gradle
@@ -0,0 +1,87 @@
+/* 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/. */
+
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+buildscript {
+ repositories {
+ maven {
+ url "https://maven.mozilla.org/maven2"
+ }
+ }
+
+ dependencies {
+ classpath "${ApplicationServicesConfig.groupId}:tooling-nimbus-gradle:${ApplicationServicesConfig.version}"
+ }
+}
+
+plugins {
+ id "com.jetbrains.python.envs" version "$python_envs_plugin"
+}
+
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+
+android {
+ defaultConfig {
+ minSdkVersion config.minSdkVersion
+ compileSdk config.compileSdkVersion
+ targetSdkVersion config.targetSdkVersion
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ namespace 'mozilla.components.feature.fxsuggest'
+}
+
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions.freeCompilerArgs += '-opt-in=kotlin.ExperimentalUnsignedTypes'
+}
+
+dependencies {
+ api ComponentsDependencies.mozilla_appservices_suggest
+
+ implementation project(':browser-state')
+ implementation project(':concept-awesomebar')
+ implementation project(':concept-engine')
+ implementation project(':feature-session')
+ implementation project(':service-nimbus')
+ implementation project(':support-base')
+ implementation project(':support-ktx')
+
+ implementation ComponentsDependencies.androidx_work_runtime
+ implementation ComponentsDependencies.kotlin_coroutines
+
+ testImplementation project(':support-test')
+
+ testImplementation ComponentsDependencies.androidx_test_core
+ testImplementation ComponentsDependencies.androidx_test_junit
+ testImplementation ComponentsDependencies.androidx_work_testing
+ testImplementation ComponentsDependencies.mozilla_appservices_full_megazord_forUnitTests
+ testImplementation ComponentsDependencies.testing_coroutines
+ testImplementation ComponentsDependencies.testing_robolectric
+}
+
+apply from: '../../../android-lint.gradle'
+apply from: '../../../publish.gradle'
+apply plugin: "org.mozilla.appservices.nimbus-gradle-plugin"
+nimbus {
+ // The path to the Nimbus feature manifest file
+ manifestFile = "fxsuggest.fml.yaml"
+
+ channels = [
+ debug: "debug",
+ release: "release",
+ ]
+
+ applicationServicesDir = gradle.hasProperty('localProperties.autoPublish.application-services.dir')
+ ? gradle.getProperty('localProperties.autoPublish.application-services.dir') : null
+}
+ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description)
diff --git a/mobile/android/android-components/components/feature/fxsuggest/fxsuggest.fml.yaml b/mobile/android/android-components/components/feature/fxsuggest/fxsuggest.fml.yaml
new file mode 100644
index 0000000000..e55a8e78eb
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/fxsuggest.fml.yaml
@@ -0,0 +1,36 @@
+# 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/.
+
+about:
+ description: Nimbus Feature Manifest for the Firefox Suggest feature.
+ android:
+ package: mozilla.components.feature.fxsuggest
+ class: .FxSuggestNimbus
+channels:
+ - debug
+ - release
+features:
+ awesomebar-suggestion-provider:
+ description: Configuration for the Firefox Suggest awesomebar suggestion provider.
+ variables:
+ available-suggestion-types:
+ description: >
+ A map of suggestion types to booleans that indicate whether or not the provider should
+ return suggestions of those types.
+ type: Map<SuggestionType, Boolean>
+ default: {
+ "amp": false,
+ "ampMobile": false,
+ "wikipedia": true,
+ }
+enums:
+ SuggestionType:
+ description: The type of a Firefox Suggest search suggestion.
+ variants:
+ amp:
+ description: A Firefox Suggestion from adMarketplace.
+ ampMobile:
+ description: A firefox Suggestion from adMarketplace specifically for mobile.
+ wikipedia:
+ description: A Firefox Suggestion for a Wikipedia page.
diff --git a/mobile/android/android-components/components/feature/fxsuggest/proguard-rules.pro b/mobile/android/android-components/components/feature/fxsuggest/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/AndroidManifest.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..e16cda1d34
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<!-- 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/. -->
+<manifest />
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionScheduler.kt b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionScheduler.kt
new file mode 100644
index 0000000000..c32ed771b5
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionScheduler.kt
@@ -0,0 +1,68 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import android.content.Context
+import androidx.work.Constraints
+import androidx.work.ExistingPeriodicWorkPolicy
+import androidx.work.NetworkType
+import androidx.work.PeriodicWorkRequest
+import androidx.work.PeriodicWorkRequestBuilder
+import androidx.work.WorkManager
+import mozilla.components.support.base.log.logger.Logger
+import mozilla.components.support.base.worker.Frequency
+import java.util.concurrent.TimeUnit
+
+/**
+ * Schedules a periodic background task to incrementally download and persist new Firefox Suggest
+ * search suggestions.
+ *
+ * @property context The Android application context.
+ * @property frequency The optional interval period for the background task. Defaults to 1 day.
+ */
+class FxSuggestIngestionScheduler(
+ private val context: Context,
+ private val frequency: Frequency = Frequency(repeatInterval = 1, repeatIntervalTimeUnit = TimeUnit.DAYS),
+) {
+ private val logger = Logger("FxSuggestIngestionScheduler")
+
+ /**
+ * Schedules a periodic background task to ingest new suggestions. Does nothing if the task is
+ * already scheduled.
+ */
+ fun startPeriodicIngestion() {
+ logger.info("Scheduling periodic ingestion for new suggestions")
+ WorkManager.getInstance(context).enqueueUniquePeriodicWork(
+ FxSuggestIngestionWorker.WORK_TAG,
+ ExistingPeriodicWorkPolicy.KEEP,
+ createPeriodicIngestionWorkerRequest(),
+ )
+ }
+
+ /**
+ * Cancels a scheduled background task to ingest new suggestions.
+ */
+ fun stopPeriodicIngestion() {
+ logger.info("Canceling periodic ingestion for new suggestions")
+ WorkManager.getInstance(context).cancelAllWorkByTag(FxSuggestIngestionWorker.WORK_TAG)
+ }
+
+ internal fun createPeriodicIngestionWorkerRequest(): PeriodicWorkRequest {
+ val constraints = getWorkerConstrains()
+ return PeriodicWorkRequestBuilder<FxSuggestIngestionWorker>(
+ this.frequency.repeatInterval,
+ this.frequency.repeatIntervalTimeUnit,
+ ).apply {
+ setConstraints(constraints)
+ addTag(FxSuggestIngestionWorker.WORK_TAG)
+ }.build()
+ }
+
+ internal fun getWorkerConstrains() = Constraints.Builder()
+ .setRequiredNetworkType(NetworkType.UNMETERED)
+ .setRequiresBatteryNotLow(true)
+ .setRequiresStorageNotLow(true)
+ .build()
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorker.kt b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorker.kt
new file mode 100644
index 0000000000..b30f8b0f4b
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorker.kt
@@ -0,0 +1,40 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.WorkerParameters
+import mozilla.components.support.base.log.logger.Logger
+
+/**
+ * A [CoroutineWorker] that downloads and persists new Firefox Suggest search suggestions.
+ *
+ * @param context The Android application context.
+ * @param params Parameters for this worker's internal state.
+ */
+internal class FxSuggestIngestionWorker(
+ context: Context,
+ params: WorkerParameters,
+) : CoroutineWorker(context, params) {
+ private val logger = Logger("FxSuggestIngestionWorker")
+
+ override suspend fun doWork(): Result {
+ logger.info("Ingesting new suggestions")
+ val storage = GlobalFxSuggestDependencyProvider.requireStorage()
+ val success = storage.ingest()
+ return if (success) {
+ logger.info("Successfully ingested new suggestions")
+ Result.success()
+ } else {
+ logger.error("Failed to ingest new suggestions")
+ Result.retry()
+ }
+ }
+
+ internal companion object {
+ const val WORK_TAG = "mozilla.components.feature.fxsuggest.ingest.work.tag"
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestStorage.kt b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestStorage.kt
new file mode 100644
index 0000000000..902ac035eb
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestStorage.kt
@@ -0,0 +1,119 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.withContext
+import mozilla.appservices.suggest.SuggestApiException
+import mozilla.appservices.suggest.SuggestIngestionConstraints
+import mozilla.appservices.suggest.SuggestStore
+import mozilla.appservices.suggest.SuggestStoreBuilder
+import mozilla.appservices.suggest.Suggestion
+import mozilla.appservices.suggest.SuggestionQuery
+import mozilla.components.concept.base.crash.CrashReporting
+import mozilla.components.support.base.log.logger.Logger
+import java.io.File
+
+/**
+ * A coroutine-aware wrapper around the synchronous [SuggestStore] interface.
+ *
+ * @param context The Android application context.
+ * @param crashReporter An optional [CrashReporting] instance for reporting unexpected caught
+ * exceptions.
+ */
+class FxSuggestStorage(context: Context) {
+ // Lazily initializes the store on first use. `cacheDir` and using the `File` constructor
+ // does I/O, so `store.value` should only be accessed from the read or write scope.
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal val store: Lazy<SuggestStore> = lazy {
+ SuggestStoreBuilder()
+ .cachePath(File(context.cacheDir, CACHE_DATABASE_NAME).absolutePath)
+ .dataPath(context.getDatabasePath(DATABASE_NAME).absolutePath)
+ .build()
+ }
+
+ // We expect almost all Suggest storage operations to be reads, with infrequent writes. The
+ // I/O dispatcher supports both workloads, and using separate scopes lets us cancel reads
+ // without affecting writes.
+ private val readScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
+ private val writeScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
+
+ private val logger = Logger("FxSuggestStorage")
+
+ /**
+ * Queries the store for suggestions.
+ *
+ * @param query The input and suggestion types to match.
+ * @return A list of matching suggestions.
+ */
+ suspend fun query(query: SuggestionQuery): List<Suggestion> =
+ withContext(readScope.coroutineContext) {
+ handleSuggestExceptions("query", emptyList()) {
+ store.value.query(query)
+ }
+ }
+
+ /**
+ * Downloads and persists new Firefox Suggest search suggestions.
+ *
+ * @param constraints Optional limits on suggestions to ingest.
+ * @return `true` if ingestion succeeded; `false` if ingestion failed and should be retried.
+ */
+ suspend fun ingest(constraints: SuggestIngestionConstraints = SuggestIngestionConstraints()): Boolean =
+ withContext(writeScope.coroutineContext) {
+ handleSuggestExceptions("ingest", false) {
+ store.value.ingest(constraints)
+ true
+ }
+ }
+
+ /**
+ * Interrupts any ongoing queries for suggestions.
+ */
+ fun cancelReads() {
+ if (store.isInitialized()) {
+ store.value.interrupt()
+ readScope.coroutineContext.cancelChildren()
+ }
+ }
+
+ /**
+ * Runs an [operation] with the given [name], ignoring and logging any non-fatal exceptions.
+ * Returns either the result of the [operation], or the provided [default] value if the
+ * [operation] throws an exception.
+ *
+ * @param name The name of the operation to run.
+ * @param default The default value to return if the operation fails.
+ * @param operation The operation to run.
+ */
+ private inline fun <T> handleSuggestExceptions(
+ name: String,
+ default: T,
+ operation: () -> T,
+ ): T {
+ return try {
+ operation()
+ } catch (e: SuggestApiException) {
+ logger.warn("Ignoring exception from `$name`", e)
+ default
+ }
+ }
+
+ internal companion object {
+ /**
+ * The database file name for cached data.
+ */
+ const val CACHE_DATABASE_NAME = "suggest.sqlite"
+
+ /**
+ * The database file name for permanent data.
+ */
+ const val DATABASE_NAME = "suggest_data.sqlite"
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProvider.kt b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProvider.kt
new file mode 100644
index 0000000000..3e51e46f3c
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProvider.kt
@@ -0,0 +1,185 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import android.content.res.Resources
+import mozilla.appservices.suggest.Suggestion
+import mozilla.appservices.suggest.SuggestionProvider
+import mozilla.appservices.suggest.SuggestionQuery
+import mozilla.components.concept.awesomebar.AwesomeBar
+import mozilla.components.feature.session.SessionUseCases
+import mozilla.components.support.ktx.kotlin.toBitmap
+import java.util.UUID
+
+private const val MAX_NUM_OF_FIREFOX_SUGGESTIONS = 1
+
+/**
+ * An [AwesomeBar.SuggestionProvider] that returns Firefox Suggest search suggestions.
+ *
+ * @param resources Your application's [Resources] instance.
+ * @param loadUrlUseCase A use case that loads a suggestion's URL when clicked.
+ * @param includeSponsoredSuggestions Whether to return suggestions for sponsored content.
+ * @param includeNonSponsoredSuggestions Whether to return suggestions for web content.
+ * @param suggestionsHeader An optional header title for grouping the returned suggestions.
+ * @param contextId The contextual services user identifier, used for telemetry.
+ */
+class FxSuggestSuggestionProvider(
+ private val resources: Resources,
+ private val loadUrlUseCase: SessionUseCases.LoadUrlUseCase,
+ private val includeSponsoredSuggestions: Boolean,
+ private val includeNonSponsoredSuggestions: Boolean,
+ private val suggestionsHeader: String? = null,
+ private val contextId: String? = null,
+) : AwesomeBar.SuggestionProvider {
+ /**
+ * [AwesomeBar.Suggestion.metadata] keys for this provider's suggestions.
+ */
+ object MetadataKeys {
+ const val CLICK_INFO = "click_info"
+ const val IMPRESSION_INFO = "impression_info"
+ }
+
+ override val id: String = UUID.randomUUID().toString()
+
+ override fun groupTitle(): String? = suggestionsHeader
+
+ override suspend fun onInputChanged(text: String): List<AwesomeBar.Suggestion> =
+ if (text.isEmpty()) {
+ emptyList()
+ } else {
+ val providers = buildList() {
+ val availableSuggestionTypes = FxSuggestNimbus.features
+ .awesomebarSuggestionProvider
+ .value()
+ .availableSuggestionTypes
+ if (includeSponsoredSuggestions && availableSuggestionTypes[SuggestionType.AMP] == true) {
+ add(SuggestionProvider.AMP)
+ }
+ if (includeSponsoredSuggestions && availableSuggestionTypes[SuggestionType.AMP_MOBILE] == true) {
+ add(SuggestionProvider.AMP_MOBILE)
+ }
+ if (includeNonSponsoredSuggestions && availableSuggestionTypes[SuggestionType.WIKIPEDIA] == true) {
+ add(SuggestionProvider.WIKIPEDIA)
+ }
+ }
+ GlobalFxSuggestDependencyProvider.requireStorage().query(
+ SuggestionQuery(
+ keyword = text,
+ providers = providers,
+ limit = MAX_NUM_OF_FIREFOX_SUGGESTIONS,
+ ),
+ ).into()
+ }
+
+ override fun onInputCancelled() {
+ GlobalFxSuggestDependencyProvider.requireStorage().cancelReads()
+ }
+
+ private suspend fun List<Suggestion>.into(): List<AwesomeBar.Suggestion> =
+ mapNotNull { suggestion ->
+ val details = when (suggestion) {
+ is Suggestion.Amp -> SuggestionDetails(
+ title = suggestion.title,
+ url = suggestion.url,
+ fullKeyword = suggestion.fullKeyword,
+ isSponsored = true,
+ icon = suggestion.icon,
+ clickInfo = contextId?.let {
+ FxSuggestInteractionInfo.Amp(
+ blockId = suggestion.blockId,
+ advertiser = suggestion.advertiser.lowercase(),
+ reportingUrl = suggestion.clickUrl,
+ iabCategory = suggestion.iabCategory,
+ contextId = it,
+ )
+ },
+ impressionInfo = contextId?.let {
+ FxSuggestInteractionInfo.Amp(
+ blockId = suggestion.blockId,
+ advertiser = suggestion.advertiser.lowercase(),
+ reportingUrl = suggestion.impressionUrl,
+ iabCategory = suggestion.iabCategory,
+ contextId = it,
+ )
+ },
+ )
+ is Suggestion.Wikipedia -> {
+ val interactionInfo = contextId?.let {
+ FxSuggestInteractionInfo.Wikipedia(contextId = it)
+ }
+ SuggestionDetails(
+ title = suggestion.title,
+ url = suggestion.url,
+ fullKeyword = suggestion.fullKeyword,
+ isSponsored = false,
+ icon = suggestion.icon,
+ clickInfo = interactionInfo,
+ impressionInfo = interactionInfo,
+ )
+ }
+ else -> return@mapNotNull null
+ }
+ AwesomeBar.Suggestion(
+ provider = this@FxSuggestSuggestionProvider,
+ icon = details.icon?.toUByteArray()?.asByteArray()?.toBitmap(),
+ title = details.title,
+ description = if (details.isSponsored) {
+ resources.getString(R.string.sponsored_suggestion_description)
+ } else {
+ null
+ },
+ onSuggestionClicked = {
+ loadUrlUseCase.invoke(details.url)
+ },
+ score = Int.MIN_VALUE,
+ metadata = buildMap {
+ details.clickInfo?.let { put(MetadataKeys.CLICK_INFO, it) }
+ details.impressionInfo?.let { put(MetadataKeys.IMPRESSION_INFO, it) }
+ },
+ )
+ }
+}
+
+internal data class SuggestionDetails(
+ val title: String,
+ val url: String,
+ val fullKeyword: String,
+ val isSponsored: Boolean,
+ val icon: List<UByte>?,
+ val clickInfo: FxSuggestInteractionInfo? = null,
+ val impressionInfo: FxSuggestInteractionInfo? = null,
+)
+
+/**
+ * Additional information about a Firefox Suggest [AwesomeBar.Suggestion] to record in telemetry when the user
+ * interacts with the suggestion.
+ */
+sealed interface FxSuggestInteractionInfo {
+ /**
+ * Interaction information for a sponsored Firefox Suggest search suggestion from AMP.
+ *
+ * @param blockId A unique identifier for the suggestion.
+ * @param advertiser The name of the advertiser providing the sponsored suggestion.
+ * @param reportingUrl The url to report the click or impression to.
+ * @param iabCategory The categorization of the suggestion.
+ * @param contextId The contextual services user identifier.
+ */
+ data class Amp(
+ val blockId: Long,
+ val advertiser: String,
+ val reportingUrl: String,
+ val iabCategory: String,
+ val contextId: String,
+ ) : FxSuggestInteractionInfo
+
+ /**
+ * Interaction information for a Firefox Suggest search suggestion from Wikipedia.
+ *
+ * @param contextId The contextual services user identifier.
+ */
+ data class Wikipedia(
+ val contextId: String,
+ ) : FxSuggestInteractionInfo
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/GlobalFxSuggestDependencyProvider.kt b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/GlobalFxSuggestDependencyProvider.kt
new file mode 100644
index 0000000000..faddb798f8
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/GlobalFxSuggestDependencyProvider.kt
@@ -0,0 +1,30 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+/**
+ * Provides global access to the dependencies needed to access Firefox Suggest search suggestions.
+ */
+object GlobalFxSuggestDependencyProvider {
+ internal var storage: FxSuggestStorage? = null
+
+ /**
+ * Initializes this provider with a wrapped Suggest store.
+ *
+ * Your application's [onCreate][android.app.Application.onCreate] method should call this
+ * method once.
+ *
+ * @param storage The wrapped Suggest store.
+ */
+ fun initialize(storage: FxSuggestStorage) {
+ this.storage = storage
+ }
+
+ internal fun requireStorage(): FxSuggestStorage {
+ return requireNotNull(storage) {
+ "`GlobalFxSuggestDependencyProvider.initialize` must be called before accessing `storage`"
+ }
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFacts.kt b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFacts.kt
new file mode 100644
index 0000000000..21493ff3f1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFacts.kt
@@ -0,0 +1,89 @@
+/* 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 mozilla.components.feature.fxsuggest.facts
+
+import mozilla.components.feature.fxsuggest.FxSuggestInteractionInfo
+import mozilla.components.support.base.Component
+import mozilla.components.support.base.facts.Action
+import mozilla.components.support.base.facts.Fact
+import mozilla.components.support.base.facts.collect
+
+/**
+ * Facts emitted for telemetry related to the Firefox Suggest feature.
+ */
+class FxSuggestFacts {
+ /**
+ * Specific types of telemetry items.
+ */
+ object Items {
+ const val AMP_SUGGESTION_CLICKED = "amp_suggestion_clicked"
+ const val AMP_SUGGESTION_IMPRESSED = "amp_suggestion_impressed"
+ const val WIKIPEDIA_SUGGESTION_CLICKED = "wikipedia_suggestion_clicked"
+ const val WIKIPEDIA_SUGGESTION_IMPRESSED = "wikipedia_suggestion_impressed"
+ }
+
+ /**
+ * Keys used in the metadata map.
+ */
+ object MetadataKeys {
+ const val INTERACTION_INFO = "interaction_info"
+ const val POSITION = "position"
+ const val IS_CLICKED = "is_clicked"
+ const val ENGAGEMENT_ABANDONED = "engagement_abandoned"
+ }
+}
+
+private fun emitFxSuggestFact(
+ action: Action,
+ item: String,
+ value: String? = null,
+ metadata: Map<String, Any>? = null,
+) {
+ Fact(
+ Component.FEATURE_FXSUGGEST,
+ action,
+ item,
+ value,
+ metadata,
+ ).collect()
+}
+
+internal fun emitSuggestionClickedFact(
+ interactionInfo: FxSuggestInteractionInfo,
+ positionInAwesomeBar: Long,
+) {
+ emitFxSuggestFact(
+ Action.INTERACTION,
+ when (interactionInfo) {
+ is FxSuggestInteractionInfo.Amp -> FxSuggestFacts.Items.AMP_SUGGESTION_CLICKED
+ is FxSuggestInteractionInfo.Wikipedia -> FxSuggestFacts.Items.WIKIPEDIA_SUGGESTION_CLICKED
+ },
+ metadata = mapOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO to interactionInfo,
+ FxSuggestFacts.MetadataKeys.POSITION to positionInAwesomeBar,
+ ),
+ )
+}
+
+internal fun emitSuggestionImpressedFact(
+ interactionInfo: FxSuggestInteractionInfo,
+ positionInAwesomeBar: Long,
+ isClicked: Boolean,
+ engagementAbandoned: Boolean,
+) {
+ emitFxSuggestFact(
+ Action.DISPLAY,
+ when (interactionInfo) {
+ is FxSuggestInteractionInfo.Amp -> FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED
+ is FxSuggestInteractionInfo.Wikipedia -> FxSuggestFacts.Items.WIKIPEDIA_SUGGESTION_IMPRESSED
+ },
+ metadata = mapOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO to interactionInfo,
+ FxSuggestFacts.MetadataKeys.POSITION to positionInAwesomeBar,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED to isClicked,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED to engagementAbandoned,
+ ),
+ )
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFactsMiddleware.kt b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFactsMiddleware.kt
new file mode 100644
index 0000000000..622a5459b4
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/java/mozilla/components/feature/fxsuggest/facts/FxSuggestFactsMiddleware.kt
@@ -0,0 +1,84 @@
+/* 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 mozilla.components.feature.fxsuggest.facts
+
+import mozilla.components.browser.state.action.AwesomeBarAction
+import mozilla.components.browser.state.action.BrowserAction
+import mozilla.components.browser.state.state.AwesomeBarState
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.concept.awesomebar.AwesomeBar
+import mozilla.components.feature.fxsuggest.FxSuggestInteractionInfo
+import mozilla.components.feature.fxsuggest.FxSuggestSuggestionProvider
+import mozilla.components.lib.state.Middleware
+import mozilla.components.lib.state.MiddlewareContext
+import mozilla.components.support.base.facts.Fact
+
+/**
+ * Reports [Fact]s for interactions with Firefox Suggest [AwesomeBar.Suggestion]s.
+ *
+ * We report two kinds of interactions: impressions and clicks. We report impressions for any Firefox Suggest
+ * search suggestions that are visible when the user finishes interacting with the [AwesomeBar].
+ * If the user taps on one of those visible Firefox Suggest suggestions, we'll also report a click for that suggestion.
+ *
+ * Each impression's [Fact.metadata] contains a [FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED] key, whose value is
+ * `false` if the user navigated to a destination (like a URL, a search results page, or a suggestion), or
+ * `true` if the user dismissed the [AwesomeBar] without navigating to a destination.
+ *
+ * We _don't_ report impressions for any suggestions that the user sees as they're still typing.
+ */
+class FxSuggestFactsMiddleware : Middleware<BrowserState, BrowserAction> {
+ override fun invoke(
+ context: MiddlewareContext<BrowserState, BrowserAction>,
+ next: (BrowserAction) -> Unit,
+ action: BrowserAction,
+ ) {
+ handleAction(context, action)
+ next(action)
+ }
+
+ private fun handleAction(
+ context: MiddlewareContext<BrowserState, BrowserAction>,
+ action: BrowserAction,
+ ) = when (action) {
+ is AwesomeBarAction.EngagementFinished -> emitSuggestionFacts(
+ awesomeBarState = context.state.awesomeBarState,
+ engagementAbandoned = action.abandoned,
+ )
+ else -> Unit
+ }
+
+ private fun emitSuggestionFacts(awesomeBarState: AwesomeBarState, engagementAbandoned: Boolean) {
+ val visibilityState = awesomeBarState.visibilityState
+ val clickedSuggestion = awesomeBarState.clickedSuggestion
+ visibilityState.visibleProviderGroups.entries.forEachIndexed { groupIndex, (_, suggestions) ->
+ suggestions.forEachIndexed { suggestionIndex, suggestion ->
+ val positionInGroup = suggestionIndex.toLong() + 1
+ val positionInAwesomeBar = groupIndex.toLong() + positionInGroup
+ val isClicked = clickedSuggestion == suggestion
+
+ val impressionInfo = suggestion.metadata?.get(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO,
+ ) as? FxSuggestInteractionInfo
+ impressionInfo?.let {
+ emitSuggestionImpressedFact(
+ interactionInfo = it,
+ positionInAwesomeBar = positionInAwesomeBar,
+ isClicked = isClicked,
+ engagementAbandoned = engagementAbandoned,
+ )
+ }
+
+ if (isClicked) {
+ val clickInfo = suggestion.metadata?.get(
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO,
+ ) as? FxSuggestInteractionInfo
+ clickInfo?.let {
+ emitSuggestionClickedFact(it, positionInAwesomeBar)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-am/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000000..8043abe0ab
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-am/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">ስፖንሰር የተደረገ</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-azb/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-azb/strings.xml
new file mode 100644
index 0000000000..303e6433d1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-azb/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">اسپانسرلی</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-be/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000000..f0718da649
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-be/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Спансавана</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bg/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000000..17e62c6132
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bg/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Спонсорирано</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-br/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-br/strings.xml
new file mode 100644
index 0000000000..20e84c0035
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-br/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Paeroniet</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bs/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000000..c887bcccfb
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-bs/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponzorisano</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ca/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000000..c704133c46
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ca/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinat</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cak/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cak/strings.xml
new file mode 100644
index 0000000000..3ce9b4753f
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cak/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">To\'on</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-co/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-co/strings.xml
new file mode 100644
index 0000000000..a6b504fa5f
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-co/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Spunsurizatu</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cs/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000000..88b9606ddd
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cs/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponzorováno</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cy/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cy/strings.xml
new file mode 100644
index 0000000000..7a2515dec8
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-cy/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Noddwyd</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-da/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000000..e76f79f71e
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-da/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsoreret</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-de/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000000..4fbf31858d
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-de/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Gesponsert</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-dsb/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-dsb/strings.xml
new file mode 100644
index 0000000000..64b039149a
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-dsb/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponserowany</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-el/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000000..aeaedde977
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-el/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Χορηγία</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rCA/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000000..051e478a4b
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rCA/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsored</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rGB/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000000..051e478a4b
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsored</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eo/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eo/strings.xml
new file mode 100644
index 0000000000..a64643096e
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eo/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patronita</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rAR/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rAR/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rAR/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rCL/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rCL/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rCL/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rES/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rES/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rES/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rMX/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rMX/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es-rMX/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-es/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-et/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000000..7c4597fbad
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-et/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsitud</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eu/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000000..32c5e58b2b
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-eu/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Babesleak hornituta</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fi/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000000..1cdf22b8c3
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fi/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsoroitu</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fr/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000000..dbbfc41eef
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsorisé</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fur/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fur/strings.xml
new file mode 100644
index 0000000000..af5f8d967c
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fur/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsorizât</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fy-rNL/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fy-rNL/strings.xml
new file mode 100644
index 0000000000..5f1de443e7
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-fy-rNL/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsore</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gl/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gn/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gn/strings.xml
new file mode 100644
index 0000000000..e91d55ed37
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-gn/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Pytyvõpyréva</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hr/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000000..a23ead351e
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponzorirano</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hsb/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hsb/strings.xml
new file mode 100644
index 0000000000..64b039149a
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hsb/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponserowany</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hu/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000000..84394b33bd
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hu/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Szponzorált</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hy-rAM/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000000..72f889cff6
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-hy-rAM/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Հովանավորված</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ia/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ia/strings.xml
new file mode 100644
index 0000000000..c22878de50
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ia/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsorisate</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-in/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000000..963220372f
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-in/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Disponsori</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-is/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000000..87c9467877
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-is/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Kostað</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-it/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000000..584b2fec52
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-it/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsorizzato</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-iw/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000000..7401f742ed
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-iw/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">ממומן</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ja/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000000..745ec27f5d
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ja/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">広告</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ka/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000000..d414242cd2
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ka/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">დაფინანსებული</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kab/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kab/strings.xml
new file mode 100644
index 0000000000..717e16f556
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kab/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">S lmendad</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kk/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000000..84f3004bbb
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Демеуленген</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kmr/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kmr/strings.xml
new file mode 100644
index 0000000000..514caeb64d
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-kmr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsorkirî</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ko/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000000..7f5e020d8a
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ko/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">스폰서</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-lo/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000000..89f8e79b05
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-lo/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">ໄດ້ຮັບການສະຫນັບສະຫນູນ</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nb-rNO/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nb-rNO/strings.xml
new file mode 100644
index 0000000000..48f8371abc
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nb-rNO/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponset</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nl/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000000..9731cdb595
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Gesponsord</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nn-rNO/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nn-rNO/strings.xml
new file mode 100644
index 0000000000..dd05ba69fc
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-nn-rNO/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsa</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-oc/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-oc/strings.xml
new file mode 100644
index 0000000000..b84ef29b0c
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-oc/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Esponsorizat</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rIN/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rIN/strings.xml
new file mode 100644
index 0000000000..84743460d5
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rIN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">ਸਪੌਂਸਰ ਕੀਤੇ</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rPK/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rPK/strings.xml
new file mode 100644
index 0000000000..bf6e67a534
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pa-rPK/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">سفارش کیتی</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pl/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000000..b1b39ff2ee
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsorowane</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rBR/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rPT/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000000..fee5548ca1
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrocinado</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-rm/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-rm/strings.xml
new file mode 100644
index 0000000000..d1e7419cf6
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-rm/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsurisà</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ru/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000000..015c2263ee
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ru/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Спонсировано</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sat/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sat/strings.xml
new file mode 100644
index 0000000000..645c2876fc
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sat/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">ᱠᱟᱹᱢᱤᱼᱤᱭᱟᱹ</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sc/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sc/strings.xml
new file mode 100644
index 0000000000..da82caab16
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sc/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Patrotzinadu</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-si/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000000..a4d3f1ffe4
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-si/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">අනුග්‍රහය ලද</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sk/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000000..2d35d68fe6
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponzorované</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-skr/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-skr/strings.xml
new file mode 100644
index 0000000000..e7fcf63035
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-skr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">سپانسر تھئے</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sl/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000000..a23ead351e
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sl/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponzorirano</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sq/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000000..043ff9e5d5
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sq/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">E sponsorizuar</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sr/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000000..8043a90ba3
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Спонзорисано</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-su/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-su/strings.xml
new file mode 100644
index 0000000000..4da34a1fac
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-su/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Disponsoran</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sv-rSE/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sv-rSE/strings.xml
new file mode 100644
index 0000000000..cd309dd7a2
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-sv-rSE/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsrad</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tg/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tg/strings.xml
new file mode 100644
index 0000000000..51fc73aafc
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tg/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Сарпарастӣ</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-th/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000000..8c928ba7bc
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-th/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">ได้รับการสนับสนุน</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tr/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000000..b8000e6734
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-tr/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsorlu</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ug/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ug/strings.xml
new file mode 100644
index 0000000000..1329b916cc
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-ug/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">قوللىغان</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-uk/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000000..71da9dc8e0
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-uk/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Спонсоровано</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-vi/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000000..e55cc5105f
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-vi/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Được tài trợ</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rCN/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000000..17dbc15473
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">赞助推广</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rTW/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000..535187d30a
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">贊助項目</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values/strings.xml b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..96cb8941a2
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+<resources>
+ <!-- The description for a sponsored suggestion from Firefox Suggest. -->
+ <string name="sponsored_suggestion_description">Sponsored</string>
+</resources>
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsMiddlewareTest.kt b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsMiddlewareTest.kt
new file mode 100644
index 0000000000..11a6ae6373
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsMiddlewareTest.kt
@@ -0,0 +1,914 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import mozilla.components.browser.state.action.AwesomeBarAction
+import mozilla.components.browser.state.state.AwesomeBarState
+import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.concept.awesomebar.AwesomeBar
+import mozilla.components.feature.fxsuggest.facts.FxSuggestFacts
+import mozilla.components.feature.fxsuggest.facts.FxSuggestFactsMiddleware
+import mozilla.components.support.base.Component
+import mozilla.components.support.base.facts.Action
+import mozilla.components.support.base.facts.Facts
+import mozilla.components.support.base.facts.processor.CollectionProcessor
+import mozilla.components.support.test.ext.joinBlocking
+import mozilla.components.support.test.mock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+class FxSuggestFactsMiddlewareTest {
+ private lateinit var processor: CollectionProcessor
+
+ @Before
+ fun setUp() {
+ processor = CollectionProcessor()
+ Facts.registerProcessor(processor)
+ }
+
+ @After
+ fun tearDown() {
+ Facts.clearProcessors()
+ }
+
+ @Test
+ fun `GIVEN no suggestions are visible WHEN the engagement is completed THEN no facts are collected`() {
+ val store = BrowserStore(
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertTrue(processor.facts.isEmpty())
+ }
+
+ @Test
+ fun `GIVEN 2 non-AMP suggestions are visible WHEN the engagement is completed THEN no facts are collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(provider),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ clickedSuggestion = providerGroupSuggestions[1],
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertTrue(processor.facts.isEmpty())
+ }
+
+ @Test
+ fun `GIVEN 1 AMP suggestion is visible WHEN the engagement is abandoned THEN 1 impression fact is collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/impression",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/click",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = true)).joinBlocking()
+
+ assertEquals(1, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, impressionInfo.blockId)
+ assertEquals("mozilla", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression", impressionInfo.reportingUrl)
+ assertEquals("22 - Shopping", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertTrue(engagementAbandoned)
+ }
+ }
+
+ @Test
+ fun `GIVEN 1 AMP suggestion is visible WHEN the engagement is completed THEN 1 impression fact is collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/impression",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/click",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(1, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, impressionInfo.blockId)
+ assertEquals("mozilla", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression", impressionInfo.reportingUrl)
+ assertEquals("22 - Shopping", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ }
+
+ @Test
+ fun `GIVEN 1 AMP suggestion is visible and a non-AMP suggestion is clicked WHEN the engagement is completed THEN 1 impression fact is collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/impression",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/click",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ clickedSuggestion = providerGroupSuggestions[0],
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(1, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, impressionInfo.blockId)
+ assertEquals("mozilla", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression", impressionInfo.reportingUrl)
+ assertEquals("22 - Shopping", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ }
+
+ @Test
+ fun `GIVEN 1 AMP suggestion is visible and clicked WHEN the engagement is completed THEN 1 impression fact and 1 click fact are collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/impression",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/click",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ clickedSuggestion = providerGroupSuggestions[1],
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(2, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, impressionInfo.blockId)
+ assertEquals("mozilla", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression", impressionInfo.reportingUrl)
+ assertEquals("22 - Shopping", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertTrue(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ processor.facts[1].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.INTERACTION, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_CLICKED, item)
+
+ assertEquals(setOf(FxSuggestFacts.MetadataKeys.INTERACTION_INFO, FxSuggestFacts.MetadataKeys.POSITION), metadata?.keys)
+
+ val clickInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, clickInfo.blockId)
+ assertEquals("mozilla", clickInfo.advertiser)
+ assertEquals("https://example.com/click", clickInfo.reportingUrl)
+ assertEquals("22 - Shopping", clickInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", clickInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+ }
+ }
+
+ @Test
+ fun `GIVEN 2 AMP suggestions are visible WHEN the engagement is completed THEN 2 impression facts are collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/impression-1",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/click-1",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 456,
+ advertiser = "good place eats",
+ reportingUrl = "https://example.com/impression-2",
+ iabCategory = "8 - Food & Drink",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 456,
+ advertiser = "good place eats",
+ reportingUrl = "https://example.com/click-2",
+ iabCategory = "8 - Food & Drink",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(2, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, impressionInfo.blockId)
+ assertEquals("mozilla", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression-1", impressionInfo.reportingUrl)
+ assertEquals("22 - Shopping", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ processor.facts[1].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(456, impressionInfo.blockId)
+ assertEquals("good place eats", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression-2", impressionInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(4, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ }
+
+ @Test
+ fun `GIVEN 2 AMP suggestions are visible and a non-AMP suggestion is clicked WHEN the engagement is completed THEN 2 impression facts are collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/impression-1",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/click-1",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 456,
+ advertiser = "good place eats",
+ reportingUrl = "https://example.com/impression-2",
+ iabCategory = "8 - Food & Drink",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 456,
+ advertiser = "good place eats",
+ reportingUrl = "https://example.com/click-2",
+ iabCategory = "8 - Food & Drink",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ clickedSuggestion = providerGroupSuggestions[2],
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(2, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, impressionInfo.blockId)
+ assertEquals("mozilla", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression-1", impressionInfo.reportingUrl)
+ assertEquals("22 - Shopping", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ processor.facts[1].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(456, impressionInfo.blockId)
+ assertEquals("good place eats", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression-2", impressionInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(4, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ }
+
+ @Test
+ fun `GIVEN 2 AMP suggestions are visible and an AMP suggestion is clicked WHEN the engagement is completed THEN 2 impression facts and 1 click fact are collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/impression-1",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/click-1",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 456,
+ advertiser = "good place eats",
+ reportingUrl = "https://example.com/impression-2",
+ iabCategory = "8 - Food & Drink",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Amp(
+ blockId = 456,
+ advertiser = "good place eats",
+ reportingUrl = "https://example.com/click-2",
+ iabCategory = "8 - Food & Drink",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ clickedSuggestion = providerGroupSuggestions[3],
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(3, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(123, impressionInfo.blockId)
+ assertEquals("mozilla", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression-1", impressionInfo.reportingUrl)
+ assertEquals("22 - Shopping", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ processor.facts[1].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(456, impressionInfo.blockId)
+ assertEquals("good place eats", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression-2", impressionInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(4, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertTrue(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ processor.facts[2].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.INTERACTION, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_CLICKED, item)
+
+ assertEquals(setOf(FxSuggestFacts.MetadataKeys.INTERACTION_INFO, FxSuggestFacts.MetadataKeys.POSITION), metadata?.keys)
+
+ val clickInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(456, clickInfo.blockId)
+ assertEquals("good place eats", clickInfo.advertiser)
+ assertEquals("https://example.com/click-2", clickInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", clickInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", clickInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(4, position)
+ }
+ }
+
+ @Test
+ fun `GIVEN 1 Wikipedia suggestion is visible WHEN the engagement is completed THEN 1 impression fact is collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Wikipedia(
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Wikipedia(
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(1, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.WIKIPEDIA_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Wikipedia)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertFalse(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ }
+
+ @Test
+ fun `GIVEN 1 Wikipedia suggestion is visible and clicked WHEN the engagement is completed THEN 1 impression fact and 1 click fact are collected`() {
+ val provider: AwesomeBar.SuggestionProvider = mock()
+ val providerGroup = AwesomeBar.SuggestionProviderGroup(listOf(provider))
+ val providerGroupSuggestions = listOf(
+ AwesomeBar.Suggestion(provider),
+ AwesomeBar.Suggestion(
+ provider = provider,
+ metadata = mapOf(
+ FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO to FxSuggestInteractionInfo.Wikipedia(
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO to FxSuggestInteractionInfo.Wikipedia(
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ ),
+ ),
+ )
+ val store = BrowserStore(
+ initialState = BrowserState(
+ awesomeBarState = AwesomeBarState(
+ visibilityState = AwesomeBar.VisibilityState(
+ visibleProviderGroups = mapOf(providerGroup to providerGroupSuggestions),
+ ),
+ clickedSuggestion = providerGroupSuggestions[1],
+ ),
+ ),
+ middleware = listOf(FxSuggestFactsMiddleware()),
+ )
+
+ store.dispatch(AwesomeBarAction.EngagementFinished(abandoned = false)).joinBlocking()
+
+ assertEquals(2, processor.facts.size)
+ processor.facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.WIKIPEDIA_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Wikipedia)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertTrue(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ processor.facts[1].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.INTERACTION, action)
+ assertEquals(FxSuggestFacts.Items.WIKIPEDIA_SUGGESTION_CLICKED, item)
+
+ assertEquals(setOf(FxSuggestFacts.MetadataKeys.INTERACTION_INFO, FxSuggestFacts.MetadataKeys.POSITION), metadata?.keys)
+
+ val clickInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Wikipedia)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", clickInfo.contextId)
+
+ val position = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(2, position)
+ }
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsTest.kt b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsTest.kt
new file mode 100644
index 0000000000..a6274d743d
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestFactsTest.kt
@@ -0,0 +1,191 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import mozilla.components.feature.fxsuggest.facts.FxSuggestFacts
+import mozilla.components.feature.fxsuggest.facts.emitSuggestionClickedFact
+import mozilla.components.feature.fxsuggest.facts.emitSuggestionImpressedFact
+import mozilla.components.support.base.Component
+import mozilla.components.support.base.facts.Action
+import mozilla.components.support.base.facts.processor.CollectionProcessor
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class FxSuggestFactsTest {
+
+ @Test
+ fun `GIVEN interaction information for an AMP suggestion WHEN emitting a click fact THEN 1 click fact is collected`() {
+ CollectionProcessor.withFactCollection { facts ->
+
+ emitSuggestionClickedFact(
+ FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/reporting",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ positionInAwesomeBar = 0,
+ )
+
+ assertEquals(1, facts.size)
+ facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.INTERACTION, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_CLICKED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ ),
+ metadata?.keys,
+ )
+
+ val clickInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(clickInfo.blockId, 123)
+ assertEquals(clickInfo.advertiser, "mozilla")
+ assertEquals(clickInfo.reportingUrl, "https://example.com/reporting")
+ assertEquals(clickInfo.iabCategory, "22 - Shopping")
+ assertEquals(clickInfo.contextId, "c303282d-f2e6-46ca-a04a-35d3d873712d")
+
+ val positionInAwesomebar = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(0, positionInAwesomebar)
+ }
+ }
+ }
+
+ @Test
+ fun `GIVEN interaction information for an AMP suggestion WHEN emitting an impression fact THEN 1 impression fact is collected`() {
+ CollectionProcessor.withFactCollection { facts ->
+
+ emitSuggestionImpressedFact(
+ FxSuggestInteractionInfo.Amp(
+ blockId = 123,
+ advertiser = "mozilla",
+ reportingUrl = "https://example.com/reporting",
+ iabCategory = "22 - Shopping",
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ positionInAwesomeBar = 0,
+ isClicked = true,
+ engagementAbandoned = false,
+ )
+
+ assertEquals(1, facts.size)
+ facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.AMP_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Amp)
+ assertEquals(impressionInfo.blockId, 123)
+ assertEquals(impressionInfo.advertiser, "mozilla")
+ assertEquals(impressionInfo.reportingUrl, "https://example.com/reporting")
+ assertEquals(impressionInfo.iabCategory, "22 - Shopping")
+ assertEquals(impressionInfo.contextId, "c303282d-f2e6-46ca-a04a-35d3d873712d")
+
+ val positionInAwesomebar = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(0, positionInAwesomebar)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertTrue(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ }
+ }
+
+ @Test
+ fun `GIVEN interaction information for a Wikipedia suggestion WHEN emitting a click fact THEN 1 click fact is collected`() {
+ CollectionProcessor.withFactCollection { facts ->
+
+ emitSuggestionClickedFact(
+ FxSuggestInteractionInfo.Wikipedia(
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ positionInAwesomeBar = 0,
+ )
+
+ assertEquals(1, facts.size)
+ facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.INTERACTION, action)
+ assertEquals(FxSuggestFacts.Items.WIKIPEDIA_SUGGESTION_CLICKED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ ),
+ metadata?.keys,
+ )
+
+ val clickInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Wikipedia)
+ assertEquals(clickInfo.contextId, "c303282d-f2e6-46ca-a04a-35d3d873712d")
+
+ val positionInAwesomebar = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(0, positionInAwesomebar)
+ }
+ }
+ }
+
+ @Test
+ fun `GIVEN interaction information for a Wikipedia suggestion WHEN emitting an impression fact THEN 1 impression fact is collected`() {
+ CollectionProcessor.withFactCollection { facts ->
+
+ emitSuggestionImpressedFact(
+ FxSuggestInteractionInfo.Wikipedia(
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ ),
+ positionInAwesomeBar = 0,
+ isClicked = true,
+ engagementAbandoned = false,
+ )
+
+ assertEquals(1, facts.size)
+ facts[0].apply {
+ assertEquals(Component.FEATURE_FXSUGGEST, component)
+ assertEquals(Action.DISPLAY, action)
+ assertEquals(FxSuggestFacts.Items.WIKIPEDIA_SUGGESTION_IMPRESSED, item)
+
+ assertEquals(
+ setOf(
+ FxSuggestFacts.MetadataKeys.INTERACTION_INFO,
+ FxSuggestFacts.MetadataKeys.POSITION,
+ FxSuggestFacts.MetadataKeys.IS_CLICKED,
+ FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED,
+ ),
+ metadata?.keys,
+ )
+
+ val impressionInfo = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.INTERACTION_INFO) as? FxSuggestInteractionInfo.Wikipedia)
+ assertEquals(impressionInfo.contextId, "c303282d-f2e6-46ca-a04a-35d3d873712d")
+
+ val positionInAwesomebar = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.POSITION) as? Long)
+ assertEquals(0, positionInAwesomebar)
+
+ val isClicked = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.IS_CLICKED) as? Boolean)
+ assertTrue(isClicked)
+
+ val engagementAbandoned = requireNotNull(metadata?.get(FxSuggestFacts.MetadataKeys.ENGAGEMENT_ABANDONED) as? Boolean)
+ assertFalse(engagementAbandoned)
+ }
+ }
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionSchedulerTest.kt b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionSchedulerTest.kt
new file mode 100644
index 0000000000..375a0f0d3a
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionSchedulerTest.kt
@@ -0,0 +1,80 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.work.Configuration
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.await
+import androidx.work.testing.WorkManagerTestInitHelper
+import kotlinx.coroutines.test.runTest
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FxSuggestIngestionSchedulerTest {
+ private lateinit var storage: FxSuggestStorage
+ private lateinit var workManager: WorkManager
+
+ @Before
+ fun setUp() {
+ storage = mock()
+ GlobalFxSuggestDependencyProvider.storage = storage
+
+ WorkManagerTestInitHelper.initializeTestWorkManager(
+ testContext,
+ Configuration.Builder().build(),
+ )
+ workManager = WorkManager.getInstance(testContext)
+ }
+
+ @After
+ fun tearDown() {
+ workManager.cancelAllWork()
+ GlobalFxSuggestDependencyProvider.storage = null
+ }
+
+ @Test
+ fun startPeriodicIngestion() = runTest {
+ val scheduler = FxSuggestIngestionScheduler(testContext)
+
+ scheduler.startPeriodicIngestion()
+
+ val workInfos = workManager.getWorkInfosForUniqueWork(FxSuggestIngestionWorker.WORK_TAG).await()
+ assertEquals(1, workInfos.size)
+ assertEquals(WorkInfo.State.ENQUEUED, workInfos.first().state)
+ }
+
+ @Test
+ fun stopPeriodicIngestion() = runTest {
+ val scheduler = FxSuggestIngestionScheduler(testContext)
+ scheduler.startPeriodicIngestion()
+
+ scheduler.stopPeriodicIngestion()
+
+ val workInfos = workManager.getWorkInfosForUniqueWork(FxSuggestIngestionWorker.WORK_TAG).await()
+ assertEquals(1, workInfos.size)
+ assertEquals(WorkInfo.State.CANCELLED, workInfos.first().state)
+ }
+
+ @Test
+ fun createPeriodicIngestionWorkerRequest() = runTest {
+ val scheduler = FxSuggestIngestionScheduler(testContext)
+
+ val workRequest = scheduler.createPeriodicIngestionWorkerRequest()
+
+ assertTrue(workRequest.workSpec.isPeriodic)
+ assertTrue(workRequest.tags.contains(FxSuggestIngestionWorker.WORK_TAG))
+ assertEquals(1 * 24 * 60 * 60 * 1000, workRequest.workSpec.intervalDuration)
+ assertEquals(scheduler.getWorkerConstrains(), workRequest.workSpec.constraints)
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorkerTest.kt b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorkerTest.kt
new file mode 100644
index 0000000000..84df98f78e
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestIngestionWorkerTest.kt
@@ -0,0 +1,61 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.work.ListenableWorker
+import androidx.work.await
+import androidx.work.testing.TestListenableWorkerBuilder
+import kotlinx.coroutines.test.runTest
+import mozilla.components.support.test.any
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.support.test.whenever
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+class FxSuggestIngestionWorkerTest {
+ private lateinit var storage: FxSuggestStorage
+
+ @Before
+ fun setUp() {
+ storage = mock()
+ GlobalFxSuggestDependencyProvider.storage = storage
+ }
+
+ @After
+ fun tearDown() {
+ GlobalFxSuggestDependencyProvider.storage = null
+ }
+
+ @Test
+ fun workSucceeds() = runTest {
+ whenever(storage.ingest(any())).thenReturn(true)
+
+ val worker = TestListenableWorkerBuilder<FxSuggestIngestionWorker>(testContext).build()
+
+ val result = worker.startWork().await()
+
+ verify(storage).ingest(any())
+ assertEquals(ListenableWorker.Result.success(), result)
+ }
+
+ @Test
+ fun workShouldRetry() = runTest {
+ whenever(storage.ingest(any())).thenReturn(false)
+
+ val worker = TestListenableWorkerBuilder<FxSuggestIngestionWorker>(testContext).build()
+
+ val result = worker.startWork().await()
+
+ verify(storage).ingest(any())
+ assertEquals(ListenableWorker.Result.retry(), result)
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProviderTest.kt b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProviderTest.kt
new file mode 100644
index 0000000000..93d106fa5f
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/test/java/mozilla/components/feature/fxsuggest/FxSuggestSuggestionProviderTest.kt
@@ -0,0 +1,394 @@
+/* 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 mozilla.components.feature.fxsuggest
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlinx.coroutines.test.runTest
+import mozilla.appservices.suggest.Suggestion
+import mozilla.appservices.suggest.SuggestionProvider
+import mozilla.appservices.suggest.SuggestionQuery
+import mozilla.components.support.test.any
+import mozilla.components.support.test.eq
+import mozilla.components.support.test.mock
+import mozilla.components.support.test.robolectric.testContext
+import mozilla.components.support.test.whenever
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(AndroidJUnit4::class)
+class FxSuggestSuggestionProviderTest {
+ private lateinit var storage: FxSuggestStorage
+
+ @Before
+ fun setUp() {
+ storage = mock()
+ val suggestionProviderConfig = AwesomebarSuggestionProvider(
+ availableSuggestionTypes = mapOf(
+ SuggestionType.AMP to true,
+ SuggestionType.AMP_MOBILE to false,
+ SuggestionType.WIKIPEDIA to true,
+ ),
+ )
+ FxSuggestNimbus.features.awesomebarSuggestionProvider.withCachedValue(suggestionProviderConfig)
+ GlobalFxSuggestDependencyProvider.storage = storage
+ }
+
+ @After
+ fun tearDown() {
+ FxSuggestNimbus.features.awesomebarSuggestionProvider.withCachedValue(null)
+ GlobalFxSuggestDependencyProvider.storage = null
+ }
+
+ @Test
+ fun inputEmpty() = runTest {
+ whenever(storage.query(any())).thenReturn(
+ listOf(
+ Suggestion.Wikipedia(
+ title = "Las Vegas",
+ url = "https://wikipedia.org/wiki/Las_Vegas",
+ icon = null,
+ iconMimetype = null,
+ fullKeyword = "las",
+ ),
+ ),
+ )
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = true,
+ includeSponsoredSuggestions = true,
+ )
+
+ val suggestions = provider.onInputChanged("")
+
+ verify(storage, never()).query(any())
+ assertTrue(suggestions.isEmpty())
+ }
+
+ @Test
+ fun inputNotEmpty() = runTest {
+ whenever(storage.query(any())).thenReturn(
+ listOf(
+ Suggestion.Amp(
+ title = "Lasagna Come Out Tomorrow",
+ url = "https://www.lasagna.restaurant",
+ rawUrl = "https://www.lasagna.restaurant",
+ icon = listOf(
+ 137u, 80u, 78u, 71u, 13u, 10u, 26u, 10u, 0u, 0u, 0u, 13u, 73u, 72u, 68u, 82u,
+ 0u, 0u, 0u, 1u, 0u, 0u, 0u, 1u, 1u, 3u, 0u, 0u, 0u, 37u, 219u, 86u, 202u, 0u,
+ 0u, 0u, 3u, 80u, 76u, 84u, 69u, 0u, 0u, 0u, 167u, 122u, 61u, 218u, 0u, 0u, 0u,
+ 1u, 116u, 82u, 78u, 83u, 0u, 64u, 230u, 216u, 102u, 0u, 0u, 0u, 10u, 73u, 68u,
+ 65u, 84u, 8u, 215u, 99u, 96u, 0u, 0u, 0u, 2u, 0u, 1u, 226u, 33u, 188u, 51u, 0u,
+ 0u, 0u, 0u, 73u, 69u, 78u, 68u, 174u, 66u, 96u, 130u,
+ ),
+ iconMimetype = null,
+ fullKeyword = "lasagna",
+ blockId = 0,
+ advertiser = "Good Place Eats",
+ iabCategory = "8 - Food & Drink",
+ impressionUrl = "https://example.com/impression_url",
+ clickUrl = "https://example.com/click_url",
+ rawClickUrl = "https://example.com/click_url",
+ score = 0.3,
+ ),
+ ),
+ )
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = true,
+ includeSponsoredSuggestions = true,
+ )
+
+ val suggestions = provider.onInputChanged("la")
+
+ verify(storage).query(
+ eq(
+ SuggestionQuery(
+ keyword = "la",
+ providers = listOf(SuggestionProvider.AMP, SuggestionProvider.WIKIPEDIA),
+ limit = 1,
+ ),
+ ),
+ )
+ assertEquals(1, suggestions.size)
+ assertEquals("Lasagna Come Out Tomorrow", suggestions[0].title)
+ assertEquals(testContext.resources.getString(R.string.sponsored_suggestion_description), suggestions[0].description)
+ assertNotNull(suggestions[0].icon)
+ assertEquals(Int.MIN_VALUE, suggestions[0].score)
+ assertTrue(suggestions[0].metadata.isNullOrEmpty())
+ }
+
+ @Test
+ fun inputCancelled() = runTest {
+ doNothing().`when`(storage).cancelReads()
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = true,
+ includeSponsoredSuggestions = true,
+ )
+
+ provider.onInputCancelled()
+
+ verify(storage).cancelReads()
+ }
+
+ @Test
+ fun includeNonSponsoredSuggestionsOnly() = runTest {
+ whenever(storage.query(any())).thenReturn(
+ listOf(
+ Suggestion.Wikipedia(
+ title = "Las Vegas",
+ url = "https://wikipedia.org/wiki/Las_Vegas",
+ icon = null,
+ iconMimetype = null,
+ fullKeyword = "las",
+ ),
+ ),
+ )
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = true,
+ includeSponsoredSuggestions = false,
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ )
+
+ val suggestions = provider.onInputChanged("la")
+
+ verify(storage).query(
+ eq(
+ SuggestionQuery(
+ keyword = "la",
+ providers = listOf(SuggestionProvider.WIKIPEDIA),
+ limit = 1,
+ ),
+ ),
+ )
+ assertEquals(1, suggestions.size)
+ assertEquals("Las Vegas", suggestions.first().title)
+ assertNull(suggestions.first().description)
+ assertNull(suggestions.first().icon)
+ assertEquals(Int.MIN_VALUE, suggestions.first().score)
+ suggestions.first().metadata?.let {
+ assertEquals(setOf(FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO, FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO), it.keys)
+
+ val clickInfo = requireNotNull(it[FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO] as? FxSuggestInteractionInfo.Wikipedia)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", clickInfo.contextId)
+
+ val impressionInfo = requireNotNull(it[FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO] as? FxSuggestInteractionInfo.Wikipedia)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+ }
+ }
+
+ @Test
+ fun includeSponsoredSuggestionsOnly() = runTest {
+ whenever(storage.query(any())).thenReturn(
+ listOf(
+ Suggestion.Amp(
+ title = "Lasagna Come Out Tomorrow",
+ url = "https://www.lasagna.restaurant",
+ rawUrl = "https://www.lasagna.restaurant",
+ icon = null,
+ iconMimetype = null,
+ fullKeyword = "lasagna",
+ blockId = 0,
+ advertiser = "Good Place Eats",
+ iabCategory = "8 - Food & Drink",
+ impressionUrl = "https://example.com/impression_url",
+ clickUrl = "https://example.com/click_url",
+ rawClickUrl = "https://example.com/click_url",
+ score = 0.3,
+ ),
+ ),
+ )
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = false,
+ includeSponsoredSuggestions = true,
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ )
+
+ val suggestions = provider.onInputChanged("la")
+
+ verify(storage).query(
+ eq(
+ SuggestionQuery(
+ keyword = "la",
+ providers = listOf(SuggestionProvider.AMP),
+ limit = 1,
+ ),
+ ),
+ )
+ assertEquals(1, suggestions.size)
+ assertEquals("Lasagna Come Out Tomorrow", suggestions.first().title)
+ assertEquals(testContext.resources.getString(R.string.sponsored_suggestion_description), suggestions.first().description)
+ assertEquals(Int.MIN_VALUE, suggestions.first().score)
+ suggestions.first().metadata?.let {
+ assertEquals(setOf(FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO, FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO), it.keys)
+
+ val clickInfo = requireNotNull(it[FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO] as? FxSuggestInteractionInfo.Amp)
+ assertEquals(0, clickInfo.blockId)
+ assertEquals("good place eats", clickInfo.advertiser)
+ assertEquals("https://example.com/click_url", clickInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", clickInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", clickInfo.contextId)
+
+ val impressionInfo = requireNotNull(it[FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO] as? FxSuggestInteractionInfo.Amp)
+ assertEquals(0, impressionInfo.blockId)
+ assertEquals("good place eats", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression_url", impressionInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+ }
+ }
+
+ @Test
+ fun includeMobileSponsoredSuggestionsOnly() = runTest {
+ FxSuggestNimbus.features.awesomebarSuggestionProvider.withCachedValue(
+ AwesomebarSuggestionProvider(
+ availableSuggestionTypes = mapOf(
+ SuggestionType.AMP to false,
+ SuggestionType.AMP_MOBILE to true,
+ SuggestionType.WIKIPEDIA to true,
+ ),
+ ),
+ )
+ whenever(storage.query(any())).thenReturn(
+ listOf(
+ Suggestion.Amp(
+ title = "Mobile - Lasagna Come Out Tomorrow",
+ url = "https://www.lasagna.restaurant",
+ rawUrl = "https://www.lasagna.restaurant",
+ icon = null,
+ iconMimetype = null,
+ fullKeyword = "lasagna",
+ blockId = 0,
+ advertiser = "Good Place Eats",
+ iabCategory = "8 - Food & Drink",
+ impressionUrl = "https://example.com/impression_url",
+ clickUrl = "https://example.com/click_url",
+ rawClickUrl = "https://example.com/click_url",
+ score = 0.3,
+ ),
+ ),
+ )
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = false,
+ includeSponsoredSuggestions = true,
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ )
+
+ val suggestions = provider.onInputChanged("la")
+
+ verify(storage).query(
+ eq(
+ SuggestionQuery(
+ keyword = "la",
+ providers = listOf(SuggestionProvider.AMP_MOBILE),
+ limit = 1,
+ ),
+ ),
+ )
+ assertEquals(1, suggestions.size)
+ assertEquals("Mobile - Lasagna Come Out Tomorrow", suggestions.first().title)
+ assertEquals(testContext.resources.getString(R.string.sponsored_suggestion_description), suggestions.first().description)
+ assertEquals(Int.MIN_VALUE, suggestions.first().score)
+ suggestions.first().metadata?.let {
+ assertEquals(setOf(FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO, FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO), it.keys)
+
+ val clickInfo = requireNotNull(it[FxSuggestSuggestionProvider.MetadataKeys.CLICK_INFO] as? FxSuggestInteractionInfo.Amp)
+ assertEquals(0, clickInfo.blockId)
+ assertEquals("good place eats", clickInfo.advertiser)
+ assertEquals("https://example.com/click_url", clickInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", clickInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", clickInfo.contextId)
+
+ val impressionInfo = requireNotNull(it[FxSuggestSuggestionProvider.MetadataKeys.IMPRESSION_INFO] as? FxSuggestInteractionInfo.Amp)
+ assertEquals(0, impressionInfo.blockId)
+ assertEquals("good place eats", impressionInfo.advertiser)
+ assertEquals("https://example.com/impression_url", impressionInfo.reportingUrl)
+ assertEquals("8 - Food & Drink", impressionInfo.iabCategory)
+ assertEquals("c303282d-f2e6-46ca-a04a-35d3d873712d", impressionInfo.contextId)
+ }
+ }
+
+ @Test
+ fun includeSponsoredSuggestionsOnlyWhenAmpUnavailable() = runTest {
+ FxSuggestNimbus.features.awesomebarSuggestionProvider.withCachedValue(
+ AwesomebarSuggestionProvider(availableSuggestionTypes = emptyMap()),
+ )
+ whenever(storage.query(any())).thenReturn(emptyList())
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = false,
+ includeSponsoredSuggestions = true,
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ )
+
+ val suggestions = provider.onInputChanged("la")
+
+ verify(storage).query(
+ eq(
+ SuggestionQuery(
+ keyword = "la",
+ providers = emptyList(),
+ limit = 1,
+ ),
+ ),
+ )
+ assertTrue(suggestions.isEmpty())
+ }
+
+ @Test
+ fun includeNonSponsoredSuggestionsOnlyWhenWikipediaUnavailable() = runTest {
+ FxSuggestNimbus.features.awesomebarSuggestionProvider.withCachedValue(
+ AwesomebarSuggestionProvider(availableSuggestionTypes = emptyMap()),
+ )
+ whenever(storage.query(any())).thenReturn(emptyList())
+
+ val provider = FxSuggestSuggestionProvider(
+ resources = testContext.resources,
+ loadUrlUseCase = mock(),
+ includeNonSponsoredSuggestions = true,
+ includeSponsoredSuggestions = false,
+ contextId = "c303282d-f2e6-46ca-a04a-35d3d873712d",
+ )
+
+ val suggestions = provider.onInputChanged("la")
+
+ verify(storage).query(
+ eq(
+ SuggestionQuery(
+ keyword = "la",
+ providers = emptyList(),
+ limit = 1,
+ ),
+ ),
+ )
+ assertTrue(suggestions.isEmpty())
+ }
+}
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/mobile/android/android-components/components/feature/fxsuggest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000..cf1c399ea8
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1,2 @@
+mock-maker-inline
+// This allows mocking final classes (classes are final by default in Kotlin)
diff --git a/mobile/android/android-components/components/feature/fxsuggest/src/test/resources/robolectric.properties b/mobile/android/android-components/components/feature/fxsuggest/src/test/resources/robolectric.properties
new file mode 100644
index 0000000000..932b01b9eb
--- /dev/null
+++ b/mobile/android/android-components/components/feature/fxsuggest/src/test/resources/robolectric.properties
@@ -0,0 +1 @@
+sdk=28