[fenix] FNX-14583 ⁃ Extract and test preference helpers for Settings (https://github.com/mozilla-mobile/fenix/pull/13402)

pull/600/head
Tiger Oakes 4 years ago committed by GitHub
parent b167911097
commit c759cea9f2

@ -0,0 +1,31 @@
/* 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 org.mozilla.fenix.components.settings
import androidx.core.content.edit
import mozilla.components.support.ktx.android.content.PreferencesHolder
class CounterPreference(
private val holder: PreferencesHolder,
private val key: String,
private val maxCount: Int
) {
val value get() = holder.preferences.getInt(key, 0)
fun underMaxCount() = value < maxCount
fun increment() {
holder.preferences.edit {
putInt(key, value + 1)
}
}
}
/**
* Property delegate for getting and an int shared preference and incrementing it.
*/
fun PreferencesHolder.counterPreference(key: String, maxCount: Int = -1) =
CounterPreference(this, key, maxCount)

@ -0,0 +1,25 @@
/* 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 org.mozilla.fenix.components.settings
import mozilla.components.support.ktx.android.content.PreferencesHolder
import mozilla.components.support.ktx.android.content.booleanPreference
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
private class DummyProperty : ReadWriteProperty<PreferencesHolder, Boolean> {
override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>) = false
override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: Boolean) = Unit
}
/**
* Property delegate for getting and setting a boolean shared preference gated by a feature flag.
*/
fun featureFlagPreference(key: String, default: Boolean, featureFlag: Boolean) =
if (featureFlag) {
booleanPreference(key, default)
} else {
DummyProperty()
}

@ -1,29 +0,0 @@
/* 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 org.mozilla.fenix.utils
import mozilla.components.support.ktx.android.content.PreferencesHolder
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
fun featureFlagPreference(
key: String,
default: Boolean,
featureFlag: Boolean
): ReadWriteProperty<PreferencesHolder, Boolean> =
FeatureFlagPreferencePreference(key, default, featureFlag)
private class FeatureFlagPreferencePreference(
private val key: String,
private val default: Boolean,
private val featureFlag: Boolean
) : ReadWriteProperty<PreferencesHolder, Boolean> {
override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>): Boolean =
featureFlag && thisRef.preferences.getBoolean(key, default)
override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: Boolean) =
thisRef.preferences.edit().putBoolean(key, value).apply()
}

@ -30,6 +30,8 @@ import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.metrics.MozillaProductDetector import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.settings.counterPreference
import org.mozilla.fenix.components.settings.featureFlagPreference
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.getPreferenceKey
@ -50,14 +52,9 @@ private const val AUTOPLAY_USER_SETTING = "AUTOPLAY_USER_SETTING"
class Settings(private val appContext: Context) : PreferencesHolder { class Settings(private val appContext: Context) : PreferencesHolder {
companion object { companion object {
const val showLoginsSecureWarningSyncMaxCount = 1
const val showLoginsSecureWarningMaxCount = 1
const val trackingProtectionOnboardingMaximumCount = 1
const val pwaVisitsToShowPromptMaxCount = 3
const val topSitesMaxCount = 16 const val topSitesMaxCount = 16
const val FENIX_PREFERENCES = "fenix_preferences" const val FENIX_PREFERENCES = "fenix_preferences"
private const val showSearchWidgetCFRMaxCount = 3
private const val BLOCKED_INT = 0 private const val BLOCKED_INT = 0
private const val ASK_TO_ALLOW_INT = 1 private const val ASK_TO_ALLOW_INT = 1
private const val ALLOWED_INT = 2 private const val ALLOWED_INT = 2
@ -177,38 +174,26 @@ class Settings(private val appContext: Context) : PreferencesHolder {
true true
) )
private val activeSearchCount by intPreference( private val activeSearchCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_search_count), appContext.getPreferenceKey(R.string.pref_key_search_count)
default = 0
) )
fun incrementActiveSearchCount() { fun incrementActiveSearchCount() = activeSearchCount.increment()
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_count),
activeSearchCount + 1
).apply()
}
private val isActiveSearcher: Boolean private val isActiveSearcher: Boolean
get() = activeSearchCount > 2 get() = activeSearchCount.value > 2
fun shouldDisplaySearchWidgetCFR(): Boolean = fun shouldDisplaySearchWidgetCFR(): Boolean =
isActiveSearcher && isActiveSearcher &&
searchWidgetCFRDismissCount < showSearchWidgetCFRMaxCount && searchWidgetCFRDismissCount.underMaxCount() &&
!searchWidgetInstalled && !searchWidgetInstalled &&
!searchWidgetCFRManuallyDismissed !searchWidgetCFRManuallyDismissed
private val searchWidgetCFRDisplayCount by intPreference( private val searchWidgetCFRDisplayCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count), appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count)
default = 0
) )
fun incrementSearchWidgetCFRDisplayed() { fun incrementSearchWidgetCFRDisplayed() = searchWidgetCFRDisplayCount.increment()
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
searchWidgetCFRDisplayCount + 1
).apply()
}
private val searchWidgetCFRManuallyDismissed by booleanPreference( private val searchWidgetCFRManuallyDismissed by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed), appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed),
@ -222,17 +207,12 @@ class Settings(private val appContext: Context) : PreferencesHolder {
).apply() ).apply()
} }
private val searchWidgetCFRDismissCount by intPreference( private val searchWidgetCFRDismissCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count), appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
default = 0 maxCount = 3
) )
fun incrementSearchWidgetCFRDismissed() { fun incrementSearchWidgetCFRDismissed() = searchWidgetCFRDismissCount.increment()
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
searchWidgetCFRDismissCount + 1
).apply()
}
val isInSearchWidgetExperiment by booleanPreference( val isInSearchWidgetExperiment by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_is_in_search_widget_experiment), appContext.getPreferenceKey(R.string.pref_key_is_in_search_widget_experiment),
@ -298,17 +278,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
val shouldShowTrackingProtectionOnboarding: Boolean val shouldShowTrackingProtectionOnboarding: Boolean
get() = !isOverrideTPPopupsForPerformanceTest && get() = !isOverrideTPPopupsForPerformanceTest &&
(trackingProtectionOnboardingCount < trackingProtectionOnboardingMaximumCount && (trackingProtectionOnboardingCount.underMaxCount() &&
!trackingProtectionOnboardingShownThisSession) !trackingProtectionOnboardingShownThisSession)
var showSecretDebugMenuThisSession = false var showSecretDebugMenuThisSession = false
var showNotificationsSetting = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
val shouldShowSecurityPinWarningSync: Boolean val shouldShowSecurityPinWarningSync: Boolean
get() = loginsSecureWarningSyncCount < showLoginsSecureWarningSyncMaxCount get() = loginsSecureWarningSyncCount.underMaxCount()
val shouldShowSecurityPinWarning: Boolean val shouldShowSecurityPinWarning: Boolean
get() = loginsSecureWarningCount < showLoginsSecureWarningMaxCount get() = loginsSecureWarningCount.underMaxCount()
var shouldUseLightTheme by booleanPreference( var shouldUseLightTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_light_theme), appContext.getPreferenceKey(R.string.pref_key_light_theme),
@ -544,12 +523,6 @@ class Settings(private val appContext: Context) : PreferencesHolder {
return touchExplorationIsEnabled || switchServiceIsEnabled return touchExplorationIsEnabled || switchServiceIsEnabled
} }
val toolbarSettingString: String
get() = when {
shouldUseBottomToolbar -> appContext.getString(R.string.preference_bottom_toolbar)
else -> appContext.getString(R.string.preference_top_toolbar)
}
fun getDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType): Boolean = fun getDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType): Boolean =
preferences.getBoolean(type.getPreferenceKey(appContext), false) preferences.getBoolean(type.getPreferenceKey(appContext), false)
@ -571,30 +544,20 @@ class Settings(private val appContext: Context) : PreferencesHolder {
).apply() ).apply()
@VisibleForTesting(otherwise = PRIVATE) @VisibleForTesting(otherwise = PRIVATE)
internal val loginsSecureWarningSyncCount by intPreference( internal val loginsSecureWarningSyncCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync), appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync),
default = 0 maxCount = 1
) )
@VisibleForTesting(otherwise = PRIVATE) @VisibleForTesting(otherwise = PRIVATE)
internal val loginsSecureWarningCount by intPreference( internal val loginsSecureWarningCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning), appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning),
default = 0 maxCount = 1
) )
fun incrementShowLoginsSecureWarningCount() { fun incrementShowLoginsSecureWarningCount() = loginsSecureWarningCount.increment()
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning),
loginsSecureWarningCount + 1
).apply()
}
fun incrementShowLoginsSecureWarningSyncCount() { fun incrementShowLoginsSecureWarningSyncCount() = loginsSecureWarningSyncCount.increment()
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync),
loginsSecureWarningSyncCount + 1
).apply()
}
val shouldShowSearchSuggestions by booleanPreference( val shouldShowSearchSuggestions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions), appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions),
@ -616,21 +579,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = false default = false
) )
fun incrementVisitedInstallableCount() { fun incrementVisitedInstallableCount() = pwaInstallableVisitCount.increment()
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits),
pwaInstallableVisitCount + 1
).apply()
}
@VisibleForTesting(otherwise = PRIVATE) @VisibleForTesting(otherwise = PRIVATE)
internal val pwaInstallableVisitCount by intPreference( internal val pwaInstallableVisitCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits), appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits),
default = 0 maxCount = 3
) )
private val userNeedsToVisitInstallableSites: Boolean private val userNeedsToVisitInstallableSites: Boolean
get() = pwaInstallableVisitCount < pwaVisitsToShowPromptMaxCount get() = pwaInstallableVisitCount.underMaxCount()
val shouldShowPwaOnboarding: Boolean val shouldShowPwaOnboarding: Boolean
get() { get() {
@ -639,9 +597,8 @@ class Settings(private val appContext: Context) : PreferencesHolder {
// ShortcutManager::pinnedShortcuts is only available on Oreo+ // ShortcutManager::pinnedShortcuts is only available on Oreo+
if (!userKnowsAboutPwas && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (!userKnowsAboutPwas && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val alreadyHavePwaInstalled = val manager = appContext.getSystemService(ShortcutManager::class.java)
appContext.getSystemService(ShortcutManager::class.java) val alreadyHavePwaInstalled = manager != null && manager.pinnedShortcuts.size > 0
.pinnedShortcuts.size > 0
// Users know about PWAs onboarding if they already have PWAs installed. // Users know about PWAs onboarding if they already have PWAs installed.
userKnowsAboutPwas = alreadyHavePwaInstalled userKnowsAboutPwas = alreadyHavePwaInstalled
@ -656,17 +613,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
) )
@VisibleForTesting(otherwise = PRIVATE) @VisibleForTesting(otherwise = PRIVATE)
internal val trackingProtectionOnboardingCount by intPreference( internal val trackingProtectionOnboardingCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding), appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding),
0 maxCount = 1
) )
fun incrementTrackingProtectionOnboardingCount() { fun incrementTrackingProtectionOnboardingCount() {
trackingProtectionOnboardingShownThisSession = true trackingProtectionOnboardingShownThisSession = true
preferences.edit().putInt( trackingProtectionOnboardingCount.increment()
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding),
trackingProtectionOnboardingCount + 1
).apply()
} }
fun getSitePermissionsPhoneFeatureAction( fun getSitePermissionsPhoneFeatureAction(
@ -703,7 +657,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default: Int default: Int
) = preferences.getInt(AUTOPLAY_USER_SETTING, default) ) = preferences.getInt(AUTOPLAY_USER_SETTING, default)
fun getSitePermissionsPhoneFeatureAutoplayAction( private fun getSitePermissionsPhoneFeatureAutoplayAction(
feature: PhoneFeature, feature: PhoneFeature,
default: AutoplayAction = AutoplayAction.BLOCKED default: AutoplayAction = AutoplayAction.BLOCKED
) = preferences.getInt(feature.getPreferenceKey(appContext), default.toInt()).toAutoplayAction() ) = preferences.getInt(feature.getPreferenceKey(appContext), default.toInt()).toAutoplayAction()
@ -785,23 +739,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
0 0
) )
fun incrementNumTimesPrivateModeOpened() { fun incrementNumTimesPrivateModeOpened() = numTimesPrivateModeOpened.increment()
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_private_mode_opened),
numTimesPrivateModeOpened + 1
).apply()
}
private var showedPrivateModeContextualFeatureRecommender by booleanPreference( private var showedPrivateModeContextualFeatureRecommender by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_showed_private_mode_cfr), appContext.getPreferenceKey(R.string.pref_key_showed_private_mode_cfr),
default = false default = false
) )
private val numTimesPrivateModeOpened: Int private val numTimesPrivateModeOpened = counterPreference(
get() = preferences.getInt( appContext.getPreferenceKey(R.string.pref_key_private_mode_opened)
appContext.getPreferenceKey(R.string.pref_key_private_mode_opened), )
0
)
val showPrivateModeContextualFeatureRecommender: Boolean val showPrivateModeContextualFeatureRecommender: Boolean
get() { get() {
@ -809,9 +756,11 @@ class Settings(private val appContext: Context) : PreferencesHolder {
.getInstalledMozillaProducts(appContext as Application) .getInstalledMozillaProducts(appContext as Application)
.contains(MozillaProductDetector.MozillaProducts.FOCUS.productName) .contains(MozillaProductDetector.MozillaProducts.FOCUS.productName)
val showCondition = val showCondition = if (focusInstalled) {
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_INSTALLED && focusInstalled) || numTimesPrivateModeOpened.value == CFR_COUNT_CONDITION_FOCUS_INSTALLED
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED && !focusInstalled) } else {
numTimesPrivateModeOpened.value == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED
}
if (showCondition && !showedPrivateModeContextualFeatureRecommender) { if (showCondition && !showedPrivateModeContextualFeatureRecommender) {
showedPrivateModeContextualFeatureRecommender = true showedPrivateModeContextualFeatureRecommender = true

@ -0,0 +1,63 @@
/* 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 org.mozilla.fenix.components.settings
import android.content.SharedPreferences
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.verify
import mozilla.components.support.ktx.android.content.PreferencesHolder
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class CounterPreferenceTest {
@MockK private lateinit var prefs: SharedPreferences
@MockK private lateinit var editor: SharedPreferences.Editor
@Before
fun setup() {
MockKAnnotations.init(this)
every { prefs.getInt("key", 0) } returns 0
every { prefs.edit() } returns editor
every { editor.putInt("key", any()) } returns editor
every { editor.apply() } just Runs
}
@Test
fun `update value after increment`() {
val holder = CounterHolder()
assertEquals(0, holder.property.value)
holder.property.increment()
verify { editor.putInt("key", 1) }
}
@Test
fun `check if value is under max count`() {
val holder = CounterHolder(maxCount = 2)
every { prefs.getInt("key", 0) } returns 0
assertEquals(0, holder.property.value)
assertTrue(holder.property.underMaxCount())
every { prefs.getInt("key", 0) } returns 2
assertEquals(2, holder.property.value)
assertFalse(holder.property.underMaxCount())
}
private inner class CounterHolder(maxCount: Int = -1) : PreferencesHolder {
override val preferences = prefs
val property = counterPreference("key", maxCount)
}
}

@ -0,0 +1,67 @@
/* 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 org.mozilla.fenix.components.settings
import android.content.SharedPreferences
import io.mockk.Called
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.verify
import mozilla.components.support.ktx.android.content.PreferencesHolder
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class FeatureFlagPreferenceTest {
@MockK private lateinit var prefs: SharedPreferences
@MockK private lateinit var editor: SharedPreferences.Editor
@Before
fun setup() {
MockKAnnotations.init(this)
every { prefs.getBoolean("key", false) } returns true
every { prefs.edit() } returns editor
every { editor.putBoolean("key", any()) } returns editor
every { editor.apply() } just Runs
}
@Test
fun `acts like boolean preference if feature flag is true`() {
val holder = FeatureFlagHolder(featureFlag = true)
assertTrue(holder.property)
verify { prefs.getBoolean("key", false) }
holder.property = false
verify { editor.putBoolean("key", false) }
}
@Test
fun `no-op if feature flag is false`() {
val holder = FeatureFlagHolder(featureFlag = false)
assertFalse(holder.property)
holder.property = true
holder.property = false
verify { prefs wasNot Called }
verify { editor wasNot Called }
}
private inner class FeatureFlagHolder(featureFlag: Boolean) : PreferencesHolder {
override val preferences = prefs
var property by featureFlagPreference(
"key",
default = false,
featureFlag = featureFlag
)
}
}

@ -128,13 +128,13 @@ class SettingsTest {
fun showLoginsDialogWarningSync() { fun showLoginsDialogWarningSync() {
// When just created // When just created
// Then // Then
assertEquals(0, settings.loginsSecureWarningSyncCount) assertEquals(0, settings.loginsSecureWarningSyncCount.value)
// When // When
settings.incrementShowLoginsSecureWarningSyncCount() settings.incrementShowLoginsSecureWarningSyncCount()
// Then // Then
assertEquals(1, settings.loginsSecureWarningSyncCount) assertEquals(1, settings.loginsSecureWarningSyncCount.value)
} }
@Test @Test
@ -154,13 +154,13 @@ class SettingsTest {
fun showLoginsDialogWarning() { fun showLoginsDialogWarning() {
// When just created // When just created
// Then // Then
assertEquals(0, settings.loginsSecureWarningCount) assertEquals(0, settings.loginsSecureWarningCount.value)
// When // When
settings.incrementShowLoginsSecureWarningCount() settings.incrementShowLoginsSecureWarningCount()
// Then // Then
assertEquals(1, settings.loginsSecureWarningCount) assertEquals(1, settings.loginsSecureWarningCount.value)
} }
@Test @Test

Loading…
Cancel
Save