From 6a6bd27e19c11a179dcf2d75155695306d5016e2 Mon Sep 17 00:00:00 2001 From: Cristian Monforte Date: Tue, 7 Oct 2025 17:30:35 +0200 Subject: [PATCH] Add attributed metrics internal dev settings --- app/build.gradle | 1 + .../StatisticsAttributedMetricsPlugin.kt | 32 ++++++ .../statistics/StatisticsInternalInfoView.kt | 96 ++++++++++++++++++ .../view_statistics_attributed_metrics.xml | 71 +++++++++++++ .../attributed-metrics-internal/build.gradle | 41 ++++++++ .../lint-baseline.xml | 4 + .../src/main/AndroidManifest.xml | 11 +++ .../AttributedMetricsDevSettingsFeatures.kt | 44 +++++++++ .../AttributedMetricsDevSettingsActivity.kt | 99 +++++++++++++++++++ .../ui/AttributedMetricsSettingPlugin.kt | 27 +++++ ...tivity_attributed_metrics_dev_settings.xml | 55 +++++++++++ .../features/api/InternalFeaturePlugin.kt | 1 + 12 files changed, 482 insertions(+) create mode 100644 app/src/internal/java/com/duckduckgo/app/statistics/StatisticsAttributedMetricsPlugin.kt create mode 100644 app/src/internal/java/com/duckduckgo/app/statistics/StatisticsInternalInfoView.kt create mode 100644 app/src/internal/res/layout/view_statistics_attributed_metrics.xml create mode 100644 attributed-metrics/attributed-metrics-internal/build.gradle create mode 100644 attributed-metrics/attributed-metrics-internal/lint-baseline.xml create mode 100644 attributed-metrics/attributed-metrics-internal/src/main/AndroidManifest.xml create mode 100644 attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/AttributedMetricsDevSettingsFeatures.kt create mode 100644 attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsDevSettingsActivity.kt create mode 100644 attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsSettingPlugin.kt create mode 100644 attributed-metrics/attributed-metrics-internal/src/main/res/layout/activity_attributed_metrics_dev_settings.xml diff --git a/app/build.gradle b/app/build.gradle index db9b9d54c548..e98816002a99 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -411,6 +411,7 @@ dependencies { implementation project(':attributed-metrics-api') implementation project(':attributed-metrics-impl') + internalImplementation project(':attributed-metrics-internal') implementation project(':breakage-reporting-impl') diff --git a/app/src/internal/java/com/duckduckgo/app/statistics/StatisticsAttributedMetricsPlugin.kt b/app/src/internal/java/com/duckduckgo/app/statistics/StatisticsAttributedMetricsPlugin.kt new file mode 100644 index 000000000000..694140818283 --- /dev/null +++ b/app/src/internal/java/com/duckduckgo/app/statistics/StatisticsAttributedMetricsPlugin.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.statistics + +import android.content.Context +import android.view.View +import com.duckduckgo.app.attributed.metrics.internal.ui.AttributedMetricsSettingPlugin +import com.duckduckgo.di.scopes.ActivityScope +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +@ContributesMultibinding(ActivityScope::class) +class StatisticsAttributedMetricsPlugin @Inject constructor() : AttributedMetricsSettingPlugin { + + override fun getView(context: Context): View { + return StatisticsInternalInfoView(context) + } +} diff --git a/app/src/internal/java/com/duckduckgo/app/statistics/StatisticsInternalInfoView.kt b/app/src/internal/java/com/duckduckgo/app/statistics/StatisticsInternalInfoView.kt new file mode 100644 index 000000000000..764167bb2d62 --- /dev/null +++ b/app/src/internal/java/com/duckduckgo/app/statistics/StatisticsInternalInfoView.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2025 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.statistics + +import android.content.Context +import android.util.AttributeSet +import android.widget.LinearLayout +import android.widget.Toast +import com.duckduckgo.anvil.annotations.InjectWith +import com.duckduckgo.app.browser.databinding.ViewStatisticsAttributedMetricsBinding +import com.duckduckgo.app.global.install.AppInstallStore +import com.duckduckgo.app.referral.AppReferrerDataStore +import com.duckduckgo.app.statistics.store.StatisticsDataStore +import com.duckduckgo.common.ui.viewbinding.viewBinding +import com.duckduckgo.di.scopes.ViewScope +import dagger.android.support.AndroidSupportInjection +import java.text.SimpleDateFormat +import java.util.* +import javax.inject.Inject + +@InjectWith(ViewScope::class) +class StatisticsInternalInfoView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, +) : LinearLayout(context, attrs, defStyle) { + + @Inject lateinit var store: StatisticsDataStore + + @Inject lateinit var referrerDataStore: AppReferrerDataStore + + @Inject lateinit var appInstallStore: AppInstallStore + + private val binding: ViewStatisticsAttributedMetricsBinding by viewBinding() + + private val dateETFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply { + timeZone = TimeZone.getTimeZone("US/Eastern") + } + override fun onAttachedToWindow() { + AndroidSupportInjection.inject(this) + super.onAttachedToWindow() + + binding.searchAtb.apply { + text = store.searchRetentionAtb ?: "unknown" + } + + binding.searchAtbSave.setOnClickListener { + store.searchRetentionAtb?.let { + store.searchRetentionAtb = binding.searchAtb.text + } + Toast.makeText(this.context, "Search Atb updated", Toast.LENGTH_SHORT).show() + } + + binding.originInput.apply { + text = referrerDataStore.utmOriginAttributeCampaign ?: "unknown" + } + + binding.originInputSave.setOnClickListener { + referrerDataStore.utmOriginAttributeCampaign = binding.originInput.text.toString() + Toast.makeText(this.context, "Origin updated", Toast.LENGTH_SHORT).show() + } + + binding.installDateInput.apply { + val timestamp = appInstallStore.installTimestamp + text = dateETFormat.format(Date(timestamp)) + } + + binding.installDateSave.setOnClickListener { + try { + val date = dateETFormat.parse(binding.installDateInput.text) + if (date != null) { + appInstallStore.installTimestamp = date.time + Toast.makeText(this.context, "Install date updated", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this.context, "Invalid date format. Use yyyy-MM-dd", Toast.LENGTH_SHORT).show() + } + } catch (e: Exception) { + Toast.makeText(this.context, "Invalid date format. Use yyyy-MM-dd", Toast.LENGTH_SHORT).show() + } + } + } +} diff --git a/app/src/internal/res/layout/view_statistics_attributed_metrics.xml b/app/src/internal/res/layout/view_statistics_attributed_metrics.xml new file mode 100644 index 000000000000..c1f8c36b82f4 --- /dev/null +++ b/app/src/internal/res/layout/view_statistics_attributed_metrics.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + diff --git a/attributed-metrics/attributed-metrics-internal/build.gradle b/attributed-metrics/attributed-metrics-internal/build.gradle new file mode 100644 index 000000000000..6707ee5e481c --- /dev/null +++ b/attributed-metrics/attributed-metrics-internal/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' + id 'com.squareup.anvil' + id 'com.google.devtools.ksp' +} + +apply from: "$rootProject.projectDir/gradle/android-library.gradle" + +android { + anvil { + generateDaggerFactories = true // default is false + } + namespace 'com.duckduckgo.attributed.metrics.internal' + testOptions { + unitTests { + includeAndroidResources = true + } + } +} + +dependencies { + anvil project(':anvil-compiler') + implementation project(':anvil-annotations') + implementation project(':attributed-metrics-api') + implementation project(':attributed-metrics-impl') + implementation project(':di') + implementation project(':internal-features-api') + implementation project(':navigation-api') + implementation project(':common-utils') + implementation project(':design-system') + + implementation AndroidX.appCompat + implementation AndroidX.constraintLayout + implementation Google.android.material + implementation Google.dagger + + testImplementation project(path: ':common-test') + testImplementation "org.mockito.kotlin:mockito-kotlin:_" + testImplementation Testing.junit4 +} diff --git a/attributed-metrics/attributed-metrics-internal/lint-baseline.xml b/attributed-metrics/attributed-metrics-internal/lint-baseline.xml new file mode 100644 index 000000000000..1526a743bda6 --- /dev/null +++ b/attributed-metrics/attributed-metrics-internal/lint-baseline.xml @@ -0,0 +1,4 @@ + + + + diff --git a/attributed-metrics/attributed-metrics-internal/src/main/AndroidManifest.xml b/attributed-metrics/attributed-metrics-internal/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..ade4ff78dfe9 --- /dev/null +++ b/attributed-metrics/attributed-metrics-internal/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/AttributedMetricsDevSettingsFeatures.kt b/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/AttributedMetricsDevSettingsFeatures.kt new file mode 100644 index 000000000000..aaaeb9886959 --- /dev/null +++ b/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/AttributedMetricsDevSettingsFeatures.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.attributed.metrics.internal + +import android.content.Context +import com.duckduckgo.anvil.annotations.PriorityKey +import com.duckduckgo.app.attributed.metrics.internal.ui.MainAttributedMetricsSettings +import com.duckduckgo.di.scopes.AppScope +import com.duckduckgo.internal.features.api.InternalFeaturePlugin +import com.duckduckgo.navigation.api.GlobalActivityStarter +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +@PriorityKey(InternalFeaturePlugin.ATTRIBUTED_METRICS_SETTINGS_PRIO_KEY) +class AttributedMetricsDevSettingsFeatures @Inject constructor( + private val globalActivityStarter: GlobalActivityStarter, +) : InternalFeaturePlugin { + override fun internalFeatureTitle(): String { + return "Attributed Metrics Settings" + } + + override fun internalFeatureSubtitle(): String { + return "Attributed Metrics Dev Settings for internal users" + } + + override fun onInternalFeatureClicked(activityContext: Context) { + globalActivityStarter.start(activityContext, MainAttributedMetricsSettings) + } +} diff --git a/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsDevSettingsActivity.kt b/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsDevSettingsActivity.kt new file mode 100644 index 000000000000..90cdcfd48ff6 --- /dev/null +++ b/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsDevSettingsActivity.kt @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.attributed.metrics.internal.ui + +import android.os.Bundle +import android.widget.Toast +import androidx.lifecycle.lifecycleScope +import com.duckduckgo.anvil.annotations.ContributeToActivityStarter +import com.duckduckgo.anvil.annotations.InjectWith +import com.duckduckgo.app.attributed.metrics.impl.AttributedMetricsState +import com.duckduckgo.app.attributed.metrics.store.AttributedMetricsDateUtils +import com.duckduckgo.app.attributed.metrics.store.EventDao +import com.duckduckgo.app.attributed.metrics.store.EventEntity +import com.duckduckgo.appbuildconfig.api.AppBuildConfig +import com.duckduckgo.attributed.metrics.internal.databinding.ActivityAttributedMetricsDevSettingsBinding +import com.duckduckgo.common.ui.DuckDuckGoActivity +import com.duckduckgo.common.ui.viewbinding.viewBinding +import com.duckduckgo.common.utils.plugins.PluginPoint +import com.duckduckgo.di.scopes.ActivityScope +import com.duckduckgo.navigation.api.GlobalActivityStarter +import kotlinx.coroutines.launch +import javax.inject.Inject + +@InjectWith(ActivityScope::class) +@ContributeToActivityStarter(MainAttributedMetricsSettings::class) +class AttributedMetricsDevSettingsActivity : DuckDuckGoActivity() { + + private val binding: ActivityAttributedMetricsDevSettingsBinding by viewBinding() + + @Inject + lateinit var eventDao: EventDao + + @Inject + lateinit var dateUtils: AttributedMetricsDateUtils + + @Inject + lateinit var appBuildConfig: AppBuildConfig + + @Inject + lateinit var attributedMetricsState: AttributedMetricsState + + @Inject + lateinit var settingsPlugins: PluginPoint + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + setupToolbar(binding.includeToolbar.toolbar) + setupViews() + setupPlugins() + } + + private fun setupPlugins() { + settingsPlugins.getPlugins() + .mapNotNull { it.getView(this) } + .forEach { view -> + binding.settingsContainer.addView(view) + } + } + + private fun setupViews() { + binding.addTestEventsButton.setOnClickListener { + addTestEvents() + } + lifecycleScope.launch { + binding.clientActive.setSecondaryText(if (attributedMetricsState.isActive()) "Yes" else "No") + binding.returningUser.setSecondaryText(if (appBuildConfig.isAppReinstall()) "Yes" else "No") + } + } + + private fun addTestEvents() { + lifecycleScope.launch { + repeat(10) { daysAgo -> + val date = dateUtils.getDateMinusDays(daysAgo) + eventDao.insertEvent(EventEntity(eventName = "ddg_search_days", count = 1, day = date)) + eventDao.insertEvent(EventEntity(eventName = "ddg_search", count = 1, day = date)) + } + Toast.makeText(this@AttributedMetricsDevSettingsActivity, "Test events added", Toast.LENGTH_SHORT).show() + } + } +} + +data object MainAttributedMetricsSettings : GlobalActivityStarter.ActivityParams { + private fun readResolve(): Any = MainAttributedMetricsSettings +} diff --git a/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsSettingPlugin.kt b/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsSettingPlugin.kt new file mode 100644 index 000000000000..b3c6e8ff7a1d --- /dev/null +++ b/attributed-metrics/attributed-metrics-internal/src/main/java/com/duckduckgo/app/attributed/metrics/internal/ui/AttributedMetricsSettingPlugin.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.attributed.metrics.internal.ui + +import android.content.Context +import android.view.View +import com.duckduckgo.anvil.annotations.ContributesPluginPoint +import com.duckduckgo.di.scopes.ActivityScope + +@ContributesPluginPoint(ActivityScope::class) +interface AttributedMetricsSettingPlugin { + fun getView(context: Context): View +} diff --git a/attributed-metrics/attributed-metrics-internal/src/main/res/layout/activity_attributed_metrics_dev_settings.xml b/attributed-metrics/attributed-metrics-internal/src/main/res/layout/activity_attributed_metrics_dev_settings.xml new file mode 100644 index 000000000000..5506a1b3ab27 --- /dev/null +++ b/attributed-metrics/attributed-metrics-internal/src/main/res/layout/activity_attributed_metrics_dev_settings.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal-features/internal-features-api/src/main/java/com/duckduckgo/internal/features/api/InternalFeaturePlugin.kt b/internal-features/internal-features-api/src/main/java/com/duckduckgo/internal/features/api/InternalFeaturePlugin.kt index 39853bd38722..b266a1224ca5 100644 --- a/internal-features/internal-features-api/src/main/java/com/duckduckgo/internal/features/api/InternalFeaturePlugin.kt +++ b/internal-features/internal-features-api/src/main/java/com/duckduckgo/internal/features/api/InternalFeaturePlugin.kt @@ -47,5 +47,6 @@ interface InternalFeaturePlugin { const val ADS_SETTINGS_PRIO_KEY = 800 const val CRASH_ANR_SETTINGS_PRIO_KEY = 900 const val WEB_VIEW_DEV_SETTINGS_PRIO_KEY = 1_000 + const val ATTRIBUTED_METRICS_SETTINGS_PRIO_KEY = 1_100 } }