fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt

1335 lines
49 KiB
Kotlin

/* 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 android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES
import android.app.Application
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.content.pm.ShortcutManager
import android.os.Build
import android.view.accessibility.AccessibilityManager
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import androidx.lifecycle.LifecycleOwner
import mozilla.components.concept.engine.Engine.HttpsOnlyMode
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
import mozilla.components.service.contile.ContileTopSitesProvider
import mozilla.components.support.ktx.android.content.PreferencesHolder
import mozilla.components.support.ktx.android.content.booleanPreference
import mozilla.components.support.ktx.android.content.floatPreference
import mozilla.components.support.ktx.android.content.intPreference
import mozilla.components.support.ktx.android.content.longPreference
import mozilla.components.support.ktx.android.content.stringPreference
import mozilla.components.support.ktx.android.content.stringSetPreference
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.FeatureFlags.historyImprovementFeatures
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
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.settings.lazyFeatureFlagPreference
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.nimbus.DefaultBrowserMessage
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.nimbus.HomeScreenSection
import org.mozilla.fenix.nimbus.MessageSurfaceId
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.deletebrowsingdata.DeleteBrowsingDataOnQuitType
import org.mozilla.fenix.settings.logins.SavedLoginsSortingStrategyMenu
import org.mozilla.fenix.settings.logins.SortingStrategy
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_ALL
import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_AUDIBLE
import org.mozilla.fenix.wallpapers.WallpaperManager
import java.security.InvalidParameterException
import java.util.UUID
private const val AUTOPLAY_USER_SETTING = "AUTOPLAY_USER_SETTING"
/**
* A simple wrapper for SharedPreferences that makes reading preference a little bit easier.
* @param appContext Reference to application context.
*/
@Suppress("LargeClass", "TooManyFunctions")
class Settings(private val appContext: Context) : PreferencesHolder {
companion object {
const val FENIX_PREFERENCES = "fenix_preferences"
private const val BLOCKED_INT = 0
private const val ASK_TO_ALLOW_INT = 1
private const val ALLOWED_INT = 2
private const val CFR_COUNT_CONDITION_FOCUS_INSTALLED = 1
private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3
private const val INACTIVE_TAB_MINIMUM_TO_SHOW_AUTO_CLOSE_DIALOG = 20
const val FOUR_HOURS_MS = 60 * 60 * 4 * 1000L
const val ONE_DAY_MS = 60 * 60 * 24 * 1000L
const val THREE_DAYS_MS = 3 * ONE_DAY_MS
const val ONE_WEEK_MS = 60 * 60 * 24 * 7 * 1000L
const val ONE_MONTH_MS = (60 * 60 * 24 * 365 * 1000L) / 12
/**
* The minimum number a search groups should contain.
* Filtering is applied depending on the [historyImprovementFeatures] flag value.
*/
const val SEARCH_GROUP_MINIMUM_SITES: Int = 2
// The maximum number of top sites to display.
const val TOP_SITES_MAX_COUNT = 16
/**
* Only fetch top sites from the [ContileTopSitesProvider] when the number of default and
* pinned sites are below this maximum threshold.
*/
const val TOP_SITES_PROVIDER_MAX_THRESHOLD = 8
private fun Action.toInt() = when (this) {
Action.BLOCKED -> BLOCKED_INT
Action.ASK_TO_ALLOW -> ASK_TO_ALLOW_INT
Action.ALLOWED -> ALLOWED_INT
}
private fun AutoplayAction.toInt() = when (this) {
AutoplayAction.BLOCKED -> BLOCKED_INT
AutoplayAction.ALLOWED -> ALLOWED_INT
}
private fun Int.toAction() = when (this) {
BLOCKED_INT -> Action.BLOCKED
ASK_TO_ALLOW_INT -> Action.ASK_TO_ALLOW
ALLOWED_INT -> Action.ALLOWED
else -> throw InvalidParameterException("$this is not a valid SitePermissionsRules.Action")
}
private fun Int.toAutoplayAction() = when (this) {
BLOCKED_INT -> AutoplayAction.BLOCKED
ALLOWED_INT -> AutoplayAction.ALLOWED
// Users from older versions may have saved invalid values. Migrate them to BLOCKED
ASK_TO_ALLOW_INT -> AutoplayAction.BLOCKED
else -> throw InvalidParameterException("$this is not a valid SitePermissionsRules.AutoplayAction")
}
}
@VisibleForTesting
internal val isCrashReportEnabledInBuild: Boolean =
BuildConfig.CRASH_REPORTING && Config.channel.isReleased
override val preferences: SharedPreferences =
appContext.getSharedPreferences(FENIX_PREFERENCES, MODE_PRIVATE)
/**
* Indicates whether or not top sites should be shown on the home screen.
*/
var showTopSitesFeature by lazyFeatureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_show_top_sites),
featureFlag = true,
default = { homescreenSections[HomeScreenSection.TOP_SITES] == true },
)
var numberOfAppLaunches by intPreference(
appContext.getPreferenceKey(R.string.pref_key_times_app_opened),
default = 0
)
var lastReviewPromptTimeInMillis by longPreference(
appContext.getPreferenceKey(R.string.pref_key_last_review_prompt_shown_time),
default = 0L
)
var lastCfrShownTimeInMillis by longPreference(
appContext.getPreferenceKey(R.string.pref_key_last_cfr_shown_time),
default = 0L
)
val canShowCfr: Boolean
get() = (System.currentTimeMillis() - lastCfrShownTimeInMillis) > THREE_DAYS_MS
var forceEnableZoom by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_accessibility_force_enable_zoom),
default = false
)
var adjustCampaignId by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_adjust_campaign),
default = ""
)
var adjustNetwork by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_adjust_network),
default = ""
)
var adjustAdGroup by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_adjust_adgroup),
default = ""
)
var adjustCreative by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_adjust_creative),
default = ""
)
var contileContextId by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_contile_context_id),
default = ""
)
var currentWallpaper by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_current_wallpaper),
default = WallpaperManager.defaultWallpaper.name
)
var wallpapersSwitchedByLogoTap by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_wallpapers_switched_by_logo_tap),
default = true
)
var openLinksInAPrivateTab by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_open_links_in_a_private_tab),
default = false
)
var allowScreenshotsInPrivateMode by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_allow_screenshots_in_private_mode),
default = false
)
var shouldReturnToBrowser by booleanPreference(
appContext.getString(R.string.pref_key_return_to_browser),
false
)
var defaultSearchEngineName by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_search_engine),
default = ""
)
var openInAppOpened by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_open_in_app_opened),
default = false
)
var installPwaOpened by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_opened),
default = false
)
var showCollectionsPlaceholderOnHome by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_collections_placeholder_home),
default = true
)
val isCrashReportingEnabled: Boolean
get() = isCrashReportEnabledInBuild &&
preferences.getBoolean(
appContext.getPreferenceKey(R.string.pref_key_crash_reporter),
true
)
val isRemoteDebuggingEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_remote_debugging),
default = false
)
val isTelemetryEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_telemetry),
default = true
)
val isMarketingTelemetryEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_marketing_telemetry),
default = true
)
var isExperimentationEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_experimentation),
default = true
)
var isOverrideTPPopupsForPerformanceTest = false
var showSecretDebugMenuThisSession = false
val shouldShowSecurityPinWarningSync: Boolean
get() = loginsSecureWarningSyncCount.underMaxCount()
val shouldShowSecurityPinWarning: Boolean
get() = secureWarningCount.underMaxCount()
var shouldShowPrivacyPopWindow by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_privacy_pop_window),
default = true
)
var shouldUseLightTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_light_theme),
default = false
)
var shouldUseAutoSize by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_accessibility_auto_size),
default = true
)
var fontSizeFactor by floatPreference(
appContext.getPreferenceKey(R.string.pref_key_accessibility_font_scale),
default = 1f
)
val shouldShowHistorySuggestions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_search_browsing_history),
default = true
)
val shouldShowBookmarkSuggestions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_search_bookmarks),
default = true
)
val shouldShowSyncedTabsSuggestions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_search_synced_tabs),
default = true
)
val shouldShowClipboardSuggestions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_clipboard_suggestions),
default = true
)
val shouldShowSearchShortcuts by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_search_engine_shortcuts),
default = false
)
private val defaultBrowserFeature: DefaultBrowserMessage by lazy {
FxNimbus.features.defaultBrowserMessage.value()
}
fun isDefaultBrowserMessageLocation(surfaceId: MessageSurfaceId): Boolean =
defaultBrowserFeature.messageLocation?.let { experimentalSurfaceId ->
if (experimentalSurfaceId == surfaceId) {
val browsers = BrowsersCache.all(appContext)
!browsers.isFirefoxDefaultBrowser
} else {
false
}
} ?: false
var gridTabView by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tab_view_grid),
default = true
)
var manuallyCloseTabs by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_close_tabs_manually),
default = true
)
var closeTabsAfterOneDay by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_close_tabs_after_one_day),
default = false
)
var closeTabsAfterOneWeek by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_close_tabs_after_one_week),
default = false
)
var closeTabsAfterOneMonth by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_close_tabs_after_one_month),
default = false
)
var allowThirdPartyRootCerts by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_allow_third_party_root_certs),
default = false
)
var nimbusUsePreview by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_nimbus_use_preview),
default = false
)
val isFirstRun: Boolean =
if (!preferences.contains(appContext.getPreferenceKey(R.string.pref_key_is_first_run))) {
preferences.edit()
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_is_first_run),
false
)
.apply()
true
} else {
false
}
/**
* Indicates the last time when the user was interacting with the [BrowserFragment],
* This is useful to determine if the user has to start on the [HomeFragment]
* or it should go directly to the [BrowserFragment].
*/
var lastBrowseActivity by longPreference(
appContext.getPreferenceKey(R.string.pref_key_last_browse_activity_time),
default = timeNowInMillis()
)
/**
* Indicates if the user has selected the option to start on the home screen after
* four hours of inactivity.
*/
var openHomepageAfterFourHoursOfInactivity by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_start_on_home_after_four_hours),
default = true
)
/**
* Indicates if the user has selected the option to always start on the home screen.
*/
var alwaysOpenTheHomepageWhenOpeningTheApp by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_start_on_home_always),
default = false
)
/**
* Indicates if the user has selected the option to never start on the home screen and have
* their last tab opened.
*/
var alwaysOpenTheLastTabWhenOpeningTheApp by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_start_on_home_never),
default = false
)
/**
* Indicates if the user should start on the home screen, based on the user's preferences.
*/
fun shouldStartOnHome(): Boolean {
return when {
openHomepageAfterFourHoursOfInactivity -> timeNowInMillis() - lastBrowseActivity >= FOUR_HOURS_MS
alwaysOpenTheHomepageWhenOpeningTheApp -> true
alwaysOpenTheLastTabWhenOpeningTheApp -> false
else -> false
}
}
/**
* Indicates if the user has enabled the inactive tabs feature.
*/
var inactiveTabsAreEnabled by featureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_inactive_tabs),
default = FeatureFlags.inactiveTabs,
featureFlag = FeatureFlags.inactiveTabs
)
/**
* Indicates if the Firefox logo on the home screen should be animated,
* to show users that they can change the wallpaper by tapping on the Firefox logo.
*/
var shouldAnimateFirefoxLogo by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_logo_animation),
default = true,
)
/**
* Indicates if the user has enabled the search term tab groups feature.
*/
var searchTermTabGroupsAreEnabled by lazyFeatureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_search_term_tab_groups),
default = { FxNimbus.features.searchTermGroups.value().enabled },
featureFlag = FeatureFlags.tabGroupFeature
)
@VisibleForTesting
internal fun timeNowInMillis(): Long = System.currentTimeMillis()
fun getTabTimeout(): Long = when {
closeTabsAfterOneDay -> ONE_DAY_MS
closeTabsAfterOneWeek -> ONE_WEEK_MS
closeTabsAfterOneMonth -> ONE_MONTH_MS
else -> Long.MAX_VALUE
}
enum class TabView {
GRID, LIST
}
fun getTabViewPingString() = if (gridTabView) TabView.GRID.name else TabView.LIST.name
enum class TabTimout {
ONE_DAY, ONE_WEEK, ONE_MONTH, MANUAL
}
fun getTabTimeoutPingString(): String = when {
closeTabsAfterOneDay -> {
TabTimout.ONE_DAY.name
}
closeTabsAfterOneWeek -> {
TabTimout.ONE_WEEK.name
}
closeTabsAfterOneMonth -> {
TabTimout.ONE_MONTH.name
}
else -> {
TabTimout.MANUAL.name
}
}
fun getTabTimeoutString(): String = when {
closeTabsAfterOneDay -> {
appContext.getString(R.string.close_tabs_after_one_day_summary)
}
closeTabsAfterOneWeek -> {
appContext.getString(R.string.close_tabs_after_one_week_summary)
}
closeTabsAfterOneMonth -> {
appContext.getString(R.string.close_tabs_after_one_month_summary)
}
else -> {
appContext.getString(R.string.close_tabs_manually_summary)
}
}
var shouldUseDarkTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_dark_theme),
default = false
)
var shouldFollowDeviceTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_follow_device_theme),
default = false
)
var shouldUseHttpsOnly by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_https_only),
default = false
)
var shouldUseHttpsOnlyInAllTabs by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_https_only_in_all_tabs),
default = true
)
var shouldUseHttpsOnlyInPrivateTabsOnly by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_https_only_in_private_tabs),
default = false
)
var shouldUseTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection),
default = true
)
/**
* Declared as a function for performance purposes. This could be declared as a variable using
* booleanPreference like other members of this class. However, doing so will make it so it will
* be initialized once Settings.kt is first called, which in turn will call `isDefaultBrowserBlocking()`.
* This will lead to a performance regression since that function can be expensive to call.
*/
fun checkIfFenixIsDefaultBrowserOnAppResume(): Boolean {
val prefKey = appContext.getPreferenceKey(R.string.pref_key_default_browser)
val isDefaultBrowserNow = isDefaultBrowserBlocking()
val wasDefaultBrowserOnLastResume = this.preferences.getBoolean(prefKey, isDefaultBrowserNow)
this.preferences.edit().putBoolean(prefKey, isDefaultBrowserNow).apply()
return isDefaultBrowserNow && !wasDefaultBrowserOnLastResume
}
/**
* This function is "blocking" since calling this can take approx. 30-40ms (timing taken on a
* G5+).
*/
fun isDefaultBrowserBlocking(): Boolean {
val browsers = BrowsersCache.all(appContext)
return browsers.isDefaultBrowser
}
var defaultBrowserNotificationDisplayed by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_should_show_default_browser_notification),
default = false
)
fun shouldShowDefaultBrowserNotification(): Boolean {
return !defaultBrowserNotificationDisplayed && !isDefaultBrowserBlocking()
}
val shouldUseAutoBatteryTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_auto_battery_theme),
default = false
)
val useStandardTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option),
true
)
val useStrictTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_strict_default),
false
)
val useCustomTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_option),
false
)
@VisibleForTesting(otherwise = PRIVATE)
fun setStrictETP() {
preferences.edit().putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_strict_default),
true
).apply()
preferences.edit().putBoolean(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_standard_option),
false
).apply()
appContext.components.let {
val policy = it.core.trackingProtectionPolicyFactory
.createTrackingProtectionPolicy()
it.useCases.settingsUseCases.updateTrackingProtection.invoke(policy)
it.useCases.sessionUseCases.reload.invoke()
}
}
val blockCookiesInCustomTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies),
true
)
val blockCookiesSelectionInCustomTrackingProtection by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select),
appContext.getString(R.string.social)
)
val blockTrackingContentInCustomTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_tracking_content),
true
)
val blockTrackingContentSelectionInCustomTrackingProtection by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_tracking_content_select),
appContext.getString(R.string.all)
)
val blockCryptominersInCustomTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cryptominers),
true
)
val blockFingerprintersInCustomTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_fingerprinters),
true
)
val blockRedirectTrackersInCustomTrackingProtection by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_redirect_trackers),
true
)
/**
* Prefer to use a fixed top toolbar when:
* - a talkback service is enabled or
* - switch access is enabled.
*
* This is automatically inferred based on the current system status. Not a setting in our app.
*/
val shouldUseFixedTopToolbar: Boolean
get() {
return touchExplorationIsEnabled || switchServiceIsEnabled
}
var lastKnownMode: BrowsingMode = BrowsingMode.Normal
get() {
val lastKnownModeWasPrivate = preferences.getBoolean(
appContext.getPreferenceKey(R.string.pref_key_last_known_mode_private),
false
)
return if (lastKnownModeWasPrivate) {
BrowsingMode.Private
} else {
BrowsingMode.Normal
}
}
set(value) {
val lastKnownModeWasPrivate = (value == BrowsingMode.Private)
preferences.edit()
.putBoolean(
appContext.getPreferenceKey(R.string.pref_key_last_known_mode_private),
lastKnownModeWasPrivate
)
.apply()
field = value
}
var shouldDeleteBrowsingDataOnQuit by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit),
default = false
)
var deleteOpenTabs by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_delete_open_tabs_now),
default = true
)
var deleteBrowsingHistory by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_delete_browsing_history_now),
default = true
)
var deleteCookies by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_delete_cookies_now),
default = true
)
var deleteCache by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_delete_caches_now),
default = true
)
var deleteSitePermissions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_delete_permissions_now),
default = true
)
var deleteDownloads by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_delete_downloads_now),
default = true
)
var shouldUseBottomToolbar by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_toolbar_bottom),
// Default accessibility users to top toolbar
default = !touchExplorationIsEnabled && !switchServiceIsEnabled
)
val toolbarPosition: ToolbarPosition
get() = if (shouldUseBottomToolbar) ToolbarPosition.BOTTOM else ToolbarPosition.TOP
/**
* Check each active accessibility service to see if it can perform gestures, if any can,
* then it is *likely* a switch service is enabled. We are assuming this to be the case based on #7486
*/
val switchServiceIsEnabled: Boolean
get() {
val accessibilityManager =
appContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
accessibilityManager?.getEnabledAccessibilityServiceList(0)?.let { activeServices ->
for (service in activeServices) {
if (service.capabilities.and(CAPABILITY_CAN_PERFORM_GESTURES) == 1) {
return true
}
}
}
return false
}
val touchExplorationIsEnabled: Boolean
get() {
val accessibilityManager =
appContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as? AccessibilityManager
return accessibilityManager?.isTouchExplorationEnabled ?: false
}
val accessibilityServicesEnabled: Boolean
get() {
return touchExplorationIsEnabled || switchServiceIsEnabled
}
fun getDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType): Boolean =
preferences.getBoolean(type.getPreferenceKey(appContext), false)
fun setDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType, value: Boolean) {
preferences.edit().putBoolean(type.getPreferenceKey(appContext), value).apply()
}
fun shouldDeleteAnyDataOnQuit() =
DeleteBrowsingDataOnQuitType.values().any { getDeleteDataOnQuit(it) }
val passwordsEncryptionKeyGenerated by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_encryption_key_generated),
false
)
fun recordPasswordsEncryptionKeyGenerated() = preferences.edit().putBoolean(
appContext.getPreferenceKey(R.string.pref_key_encryption_key_generated),
true
).apply()
@VisibleForTesting(otherwise = PRIVATE)
internal val loginsSecureWarningSyncCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync),
maxCount = 1
)
@VisibleForTesting(otherwise = PRIVATE)
internal val secureWarningCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_secure_warning),
maxCount = 1
)
fun incrementSecureWarningCount() = secureWarningCount.increment()
fun incrementShowLoginsSecureWarningSyncCount() = loginsSecureWarningSyncCount.increment()
val shouldShowSearchSuggestions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions),
default = true
)
val shouldAutocompleteInAwesomebar by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_enable_autocomplete_urls),
default = true
)
var defaultTopSitesAdded by booleanPreference(
appContext.getPreferenceKey(R.string.default_top_sites_added),
default = false
)
var shouldShowSearchSuggestionsInPrivate by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions_in_private),
default = false
)
var showSearchSuggestionsInPrivateOnboardingFinished by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions_in_private_onboarding),
default = false
)
/**
* Indicates if the home onboarding dialog has already shown before.
*/
var hasShownHomeOnboardingDialog by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_has_shown_home_onboarding),
default = false
)
fun incrementVisitedInstallableCount() = pwaInstallableVisitCount.increment()
@VisibleForTesting(otherwise = PRIVATE)
internal val pwaInstallableVisitCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits),
maxCount = 3
)
private val userNeedsToVisitInstallableSites: Boolean
get() = pwaInstallableVisitCount.underMaxCount()
val shouldShowPwaCfr: Boolean
get() {
if (!canShowCfr) return false
// We only want to show this on the 3rd time a user visits a site
if (userNeedsToVisitInstallableSites) return false
// ShortcutManager::pinnedShortcuts is only available on Oreo+
if (!userKnowsAboutPwas && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = appContext.getSystemService(ShortcutManager::class.java)
val alreadyHavePwaInstalled = manager != null && manager.pinnedShortcuts.size > 0
// Users know about PWAs onboarding if they already have PWAs installed.
userKnowsAboutPwas = alreadyHavePwaInstalled
}
// Show dialog only if user does not know abut PWAs
return !userKnowsAboutPwas
}
var userKnowsAboutPwas by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_user_knows_about_pwa),
default = false
)
var shouldShowOpenInAppBanner by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_should_show_open_in_app_banner),
default = true
)
val shouldShowOpenInAppCfr: Boolean
get() = canShowCfr && shouldShowOpenInAppBanner
var shouldShowAutoCloseTabsBanner by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_should_show_auto_close_tabs_banner),
default = true
)
var shouldShowInactiveTabsOnboardingPopup by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_should_show_inactive_tabs_popup),
default = true
)
/**
* Indicates if the auto-close dialog for inactive tabs has been dismissed before.
*/
var hasInactiveTabsAutoCloseDialogBeenDismissed by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_has_inactive_tabs_auto_close_dialog_dismissed),
default = false
)
/**
* Indicates if the auto-close dialog should be visible based on
* if the user has dismissed it before [hasInactiveTabsAutoCloseDialogBeenDismissed],
* if the minimum number of tabs has been accumulated [numbersOfTabs]
* and if the auto-close setting is already set to [closeTabsAfterOneMonth].
*/
fun shouldShowInactiveTabsAutoCloseDialog(numbersOfTabs: Int): Boolean {
return !hasInactiveTabsAutoCloseDialogBeenDismissed &&
numbersOfTabs >= INACTIVE_TAB_MINIMUM_TO_SHOW_AUTO_CLOSE_DIALOG &&
!closeTabsAfterOneMonth
}
/**
* Indicates if the jump back in CRF should be shown.
*/
var shouldShowJumpBackInCFR by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_should_show_jump_back_in_tabs_popup),
default = true
)
fun getSitePermissionsPhoneFeatureAction(
feature: PhoneFeature,
default: Action = Action.ASK_TO_ALLOW
) =
preferences.getInt(feature.getPreferenceKey(appContext), default.toInt()).toAction()
/**
* Saves the user selected autoplay setting.
*
* Under the hood, autoplay is represented by two settings, [AUTOPLAY_AUDIBLE] and
* [AUTOPLAY_INAUDIBLE]. The user selection cannot be inferred from the combination of these
* settings because, while on [AUTOPLAY_ALLOW_ON_WIFI], they will be indistinguishable from
* either [AUTOPLAY_ALLOW_ALL] or [AUTOPLAY_BLOCK_ALL]. Because of this, we are forced to save
* the user selected setting as well.
*/
fun setAutoplayUserSetting(
autoplaySetting: Int
) {
preferences.edit().putInt(AUTOPLAY_USER_SETTING, autoplaySetting).apply()
}
/**
* Gets the user selected autoplay setting.
*
* Under the hood, autoplay is represented by two settings, [AUTOPLAY_AUDIBLE] and
* [AUTOPLAY_INAUDIBLE]. The user selection cannot be inferred from the combination of these
* settings because, while on [AUTOPLAY_ALLOW_ON_WIFI], they will be indistinguishable from
* either [AUTOPLAY_ALLOW_ALL] or [AUTOPLAY_BLOCK_ALL]. Because of this, we are forced to save
* the user selected setting as well.
*/
fun getAutoplayUserSetting() = preferences.getInt(AUTOPLAY_USER_SETTING, AUTOPLAY_BLOCK_AUDIBLE)
private fun getSitePermissionsPhoneFeatureAutoplayAction(
feature: PhoneFeature,
default: AutoplayAction = AutoplayAction.BLOCKED
) = preferences.getInt(feature.getPreferenceKey(appContext), default.toInt()).toAutoplayAction()
fun setSitePermissionsPhoneFeatureAction(
feature: PhoneFeature,
value: Action
) {
preferences.edit().putInt(feature.getPreferenceKey(appContext), value.toInt()).apply()
}
fun getSitePermissionsCustomSettingsRules(): SitePermissionsRules {
return SitePermissionsRules(
notification = getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION),
microphone = getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE),
location = getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION),
camera = getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA),
autoplayAudible = getSitePermissionsPhoneFeatureAutoplayAction(
feature = PhoneFeature.AUTOPLAY_AUDIBLE,
default = AutoplayAction.BLOCKED
),
autoplayInaudible = getSitePermissionsPhoneFeatureAutoplayAction(
feature = PhoneFeature.AUTOPLAY_INAUDIBLE,
default = AutoplayAction.ALLOWED
),
persistentStorage = getSitePermissionsPhoneFeatureAction(PhoneFeature.PERSISTENT_STORAGE),
crossOriginStorageAccess = getSitePermissionsPhoneFeatureAction(PhoneFeature.CROSS_ORIGIN_STORAGE_ACCESS),
mediaKeySystemAccess = getSitePermissionsPhoneFeatureAction(PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS)
)
}
fun setSitePermissionSettingListener(lifecycleOwner: LifecycleOwner, listener: () -> Unit) {
val sitePermissionKeys = listOf(
PhoneFeature.NOTIFICATION,
PhoneFeature.MICROPHONE,
PhoneFeature.LOCATION,
PhoneFeature.CAMERA,
PhoneFeature.AUTOPLAY_AUDIBLE,
PhoneFeature.AUTOPLAY_INAUDIBLE,
PhoneFeature.PERSISTENT_STORAGE,
PhoneFeature.CROSS_ORIGIN_STORAGE_ACCESS,
PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS
).map { it.getPreferenceKey(appContext) }
preferences.registerOnSharedPreferenceChangeListener(lifecycleOwner) { _, key ->
if (key in sitePermissionKeys) listener.invoke()
}
}
var shouldShowVoiceSearch by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_voice_search),
default = true
)
/**
* Used in [SearchDialogFragment.kt], [SearchFragment.kt] (deprecated), and [PairFragment.kt]
* to see if we need to check for camera permissions before using the QR code scanner.
*/
var shouldShowCameraPermissionPrompt by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_camera_permissions_needed),
default = true
)
/**
* Sets the state of permissions that have been checked, where [false] denotes already checked
* and [true] denotes needing to check. See [shouldShowCameraPermissionPrompt].
*/
var setCameraPermissionNeededState by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_camera_permissions_needed),
default = true
)
var shouldPromptToSaveLogins by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_save_logins),
default = true
)
var shouldAutofillLogins by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_autofill_logins),
default = true
)
var lastPlacesStorageMaintenance by longPreference(
appContext.getPreferenceKey(R.string.pref_key_last_maintenance),
default = 0
)
fun addSearchWidgetInstalled(count: Int) {
val key = appContext.getPreferenceKey(R.string.pref_key_search_widget_installed)
val newValue = preferences.getInt(key, 0) + count
preferences.edit()
.putInt(key, newValue)
.apply()
}
val searchWidgetInstalled: Boolean
get() = 0 < preferences.getInt(
appContext.getPreferenceKey(R.string.pref_key_search_widget_installed),
0
)
fun incrementNumTimesPrivateModeOpened() = numTimesPrivateModeOpened.increment()
var showedPrivateModeContextualFeatureRecommender by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_showed_private_mode_cfr),
default = false
)
private val numTimesPrivateModeOpened = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_private_mode_opened)
)
val shouldShowPrivateModeCfr: Boolean
get() {
if (!canShowCfr) return false
val focusInstalled = MozillaProductDetector
.getInstalledMozillaProducts(appContext as Application)
.contains(MozillaProductDetector.MozillaProducts.FOCUS.productName)
val showCondition = if (focusInstalled) {
numTimesPrivateModeOpened.value >= CFR_COUNT_CONDITION_FOCUS_INSTALLED
} else {
numTimesPrivateModeOpened.value >= CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED
}
if (showCondition && !showedPrivateModeContextualFeatureRecommender) {
return true
}
return false
}
var openLinksInExternalApp by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_open_links_in_external_app),
default = false
)
var allowDomesticChinaFxaServer by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_allow_domestic_china_fxa_server),
default = true
)
var overrideFxAServer by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_override_fxa_server),
default = ""
)
var overrideSyncTokenServer by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_override_sync_tokenserver),
default = ""
)
var overrideAmoUser by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_override_amo_user),
default = ""
)
var overrideAmoCollection by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_override_amo_collection),
default = ""
)
fun amoCollectionOverrideConfigured(): Boolean {
return overrideAmoUser.isNotEmpty() || overrideAmoCollection.isNotEmpty()
}
var topSitesSize by intPreference(
appContext.getPreferenceKey(R.string.pref_key_top_sites_size),
default = 0
)
val topSitesMaxLimit by intPreference(
appContext.getPreferenceKey(R.string.pref_key_top_sites_max_limit),
default = TOP_SITES_MAX_COUNT
)
var openTabsCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_open_tabs_count),
0
)
var mobileBookmarksSize by intPreference(
appContext.getPreferenceKey(R.string.pref_key_mobile_bookmarks_size),
0
)
var desktopBookmarksSize by intPreference(
appContext.getPreferenceKey(R.string.pref_key_desktop_bookmarks_size),
0
)
/**
* Storing number of installed add-ons for telemetry purposes
*/
var installedAddonsCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_installed_addons_count),
0
)
/**
* Storing the list of installed add-ons for telemetry purposes
*/
var installedAddonsList by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_installed_addons_list),
default = ""
)
/**
* Storing number of enabled add-ons for telemetry purposes
*/
var enabledAddonsCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_enabled_addons_count),
0
)
/**
* Storing the list of enabled add-ons for telemetry purposes
*/
var enabledAddonsList by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_enabled_addons_list),
default = ""
)
private var savedLoginsSortingStrategyString by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_saved_logins_sorting_strategy),
default = SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort.strategyString
)
val savedLoginsMenuHighlightedItem: SavedLoginsSortingStrategyMenu.Item
get() = SavedLoginsSortingStrategyMenu.Item.fromString(savedLoginsSortingStrategyString)
var savedLoginsSortingStrategy: SortingStrategy
get() {
return when (savedLoginsMenuHighlightedItem) {
SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort -> SortingStrategy.Alphabetically
SavedLoginsSortingStrategyMenu.Item.LastUsedSort -> SortingStrategy.LastUsed
}
}
set(value) {
savedLoginsSortingStrategyString = when (value) {
is SortingStrategy.Alphabetically ->
SavedLoginsSortingStrategyMenu.Item.AlphabeticallySort.strategyString
is SortingStrategy.LastUsed ->
SavedLoginsSortingStrategyMenu.Item.LastUsedSort.strategyString
}
}
var isPullToRefreshEnabledInBrowser by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_website_pull_to_refresh),
default = true
)
var isDynamicToolbarEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_dynamic_toolbar),
default = true
)
var isSwipeToolbarToSwitchTabsEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_swipe_toolbar_switch_tabs),
default = true
)
var addressFeature by featureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_show_address_feature),
default = false,
featureFlag = FeatureFlags.addressesFeature
)
private var isHistoryMetadataEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_history_metadata_feature),
default = false
)
private val homescreenSections: Map<HomeScreenSection, Boolean> by lazy {
FxNimbus.features.homescreen.value().sectionsEnabled
}
var historyMetadataUIFeature by lazyFeatureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_history_metadata_feature),
default = { homescreenSections[HomeScreenSection.RECENT_EXPLORATIONS] == true },
featureFlag = FeatureFlags.historyMetadataUIFeature || isHistoryMetadataEnabled
)
/**
* Indicates if the recent tabs functionality should be visible.
* Returns true if the [FeatureFlags.showRecentTabsFeature] and [R.string.pref_key_recent_tabs] are true.
*/
var showRecentTabsFeature by lazyFeatureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_recent_tabs),
featureFlag = FeatureFlags.showRecentTabsFeature,
default = { homescreenSections[HomeScreenSection.JUMP_BACK_IN] == true },
)
/**
* Indicates if the recent saved bookmarks functionality should be visible.
* Returns true if the [FeatureFlags.showRecentTabsFeature] and [R.string.pref_key_recent_bookmarks] are true.
*/
var showRecentBookmarksFeature by lazyFeatureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_recent_bookmarks),
default = { homescreenSections[HomeScreenSection.RECENTLY_SAVED] == true },
featureFlag = FeatureFlags.recentBookmarksFeature
)
/**
* Storing desktop item checkbox value in the home screen menu.
* If set to true, next opened tab from home screen will be opened in desktop mode.
*/
var openNextTabInDesktopMode by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_open_next_tab_desktop_mode),
default = false
)
var signedInFxaAccount by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_fxa_signed_in),
default = false
)
/**
* Storing the user choice from the "Credit cards" settings for whether save and autofill cards
* should be enabled or not.
* If set to `true` when the user focuses on credit card fields in the webpage an Android prompt letting her
* select the card details to be automatically filled will appear.
*/
var shouldAutofillCreditCardDetails by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_credit_cards_save_and_autofill_cards),
default = true
)
/**
* Stores the user choice from the "Autofill Addresses" settings for whether
* save and autofill addresses should be enabled or not.
* If set to `true` when the user focuses on address fields in a webpage an Android prompt is shown,
* allowing the selection of an address details to be automatically filled in the webpage fields.
*/
var shouldAutofillAddressDetails by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_addresses_save_and_autofill_addresses),
default = true
)
var showPocketRecommendationsFeature by lazyFeatureFlagPreference(
appContext.getPreferenceKey(R.string.pref_key_pocket_homescreen_recommendations),
featureFlag = FeatureFlags.isPocketRecommendationsFeatureEnabled(appContext),
default = { homescreenSections[HomeScreenSection.POCKET] == true },
)
/**
* Get the profile id to use in the sponsored stories communications with the Pocket endpoint.
*/
val pocketSponsoredStoriesProfileId by stringPreference(
appContext.getPreferenceKey(R.string.pref_key_pocket_sponsored_stories_profile),
default = UUID.randomUUID().toString(),
persistDefaultIfNotExists = true
)
/**
* Indicates if the Contile functionality should be visible.
*/
var showContileFeature by lazyFeatureFlagPreference(
key = appContext.getPreferenceKey(R.string.pref_key_enable_contile),
default = { homescreenSections[HomeScreenSection.CONTILE_TOP_SITES] == true },
featureFlag = true,
)
/**
* Indicates if the Task Continuity enhancements are enabled.
*/
var enableTaskContinuityEnhancements by featureFlagPreference(
key = appContext.getPreferenceKey(R.string.pref_key_enable_task_continuity),
default = false,
featureFlag = FeatureFlags.taskContinuityFeature,
)
/**
* Indicates if the Unified Search feature should be visible.
*/
var showUnifiedSearchFeature by lazyFeatureFlagPreference(
key = appContext.getPreferenceKey(R.string.pref_key_show_unified_search),
default = { FxNimbus.features.unifiedSearch.value(appContext).enabled },
featureFlag = FeatureFlags.unifiedSearchFeature
)
/**
* Blocklist used to filter items from the home screen that have previously been removed.
*/
var homescreenBlocklist by stringSetPreference(
appContext.getPreferenceKey(R.string.pref_key_home_blocklist),
default = setOf()
)
/**
* Get the current mode for how https-only is enabled.
*/
fun getHttpsOnlyMode(): HttpsOnlyMode {
return if (!shouldUseHttpsOnly) {
HttpsOnlyMode.DISABLED
} else if (shouldUseHttpsOnlyInPrivateTabsOnly) {
HttpsOnlyMode.ENABLED_PRIVATE_ONLY
} else {
HttpsOnlyMode.ENABLED
}
}
}