From 7fae3666e7c09782808c31893caaf52db1aef700 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 9 Sep 2024 19:01:21 +0200 Subject: [PATCH] Add setting for app telemetry --- app-k9mail/build.gradle.kts | 1 + .../main/kotlin/app/k9mail/K9KoinModule.kt | 3 ++ .../k9mail/telemetry/K9TelemetryManager.kt | 7 ++++ app-thunderbird/build.gradle.kts | 1 + .../android/ThunderbirdKoinModule.kt | 3 ++ .../src/main/res/drawable/ic_analytics.xml | 35 +++++++++++++++++++ feature/telemetry/api/build.gradle.kts | 3 ++ .../feature/telemetry/api/TelemetryManager.kt | 8 +++++ feature/telemetry/glean/build.gradle.kts | 12 +++++++ .../telemetry/glean/GleanTelemetryManager.kt | 7 ++++ legacy/core/build.gradle.kts | 1 + legacy/core/src/main/java/com/fsck/k9/K9.kt | 15 ++++++++ .../GeneralSettingsDescriptions.java | 10 ++++++ .../com/fsck/k9/preferences/Settings.java | 2 +- .../core/src/test/java/com/fsck/k9/TestApp.kt | 6 ++++ legacy/storage/build.gradle.kts | 1 + .../test/java/com/fsck/k9/storage/TestApp.kt | 6 ++++ legacy/ui/legacy/build.gradle.kts | 1 + .../general/GeneralSettingsDataStore.kt | 2 ++ .../general/GeneralSettingsFragment.kt | 12 +++++++ .../ui/legacy/src/main/res/values/strings.xml | 7 ++++ .../src/main/res/xml/general_settings.xml | 15 ++++++++ .../src/test/java/com/fsck/k9/TestApp.kt | 6 ++++ settings.gradle.kts | 5 +++ 24 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 app-k9mail/src/main/kotlin/app/k9mail/telemetry/K9TelemetryManager.kt create mode 100644 core/ui/legacy/designsystem/src/main/res/drawable/ic_analytics.xml create mode 100644 feature/telemetry/api/build.gradle.kts create mode 100644 feature/telemetry/api/src/main/kotlin/app/k9mail/feature/telemetry/api/TelemetryManager.kt create mode 100644 feature/telemetry/glean/build.gradle.kts create mode 100644 feature/telemetry/glean/src/main/kotlin/app/k9mail/feature/telemetry/glean/GleanTelemetryManager.kt diff --git a/app-k9mail/build.gradle.kts b/app-k9mail/build.gradle.kts index 11a8ea47c2..ca0a7c406b 100644 --- a/app-k9mail/build.gradle.kts +++ b/app-k9mail/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(projects.feature.widget.messageList) implementation(projects.feature.widget.shortcut) implementation(projects.feature.widget.unread) + implementation(projects.feature.telemetry.api) implementation(libs.androidx.work.runtime) diff --git a/app-k9mail/src/main/kotlin/app/k9mail/K9KoinModule.kt b/app-k9mail/src/main/kotlin/app/k9mail/K9KoinModule.kt index fca597faa8..96de236791 100644 --- a/app-k9mail/src/main/kotlin/app/k9mail/K9KoinModule.kt +++ b/app-k9mail/src/main/kotlin/app/k9mail/K9KoinModule.kt @@ -7,10 +7,12 @@ import app.k9mail.core.featureflag.FeatureFlagFactory import app.k9mail.core.ui.theme.api.FeatureThemeProvider import app.k9mail.core.ui.theme.api.ThemeProvider import app.k9mail.dev.developmentModuleAdditions +import app.k9mail.feature.telemetry.api.TelemetryManager import app.k9mail.feature.widget.shortcut.LauncherShortcutActivity import app.k9mail.featureflag.K9FeatureFlagFactory import app.k9mail.provider.K9AppNameProvider import app.k9mail.provider.K9FeatureThemeProvider +import app.k9mail.telemetry.K9TelemetryManager import app.k9mail.widget.appWidgetModule import com.fsck.k9.AppConfig import com.fsck.k9.BuildConfig @@ -33,6 +35,7 @@ val appModule = module { single { K9ThemeProvider() } single { K9FeatureThemeProvider() } single { K9FeatureFlagFactory() } + single { K9TelemetryManager() } developmentModuleAdditions() } diff --git a/app-k9mail/src/main/kotlin/app/k9mail/telemetry/K9TelemetryManager.kt b/app-k9mail/src/main/kotlin/app/k9mail/telemetry/K9TelemetryManager.kt new file mode 100644 index 0000000000..c5e2dd44c0 --- /dev/null +++ b/app-k9mail/src/main/kotlin/app/k9mail/telemetry/K9TelemetryManager.kt @@ -0,0 +1,7 @@ +package app.k9mail.telemetry + +import app.k9mail.feature.telemetry.api.TelemetryManager + +class K9TelemetryManager : TelemetryManager { + override fun isTelemetryFeatureIncluded(): Boolean = false +} diff --git a/app-thunderbird/build.gradle.kts b/app-thunderbird/build.gradle.kts index 4ccc951cb8..6a556e8099 100644 --- a/app-thunderbird/build.gradle.kts +++ b/app-thunderbird/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(projects.feature.widget.messageList) implementation(projects.feature.widget.shortcut) implementation(projects.feature.widget.unread) + implementation(projects.feature.telemetry.glean) implementation(libs.androidx.work.runtime) diff --git a/app-thunderbird/src/main/kotlin/net/thunderbird/android/ThunderbirdKoinModule.kt b/app-thunderbird/src/main/kotlin/net/thunderbird/android/ThunderbirdKoinModule.kt index 92b41e1890..61c3e3abd2 100644 --- a/app-thunderbird/src/main/kotlin/net/thunderbird/android/ThunderbirdKoinModule.kt +++ b/app-thunderbird/src/main/kotlin/net/thunderbird/android/ThunderbirdKoinModule.kt @@ -5,6 +5,8 @@ import app.k9mail.core.common.provider.AppNameProvider import app.k9mail.core.featureflag.FeatureFlagFactory import app.k9mail.core.ui.theme.api.FeatureThemeProvider import app.k9mail.core.ui.theme.api.ThemeProvider +import app.k9mail.feature.telemetry.api.TelemetryManager +import app.k9mail.feature.telemetry.glean.GleanTelemetryManager import app.k9mail.feature.widget.shortcut.LauncherShortcutActivity import com.fsck.k9.AppConfig import com.fsck.k9.activity.MessageCompose @@ -32,6 +34,7 @@ val appModule = module { single { TbThemeProvider() } single { TbFeatureThemeProvider() } single { TbFeatureFlagFactory() } + single { GleanTelemetryManager() } developmentModuleAdditions() } diff --git a/core/ui/legacy/designsystem/src/main/res/drawable/ic_analytics.xml b/core/ui/legacy/designsystem/src/main/res/drawable/ic_analytics.xml new file mode 100644 index 0000000000..b1ecf6e44c --- /dev/null +++ b/core/ui/legacy/designsystem/src/main/res/drawable/ic_analytics.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/feature/telemetry/api/build.gradle.kts b/feature/telemetry/api/build.gradle.kts new file mode 100644 index 0000000000..063b640132 --- /dev/null +++ b/feature/telemetry/api/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id(ThunderbirdPlugins.Library.jvm) +} diff --git a/feature/telemetry/api/src/main/kotlin/app/k9mail/feature/telemetry/api/TelemetryManager.kt b/feature/telemetry/api/src/main/kotlin/app/k9mail/feature/telemetry/api/TelemetryManager.kt new file mode 100644 index 0000000000..5ff30714a6 --- /dev/null +++ b/feature/telemetry/api/src/main/kotlin/app/k9mail/feature/telemetry/api/TelemetryManager.kt @@ -0,0 +1,8 @@ +package app.k9mail.feature.telemetry.api + +interface TelemetryManager { + /** + * Returns `true` if the app has a telemetry feature included. + */ + fun isTelemetryFeatureIncluded(): Boolean +} diff --git a/feature/telemetry/glean/build.gradle.kts b/feature/telemetry/glean/build.gradle.kts new file mode 100644 index 0000000000..6979d51245 --- /dev/null +++ b/feature/telemetry/glean/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + id(ThunderbirdPlugins.Library.android) +} + +android { + namespace = "app.k9mail.feature.telemetry.glean" + resourcePrefix = "telemetry_glean_" +} + +dependencies { + api(projects.feature.telemetry.api) +} diff --git a/feature/telemetry/glean/src/main/kotlin/app/k9mail/feature/telemetry/glean/GleanTelemetryManager.kt b/feature/telemetry/glean/src/main/kotlin/app/k9mail/feature/telemetry/glean/GleanTelemetryManager.kt new file mode 100644 index 0000000000..1405bc0029 --- /dev/null +++ b/feature/telemetry/glean/src/main/kotlin/app/k9mail/feature/telemetry/glean/GleanTelemetryManager.kt @@ -0,0 +1,7 @@ +package app.k9mail.feature.telemetry.glean + +import app.k9mail.feature.telemetry.api.TelemetryManager + +class GleanTelemetryManager : TelemetryManager { + override fun isTelemetryFeatureIncluded(): Boolean = true +} diff --git a/legacy/core/build.gradle.kts b/legacy/core/build.gradle.kts index 9827f00de7..ab6bb7008f 100644 --- a/legacy/core/build.gradle.kts +++ b/legacy/core/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { api(projects.legacy.search) implementation(projects.plugins.openpgpApiLib.openpgpApi) + implementation(projects.feature.telemetry.api) api(libs.androidx.annotation) diff --git a/legacy/core/src/main/java/com/fsck/k9/K9.kt b/legacy/core/src/main/java/com/fsck/k9/K9.kt index 6b52c56733..a9f89de472 100644 --- a/legacy/core/src/main/java/com/fsck/k9/K9.kt +++ b/legacy/core/src/main/java/com/fsck/k9/K9.kt @@ -2,6 +2,7 @@ package com.fsck.k9 import android.content.Context import android.content.SharedPreferences +import app.k9mail.feature.telemetry.api.TelemetryManager import app.k9mail.legacy.account.Account import app.k9mail.legacy.account.Account.SortType import app.k9mail.legacy.di.DI @@ -20,6 +21,7 @@ import timber.log.Timber.DebugTree // TODO "Use GeneralSettingsManager and GeneralSettings instead" object K9 : EarlyInit { private val generalSettingsManager: RealGeneralSettingsManager by inject() + private val telemetryManager: TelemetryManager by inject() /** * If this is `true`, various development settings will be enabled. @@ -267,6 +269,11 @@ object K9 : EarlyInit { @JvmStatic var swipeLeftAction: SwipeAction = SwipeAction.ToggleRead + // TODO: This is a feature-specific setting that doesn't need to be available to apps that don't include the + // feature. Extract `Storage` and `StorageEditor` to a separate module so feature modules can retrieve and store + // their own settings. + var isTelemetryEnabled = false + val isQuietTime: Boolean get() { if (!isQuietTimeEnabled) { @@ -384,6 +391,10 @@ object K9 : EarlyInit { swipeRightAction = storage.getEnum("swipeRightAction", SwipeAction.ToggleSelection) swipeLeftAction = storage.getEnum("swipeLeftAction", SwipeAction.ToggleRead) + + if (telemetryManager.isTelemetryFeatureIncluded()) { + isTelemetryEnabled = storage.getBoolean("enableTelemetry", true) + } } internal fun save(editor: StorageEditor) { @@ -448,6 +459,10 @@ object K9 : EarlyInit { editor.putEnum("swipeRightAction", swipeRightAction) editor.putEnum("swipeLeftAction", swipeLeftAction) + if (telemetryManager.isTelemetryFeatureIncluded()) { + editor.putBoolean("enableTelemetry", isTelemetryEnabled) + } + fontSizes.save(editor) } diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java b/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java index f6dd1abd70..6a7eea8d77 100644 --- a/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java +++ b/legacy/core/src/main/java/com/fsck/k9/preferences/GeneralSettingsDescriptions.java @@ -9,6 +9,7 @@ import java.util.TreeMap; import android.content.Context; +import app.k9mail.feature.telemetry.api.TelemetryManager; import app.k9mail.legacy.account.Account; import app.k9mail.legacy.account.Account.SortType; import app.k9mail.legacy.di.DI; @@ -48,6 +49,8 @@ class GeneralSettingsDescriptions { static final Map>> SETTINGS; private static final Map UPGRADERS; + private static final TelemetryManager telemetryManager = DI.get(TelemetryManager.class); + static { Map>> s = new LinkedHashMap<>(); @@ -298,6 +301,13 @@ class GeneralSettingsDescriptions { new EnumSetting<>(PostMarkAsUnreadNavigation.class, PostMarkAsUnreadNavigation.ReturnToMessageList)) )); + // TODO: Add a way to properly support feature-specific settings. + if (telemetryManager.isTelemetryFeatureIncluded()) { + s.put("enableTelemetry", Settings.versions( + new V(97, new BooleanSetting(true)) + )); + } + SETTINGS = Collections.unmodifiableMap(s); Map u = new HashMap<>(); diff --git a/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java b/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java index 977fb55a29..acc6abe3bb 100644 --- a/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java +++ b/legacy/core/src/main/java/com/fsck/k9/preferences/Settings.java @@ -33,7 +33,7 @@ class Settings { * * @see SettingsExporter */ - public static final int VERSION = 96; + public static final int VERSION = 97; static Map validate(int version, Map>> settings, Map importedSettings, boolean useDefaultValues) { diff --git a/legacy/core/src/test/java/com/fsck/k9/TestApp.kt b/legacy/core/src/test/java/com/fsck/k9/TestApp.kt index 200294a17b..dd0a645798 100644 --- a/legacy/core/src/test/java/com/fsck/k9/TestApp.kt +++ b/legacy/core/src/test/java/com/fsck/k9/TestApp.kt @@ -2,6 +2,7 @@ package com.fsck.k9 import android.app.Application import androidx.work.WorkManager +import app.k9mail.feature.telemetry.api.TelemetryManager import app.k9mail.legacy.di.DI import com.fsck.k9.backend.BackendManager import com.fsck.k9.controller.ControllerExtension @@ -43,4 +44,9 @@ val testModule = module { single { mock() } single(named("controllerExtensions")) { emptyList() } single { mock() } + single { + object : TelemetryManager { + override fun isTelemetryFeatureIncluded(): Boolean = true + } + } } diff --git a/legacy/storage/build.gradle.kts b/legacy/storage/build.gradle.kts index 462725e9e7..911a183e11 100644 --- a/legacy/storage/build.gradle.kts +++ b/legacy/storage/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { testImplementation(projects.mail.testing) testImplementation(projects.legacy.testing) + testImplementation(projects.feature.telemetry.api) testImplementation(libs.robolectric) testImplementation(libs.commons.io) } diff --git a/legacy/storage/src/test/java/com/fsck/k9/storage/TestApp.kt b/legacy/storage/src/test/java/com/fsck/k9/storage/TestApp.kt index fc94b89751..9d3bf421a1 100644 --- a/legacy/storage/src/test/java/com/fsck/k9/storage/TestApp.kt +++ b/legacy/storage/src/test/java/com/fsck/k9/storage/TestApp.kt @@ -1,6 +1,7 @@ package com.fsck.k9.storage import android.app.Application +import app.k9mail.feature.telemetry.api.TelemetryManager import app.k9mail.legacy.di.DI import com.fsck.k9.AppConfig import com.fsck.k9.Core @@ -32,4 +33,9 @@ val testModule = module { single { mock() } single { K9StoragePersister(get()) } single { mock() } + single { + object : TelemetryManager { + override fun isTelemetryFeatureIncluded(): Boolean = true + } + } } diff --git a/legacy/ui/legacy/build.gradle.kts b/legacy/ui/legacy/build.gradle.kts index 4dcf4efb3f..866e3b6e85 100644 --- a/legacy/ui/legacy/build.gradle.kts +++ b/legacy/ui/legacy/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { // TODO: Remove AccountOauth dependency implementation(projects.feature.account.oauth) implementation(projects.feature.settings.import) + implementation(projects.feature.telemetry.api) compileOnly(projects.mail.protocols.imap) diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt index bbbd759d26..1f6ac67eb7 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsDataStore.kt @@ -45,6 +45,7 @@ class GeneralSettingsDataStore( "debug_logging" -> K9.isDebugLoggingEnabled "sensitive_logging" -> K9.isSensitiveDebugLoggingEnabled "volume_navigation" -> K9.isUseVolumeKeysForNavigation + "enable_telemetry" -> K9.isTelemetryEnabled else -> defValue } } @@ -74,6 +75,7 @@ class GeneralSettingsDataStore( "debug_logging" -> K9.isDebugLoggingEnabled = value "sensitive_logging" -> K9.isSensitiveDebugLoggingEnabled = value "volume_navigation" -> K9.isUseVolumeKeysForNavigation = value + "enable_telemetry" -> K9.isTelemetryEnabled = value else -> return } diff --git a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsFragment.kt b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsFragment.kt index 8290ae0eeb..7a23db487b 100644 --- a/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsFragment.kt +++ b/legacy/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsFragment.kt @@ -7,9 +7,12 @@ import android.view.MenuInflater import android.view.MenuItem import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.preference.ListPreference +import androidx.preference.Preference +import app.k9mail.feature.telemetry.api.TelemetryManager import com.fsck.k9.ui.R import com.fsck.k9.ui.base.extensions.withArguments import com.fsck.k9.ui.observe +import com.fsck.k9.ui.settings.remove import com.google.android.material.snackbar.Snackbar import com.takisoft.preferencex.PreferenceFragmentCompat import org.koin.android.ext.android.inject @@ -19,6 +22,7 @@ import com.fsck.k9.core.R as CoreR class GeneralSettingsFragment : PreferenceFragmentCompat() { private val viewModel: GeneralSettingsViewModel by viewModel() private val dataStore: GeneralSettingsDataStore by inject() + private val telemetryManager: TelemetryManager by inject() private var rootKey: String? = null private var currentUiState: GeneralSettingsUiState? = null @@ -37,6 +41,7 @@ class GeneralSettingsFragment : PreferenceFragmentCompat() { setPreferencesFromResource(R.xml.general_settings, rootKey) initializeTheme() + initializeDataCollection() viewModel.uiState.observe(this) { uiState -> updateUiState(uiState) @@ -80,6 +85,12 @@ class GeneralSettingsFragment : PreferenceFragmentCompat() { } } + private fun initializeDataCollection() { + if (!telemetryManager.isTelemetryFeatureIncluded()) { + findPreference(PREFERENCE_DATA_COLLECTION)?.remove() + } + } + private fun updateUiState(uiState: GeneralSettingsUiState) { val oldUiState = currentUiState currentUiState = uiState @@ -119,6 +130,7 @@ class GeneralSettingsFragment : PreferenceFragmentCompat() { companion object { private const val PREFERENCE_THEME = "theme" private const val PREFERENCE_SCREEN_DEBUGGING = "debug_preferences" + private const val PREFERENCE_DATA_COLLECTION = "data_collection" fun create(rootKey: String? = null) = GeneralSettingsFragment().withArguments(ARG_PREFERENCE_ROOT to rootKey) } diff --git a/legacy/ui/legacy/src/main/res/values/strings.xml b/legacy/ui/legacy/src/main/res/values/strings.xml index 69df1617e9..73c56991ab 100644 --- a/legacy/ui/legacy/src/main/res/values/strings.xml +++ b/legacy/ui/legacy/src/main/res/values/strings.xml @@ -1114,4 +1114,11 @@ You can keep this message and use it as a backup for your secret key. If you wan unread, %s + + + Data collection + + Usage and technical data + + Shares performance, usage, hardware and customization data about this app with Mozilla to help us make Thunderbird better diff --git a/legacy/ui/legacy/src/main/res/xml/general_settings.xml b/legacy/ui/legacy/src/main/res/xml/general_settings.xml index a621d94ed9..15e09f1d9b 100644 --- a/legacy/ui/legacy/src/main/res/xml/general_settings.xml +++ b/legacy/ui/legacy/src/main/res/xml/general_settings.xml @@ -514,6 +514,21 @@ + + + + + + { TestCoreResourceProvider() } single { InMemoryStoragePersister() } + single { + object : TelemetryManager { + override fun isTelemetryFeatureIncluded(): Boolean = true + } + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index bd1c22e45f..8663d84409 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -75,6 +75,11 @@ include( ":feature:migration:provider", ) +include( + ":feature:telemetry:api", + ":feature:telemetry:glean", +) + include( ":core:common", ":core:featureflags",