diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /mobile/android/android-components/components/feature/fxsuggest | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mobile/android/android-components/components/feature/fxsuggest')
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 |