From bba787e87ee045d17347b69c20d58aa82c2bec13 Mon Sep 17 00:00:00 2001 From: "codrut.topliceanu" Date: Tue, 12 Oct 2021 13:44:30 +0300 Subject: [PATCH] For #21732 - Adds inactive tabs survey on disable + telemetry --- app/metrics.yaml | 32 +++++ .../mozilla/fenix/components/metrics/Event.kt | 7 ++ .../components/metrics/GleanMetricsService.kt | 8 ++ .../fenix/settings/TabsSettingsFragment.kt | 93 +++++++++++++- .../java/org/mozilla/fenix/utils/Settings.kt | 8 ++ .../layout/survey_inactive_tabs_disable.xml | 114 ++++++++++++++++++ app/src/main/res/values/colors.xml | 3 + app/src/main/res/values/preference_keys.xml | 3 + app/src/main/res/values/strings.xml | 16 +-- 9 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 app/src/main/res/layout/survey_inactive_tabs_disable.xml diff --git a/app/metrics.yaml b/app/metrics.yaml index 2ecfba2355..85daffad31 100644 --- a/app/metrics.yaml +++ b/app/metrics.yaml @@ -1720,6 +1720,38 @@ preferences: notification_emails: - android-probes@mozilla.com expires: "2022-11-01" + inactive_tabs_survey_opened: + type: event + description: > + A survey for asking the user why she intends to turn off the + inactive tabs feature is shown. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/21732 + data_reviews: + - https://github.com/mozilla-mobile/fenix/pull/21862#issuecomment-949598042 + data_sensitivity: + - interaction + notification_emails: + - android-probes@mozilla.com + expires: "2022-02-01" + turn_off_inactive_tabs_survey: + type: event + description: > + The user has disabled inactive tabs feature and responded + to our request for feedback. + extra_keys: + feedback: + description: | + The user's feedback regarding inactive tabs feature. + bugs: + - https://github.com/mozilla-mobile/fenix/issues/21732 + data_reviews: + - https://github.com/mozilla-mobile/fenix/pull/21862#issuecomment-946977614 + data_sensitivity: + - interaction + notification_emails: + - android-probes@mozilla.com + expires: "2022-02-01" search.default_engine: code: diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt index 96a3a9adf7..340eacfe6e 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Event.kt @@ -20,6 +20,7 @@ import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.Logins import org.mozilla.fenix.GleanMetrics.Onboarding import org.mozilla.fenix.GleanMetrics.Pocket +import org.mozilla.fenix.GleanMetrics.Preferences import org.mozilla.fenix.GleanMetrics.ProgressiveWebApp import org.mozilla.fenix.GleanMetrics.SearchShortcuts import org.mozilla.fenix.GleanMetrics.TabsTray @@ -203,6 +204,12 @@ sealed class Event { data class TabsTrayCloseInactiveTab(val amountClosed: Int = 1) : Event() object TabsTrayOpenInactiveTab : Event() + object InactiveTabsSurveyOpened : Event() + data class InactiveTabsOffSurvey(val feedback: String) : Event() { + override val extras: Map + get() = mapOf(Preferences.turnOffInactiveTabsSurveyKeys.feedback to feedback.lowercase(Locale.ROOT)) + } + object ProgressiveWebAppOpenFromHomescreenTap : Event() object ProgressiveWebAppInstallAsShortcut : Event() diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt index 91267ab834..4b295a8b1b 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/GleanMetricsService.kt @@ -37,6 +37,7 @@ import org.mozilla.fenix.GleanMetrics.Metrics import org.mozilla.fenix.GleanMetrics.Onboarding import org.mozilla.fenix.GleanMetrics.Pings import org.mozilla.fenix.GleanMetrics.Pocket +import org.mozilla.fenix.GleanMetrics.Preferences import org.mozilla.fenix.GleanMetrics.ProgressiveWebApp import org.mozilla.fenix.GleanMetrics.ReaderMode import org.mozilla.fenix.GleanMetrics.RecentBookmarks @@ -626,6 +627,13 @@ private val Event.wrapper: EventWrapper<*>? is Event.TabsTrayOpenInactiveTab -> EventWrapper( { TabsTray.openInactiveTab.add() } ) + is Event.InactiveTabsSurveyOpened -> EventWrapper( + { Preferences.inactiveTabsSurveyOpened.record(it) } + ) + is Event.InactiveTabsOffSurvey -> EventWrapper( + { Preferences.turnOffInactiveTabsSurvey.record(it) }, + { Preferences.turnOffInactiveTabsSurveyKeys.valueOf(it) } + ) is Event.AutoPlaySettingVisited -> EventWrapper( { Autoplay.visitedSetting.record(it) } ) diff --git a/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt index 5cc919db1c..fc672a6370 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/TabsSettingsFragment.kt @@ -4,8 +4,12 @@ package org.mozilla.fenix.settings +import android.content.res.Configuration import android.os.Bundle +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup +import android.widget.RadioButton import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference @@ -14,14 +18,18 @@ import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event.TabViewSettingChanged import org.mozilla.fenix.components.metrics.Event.TabViewSettingChanged.Type +import org.mozilla.fenix.databinding.SurveyInactiveTabsDisableBinding import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.utils.view.addToRadioGroup +import java.util.Locale /** * Lets the user customize auto closing tabs. */ +@Suppress("TooManyFunctions") class TabsSettingsFragment : PreferenceFragmentCompat() { private lateinit var listRadioButton: RadioButtonPreference private lateinit var gridRadioButton: RadioButtonPreference @@ -32,6 +40,9 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { private lateinit var inactiveTabsCategory: PreferenceCategory private lateinit var inactiveTabs: SwitchPreference private lateinit var searchTermTabGroups: SwitchPreference + private val shouldShowInactiveTabsTurnOffSurvey + get() = requireContext().settings().isTelemetryEnabled && + requireContext().settings().shouldShowInactiveTabsTurnOffSurvey override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.tabs_preferences, rootKey) @@ -45,6 +56,7 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { override fun onResume() { super.onResume() showToolbar(getString(R.string.preferences_tabs)) + setupPreferences() } @@ -68,8 +80,25 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { radioOneDay = requirePreference(R.string.pref_key_close_tabs_after_one_day) inactiveTabs = requirePreference(R.string.pref_key_inactive_tabs).also { - it.isChecked = it.context.settings().inactiveTabsAreEnabled - it.onPreferenceChangeListener = SharedPreferenceUpdater() + it.isChecked = requireContext().settings().inactiveTabsAreEnabled + it.setOnPreferenceChangeListener { preference, newValue -> + if (shouldShowInactiveTabsTurnOffSurvey && newValue == false) { + // The first time the user tries to disable the feature show a little survey for her motives. + val inactiveTabsSurveyBinding = SurveyInactiveTabsDisableBinding.inflate( + LayoutInflater.from(context), + view as ViewGroup, + true + ) + setupSurvey(inactiveTabsSurveyBinding) + requireContext().metrics.track(Event.InactiveTabsSurveyOpened) + + // Don't update the preference as a direct action of user tapping the switch. + // Only disable the feature after the user selects an option in the survey or expressly closes it. + false + } else { + SharedPreferenceUpdater().onPreferenceChange(preference, newValue) + } + } } inactiveTabsCategory = requirePreference(R.string.pref_key_inactive_tabs_category).also { @@ -88,6 +117,66 @@ class TabsSettingsFragment : PreferenceFragmentCompat() { setupRadioGroups() } + private fun setupSurvey(inactiveTabsSurveyBinding: SurveyInactiveTabsDisableBinding) { + inactiveTabsSurveyBinding.closeSurvey.setOnClickListener { + finishInactiveTabsSurvey(inactiveTabsSurveyBinding) + + // Register that user closed this survey without picking any option. + requireContext().metrics.track( + Event.InactiveTabsOffSurvey("none") + ) + } + + // A map is needed to help retrieve the correct string on SEND. + // These values are also sent to Glean which will truncate anything over 100 UTF8 characters. + val radioButtonsMap: Map = mapOf( + R.id.rb_do_not_understand to R.string.inactive_tabs_survey_do_not_understand, + R.id.rb_do_it_myself to R.string.inactive_tabs_survey_do_it_myself, + R.id.rb_time_too_long to R.string.inactive_tabs_survey_time_too_long_option, + R.id.rb_time_too_short to R.string.inactive_tabs_survey_time_too_short_option, + ) + + // Sets the Radio buttons' text + radioButtonsMap.forEach { + inactiveTabsSurveyBinding.surveyGroup.findViewById(it.key)?.text = + requireContext().getText(it.value) + } + + inactiveTabsSurveyBinding.sendButton.setOnClickListener { + val checkedRadioButtonId = inactiveTabsSurveyBinding.surveyGroup.checkedRadioButtonId + // If no option has been selected the button does not need to do anything. + if (checkedRadioButtonId != -1) { + finishInactiveTabsSurvey(inactiveTabsSurveyBinding) + + // Using the stringId of the selected option an event is sent using English. + radioButtonsMap[checkedRadioButtonId]?.let { stringId -> + requireContext().metrics.track( + Event.InactiveTabsOffSurvey(getDefaultString(stringId)) + ) + } + } + } + } + + /** + * Set the inactive tabs survey completed and the feature disabled. + */ + private fun finishInactiveTabsSurvey(inactiveTabsSurveyBinding: SurveyInactiveTabsDisableBinding) { + inactiveTabsSurveyBinding.surveyContainer.visibility = View.GONE + requireContext().settings().shouldShowInactiveTabsTurnOffSurvey = false + requireContext().settings().inactiveTabsAreEnabled = false + requirePreference(R.string.pref_key_inactive_tabs).isChecked = false + } + + /** + * Get the "en-US" string value for the indicated [resourceId]. + */ + private fun getDefaultString(resourceId: Int): String { + val config = Configuration(requireContext().resources.configuration) + config.setLocale(Locale.ENGLISH) + return requireContext().createConfigurationContext(config).getText(resourceId).toString() + } + private fun setupRadioGroups() { addToRadioGroup( listRadioButton, diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 1c5a9c9d31..527d0b7ac0 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -867,6 +867,14 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) + /** + * Should we display a feedback request to the user when he turns off the Inactive Tabs feature + */ + var shouldShowInactiveTabsTurnOffSurvey by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_should_show_inactive_tabs_turn_off_survey), + default = true + ) + fun getSitePermissionsPhoneFeatureAction( feature: PhoneFeature, default: Action = Action.ASK_TO_ALLOW diff --git a/app/src/main/res/layout/survey_inactive_tabs_disable.xml b/app/src/main/res/layout/survey_inactive_tabs_disable.xml new file mode 100644 index 0000000000..fe150c81df --- /dev/null +++ b/app/src/main/res/layout/survey_inactive_tabs_disable.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + +