You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
445 lines
19 KiB
Kotlin
445 lines
19 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.settings
|
|
|
|
import android.content.ActivityNotFoundException
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.SharedPreferences
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.Build.VERSION.SDK_INT
|
|
import android.os.Bundle
|
|
import android.provider.Settings
|
|
import android.widget.Toast
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.lifecycle.lifecycleScope
|
|
import androidx.navigation.Navigation
|
|
import androidx.navigation.fragment.findNavController
|
|
import androidx.preference.Preference
|
|
import androidx.preference.Preference.OnPreferenceClickListener
|
|
import androidx.preference.PreferenceCategory
|
|
import androidx.preference.PreferenceFragmentCompat
|
|
import androidx.preference.SwitchPreference
|
|
import kotlinx.coroutines.launch
|
|
import mozilla.components.concept.sync.AccountObserver
|
|
import mozilla.components.concept.sync.AuthType
|
|
import mozilla.components.concept.sync.OAuthAccount
|
|
import mozilla.components.concept.sync.Profile
|
|
import org.mozilla.fenix.BrowserDirection
|
|
import org.mozilla.fenix.Config
|
|
import org.mozilla.fenix.FeatureFlags
|
|
import org.mozilla.fenix.FenixApplication
|
|
import org.mozilla.fenix.HomeActivity
|
|
import org.mozilla.fenix.R
|
|
import org.mozilla.fenix.R.string.pref_key_about
|
|
import org.mozilla.fenix.R.string.pref_key_accessibility
|
|
import org.mozilla.fenix.R.string.pref_key_account
|
|
import org.mozilla.fenix.R.string.pref_key_account_auth_error
|
|
import org.mozilla.fenix.R.string.pref_key_account_category
|
|
import org.mozilla.fenix.R.string.pref_key_add_private_browsing_shortcut
|
|
import org.mozilla.fenix.R.string.pref_key_data_choices
|
|
import org.mozilla.fenix.R.string.pref_key_delete_browsing_data
|
|
import org.mozilla.fenix.R.string.pref_key_delete_browsing_data_on_quit_preference
|
|
import org.mozilla.fenix.R.string.pref_key_help
|
|
import org.mozilla.fenix.R.string.pref_key_language
|
|
import org.mozilla.fenix.R.string.pref_key_launch_links_in_private_mode
|
|
import org.mozilla.fenix.R.string.pref_key_leakcanary
|
|
import org.mozilla.fenix.R.string.pref_key_make_default_browser
|
|
import org.mozilla.fenix.R.string.pref_key_privacy_link
|
|
import org.mozilla.fenix.R.string.pref_key_rate
|
|
import org.mozilla.fenix.R.string.pref_key_remote_debugging
|
|
import org.mozilla.fenix.R.string.pref_key_search_settings
|
|
import org.mozilla.fenix.R.string.pref_key_sign_in
|
|
import org.mozilla.fenix.R.string.pref_key_site_permissions
|
|
import org.mozilla.fenix.R.string.pref_key_theme
|
|
import org.mozilla.fenix.R.string.pref_key_tracking_protection_settings
|
|
import org.mozilla.fenix.R.string.pref_key_your_rights
|
|
import org.mozilla.fenix.components.PrivateShortcutCreateManager
|
|
import org.mozilla.fenix.components.metrics.Event
|
|
import org.mozilla.fenix.ext.components
|
|
import org.mozilla.fenix.ext.getPreferenceKey
|
|
import org.mozilla.fenix.ext.metrics
|
|
import org.mozilla.fenix.ext.requireComponents
|
|
import org.mozilla.fenix.ext.settings
|
|
import org.mozilla.fenix.settings.account.AccountAuthErrorPreference
|
|
import org.mozilla.fenix.settings.account.AccountPreference
|
|
import org.mozilla.fenix.utils.ItsNotBrokenSnack
|
|
|
|
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
|
class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
|
|
private val preferenceChangeListener =
|
|
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
|
try {
|
|
context?.let {
|
|
it.components.analytics.metrics.track(
|
|
Event.PreferenceToggled
|
|
(key, sharedPreferences.getBoolean(key, false), it)
|
|
)
|
|
}
|
|
} catch (e: IllegalArgumentException) {
|
|
// The event is not tracked
|
|
} catch (e: ClassCastException) {
|
|
// The setting is not a boolean, not tracked
|
|
}
|
|
}
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
|
|
// Observe account changes to keep the UI up-to-date.
|
|
requireComponents.backgroundServices.accountManager.register(this, owner = this, autoPause = true)
|
|
|
|
// It's important to update the account UI state in onCreate, even though we also call it in onResume, since
|
|
// that ensures we'll never display an incorrect state in the UI. For example, if user is signed-in, and we
|
|
// don't perform this call in onCreate, we'll briefly display a "Sign In" preference, which will then get
|
|
// replaced by the correct account information once this call is ran in onResume shortly after.
|
|
updateAccountUIState(context!!, requireComponents.backgroundServices.accountManager.accountProfile())
|
|
|
|
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
|
|
|
|
if (SDK_INT <= Build.VERSION_CODES.M) {
|
|
findPreference<DefaultBrowserPreference>(getPreferenceKey(R.string.pref_key_make_default_browser))?.apply {
|
|
isVisible = false
|
|
}
|
|
}
|
|
|
|
if (FeatureFlags.deleteDataOnQuit) {
|
|
findPreference<Preference>(
|
|
getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit_preference)
|
|
)?.apply {
|
|
isVisible = true
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
setPreferencesFromResource(R.xml.preferences, rootKey)
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
|
|
(activity as AppCompatActivity).title = getString(R.string.settings_title)
|
|
(activity as AppCompatActivity).supportActionBar?.show()
|
|
val defaultBrowserPreference =
|
|
findPreference<DefaultBrowserPreference>(getPreferenceKey(R.string.pref_key_make_default_browser))
|
|
defaultBrowserPreference?.updateSwitch()
|
|
|
|
val trackingProtectionPreference =
|
|
findPreference<Preference>(getPreferenceKey(R.string.pref_key_tracking_protection_settings))
|
|
trackingProtectionPreference?.summary = context?.let {
|
|
if (it.settings.shouldUseTrackingProtection) {
|
|
getString(R.string.tracking_protection_on)
|
|
} else {
|
|
getString(R.string.tracking_protection_off)
|
|
}
|
|
}
|
|
|
|
val themesPreference =
|
|
findPreference<Preference>(getPreferenceKey(R.string.pref_key_theme))
|
|
themesPreference?.summary = context?.let {
|
|
it.settings.themeSettingString
|
|
}
|
|
|
|
val aboutPreference = findPreference<Preference>(getPreferenceKey(R.string.pref_key_about))
|
|
val appName = getString(R.string.app_name)
|
|
aboutPreference?.title = getString(R.string.preferences_about, appName)
|
|
|
|
setupPreferences()
|
|
|
|
updateAccountUIState(context!!, requireComponents.backgroundServices.accountManager.accountProfile())
|
|
}
|
|
|
|
@Suppress("ComplexMethod", "LongMethod")
|
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
|
when (preference.key) {
|
|
resources.getString(pref_key_search_settings) -> {
|
|
navigateToSearchEngineSettings()
|
|
}
|
|
resources.getString(pref_key_tracking_protection_settings) -> {
|
|
navigateToTrackingProtectionSettings()
|
|
}
|
|
resources.getString(pref_key_site_permissions) -> {
|
|
navigateToSitePermissions()
|
|
}
|
|
resources.getString(pref_key_add_private_browsing_shortcut) -> {
|
|
requireContext().metrics.track(Event.PrivateBrowsingCreateShortcut)
|
|
PrivateShortcutCreateManager.createPrivateShortcut(requireContext())
|
|
}
|
|
resources.getString(pref_key_accessibility) -> {
|
|
navigateToAccessibility()
|
|
}
|
|
resources.getString(pref_key_language) -> {
|
|
// TODO #220
|
|
ItsNotBrokenSnack(context!!).showSnackbar(issueNumber = "220")
|
|
}
|
|
resources.getString(pref_key_data_choices) -> {
|
|
navigateToDataChoices()
|
|
}
|
|
resources.getString(pref_key_help) -> {
|
|
(activity as HomeActivity).openToBrowserAndLoad(
|
|
searchTermOrURL = SupportUtils.getSumoURLForTopic(context!!, SupportUtils.SumoTopic.HELP),
|
|
newTab = true,
|
|
from = BrowserDirection.FromSettings
|
|
)
|
|
}
|
|
resources.getString(pref_key_rate) -> {
|
|
try {
|
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(SupportUtils.RATE_APP_URL)))
|
|
} catch (e: ActivityNotFoundException) {
|
|
// Device without the play store installed.
|
|
// Opening the play store website.
|
|
(activity as HomeActivity).openToBrowserAndLoad(
|
|
searchTermOrURL = SupportUtils.FENIX_PLAY_STORE_URL,
|
|
newTab = true,
|
|
from = BrowserDirection.FromSettings
|
|
)
|
|
}
|
|
}
|
|
resources.getString(pref_key_about) -> {
|
|
navigateToAbout()
|
|
}
|
|
resources.getString(pref_key_account) -> {
|
|
navigateToAccountSettings()
|
|
}
|
|
resources.getString(pref_key_account_auth_error) -> {
|
|
navigateToAccountProblem()
|
|
}
|
|
resources.getString(pref_key_delete_browsing_data) -> {
|
|
navigateToDeleteBrowsingData()
|
|
}
|
|
resources.getString(pref_key_delete_browsing_data_on_quit_preference) -> {
|
|
navigateToDeleteBrowsingDataOnQuit()
|
|
}
|
|
resources.getString(pref_key_theme) -> {
|
|
navigateToThemeSettings()
|
|
}
|
|
resources.getString(pref_key_privacy_link) -> {
|
|
requireContext().let { context ->
|
|
val intent = SupportUtils.createCustomTabIntent(context, SupportUtils.getPrivacyNoticeUrl())
|
|
startActivity(intent)
|
|
}
|
|
}
|
|
resources.getString(pref_key_your_rights) -> {
|
|
requireContext().let { context ->
|
|
val intent = SupportUtils.createCustomTabIntent(
|
|
context,
|
|
SupportUtils.getSumoURLForTopic(context, SupportUtils.SumoTopic.YOUR_RIGHTS)
|
|
)
|
|
startActivity(intent)
|
|
}
|
|
}
|
|
}
|
|
return super.onPreferenceTreeClick(preference)
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
|
|
}
|
|
|
|
private fun getClickListenerForSignIn(): OnPreferenceClickListener {
|
|
return OnPreferenceClickListener {
|
|
context!!.components.services.launchPairingSignIn(context!!, findNavController())
|
|
true
|
|
}
|
|
}
|
|
|
|
private fun setupPreferences() {
|
|
val makeDefaultBrowserKey = getPreferenceKey(pref_key_make_default_browser)
|
|
val leakKey = getPreferenceKey(pref_key_leakcanary)
|
|
val debuggingKey = getPreferenceKey(pref_key_remote_debugging)
|
|
val preferenceAlwaysOpenInPrivateModeKey = getPreferenceKey(
|
|
pref_key_launch_links_in_private_mode
|
|
)
|
|
|
|
val preferenceMakeDefaultBrowser = findPreference<Preference>(makeDefaultBrowserKey)
|
|
val preferenceLeakCanary = findPreference<Preference>(leakKey)
|
|
val preferenceRemoteDebugging = findPreference<Preference>(debuggingKey)
|
|
val preferenceAlwaysOpenInPrivateMode = findPreference<SwitchPreference>(preferenceAlwaysOpenInPrivateModeKey)
|
|
|
|
preferenceAlwaysOpenInPrivateMode?.setOnPreferenceClickListener {
|
|
requireContext().settings.alwaysOpenInPrivateMode = !requireContext().settings.alwaysOpenInPrivateMode
|
|
true
|
|
}
|
|
|
|
preferenceMakeDefaultBrowser?.onPreferenceClickListener =
|
|
getClickListenerForMakeDefaultBrowser()
|
|
|
|
if (!Config.channel.isReleased) {
|
|
preferenceLeakCanary?.setOnPreferenceChangeListener { _, newValue ->
|
|
(context?.applicationContext as FenixApplication).toggleLeakCanary(newValue as Boolean)
|
|
true
|
|
}
|
|
}
|
|
|
|
preferenceRemoteDebugging?.setOnPreferenceChangeListener { preference, newValue ->
|
|
preference.context.settings.preferences.edit()
|
|
.putBoolean(preference.key, newValue as Boolean).apply()
|
|
requireComponents.core.engine.settings.remoteDebuggingEnabled = newValue
|
|
true
|
|
}
|
|
}
|
|
|
|
private val defaultClickListener = OnPreferenceClickListener { preference ->
|
|
Toast.makeText(context, "${preference.title} Clicked", Toast.LENGTH_SHORT).show()
|
|
true
|
|
}
|
|
|
|
private fun getClickListenerForMakeDefaultBrowser(): OnPreferenceClickListener {
|
|
return if (SDK_INT >= Build.VERSION_CODES.N) {
|
|
OnPreferenceClickListener {
|
|
val intent = Intent(
|
|
Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS
|
|
)
|
|
startActivity(intent)
|
|
true
|
|
}
|
|
} else {
|
|
defaultClickListener
|
|
}
|
|
}
|
|
|
|
private fun navigateToSearchEngineSettings() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToTrackingProtectionSettings() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToTrackingProtectionFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToThemeSettings() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToThemeFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToSitePermissions() {
|
|
val directions =
|
|
SettingsFragmentDirections.actionSettingsFragmentToSitePermissionsFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToAccessibility() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToAccessibilityFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToDataChoices() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToDataChoicesFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToAbout() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToAboutFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToAccountProblem() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToAccountProblemFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToAccountSettings() {
|
|
val directions =
|
|
SettingsFragmentDirections.actionSettingsFragmentToAccountSettingsFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToDeleteBrowsingData() {
|
|
val directions = SettingsFragmentDirections.actionSettingsFragmentToDeleteBrowsingDataFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
private fun navigateToDeleteBrowsingDataOnQuit() {
|
|
val directions =
|
|
SettingsFragmentDirections.actionSettingsFragmentToDeleteBrowsingDataOnQuitFragment()
|
|
Navigation.findNavController(view!!).navigate(directions)
|
|
}
|
|
|
|
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
|
|
lifecycleScope.launch {
|
|
context?.let {
|
|
updateAccountUIState(it, it.components.backgroundServices.accountManager.accountProfile())
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onLoggedOut() {
|
|
lifecycleScope.launch {
|
|
context?.let {
|
|
updateAccountUIState(it, it.components.backgroundServices.accountManager.accountProfile())
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onProfileUpdated(profile: Profile) {
|
|
lifecycleScope.launch {
|
|
context?.let {
|
|
updateAccountUIState(it, profile)
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onAuthenticationProblems() {
|
|
lifecycleScope.launch {
|
|
context?.let {
|
|
updateAccountUIState(it, it.components.backgroundServices.accountManager.accountProfile())
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the UI to reflect current account state.
|
|
* Possible conditions are logged-in without problems, logged-out, and logged-in but needs to re-authenticate.
|
|
*/
|
|
private fun updateAccountUIState(context: Context, profile: Profile?) {
|
|
val preferenceSignIn =
|
|
findPreference<Preference>(context.getPreferenceKey(pref_key_sign_in))
|
|
val preferenceFirefoxAccount =
|
|
findPreference<AccountPreference>(context.getPreferenceKey(pref_key_account))
|
|
val preferenceFirefoxAccountAuthError =
|
|
findPreference<AccountAuthErrorPreference>(context.getPreferenceKey(pref_key_account_auth_error))
|
|
val accountPreferenceCategory =
|
|
findPreference<PreferenceCategory>(context.getPreferenceKey(pref_key_account_category))
|
|
|
|
val accountManager = requireComponents.backgroundServices.accountManager
|
|
val account = accountManager.authenticatedAccount()
|
|
|
|
// Signed-in, no problems.
|
|
if (account != null && !accountManager.accountNeedsReauth()) {
|
|
preferenceSignIn?.isVisible = false
|
|
preferenceSignIn?.onPreferenceClickListener = null
|
|
preferenceFirefoxAccountAuthError?.isVisible = false
|
|
preferenceFirefoxAccount?.isVisible = true
|
|
accountPreferenceCategory?.isVisible = true
|
|
|
|
preferenceFirefoxAccount?.displayName = profile?.displayName
|
|
preferenceFirefoxAccount?.email = profile?.email
|
|
|
|
// Signed-in, need to re-authenticate.
|
|
} else if (account != null && accountManager.accountNeedsReauth()) {
|
|
preferenceFirefoxAccount?.isVisible = false
|
|
preferenceFirefoxAccountAuthError?.isVisible = true
|
|
accountPreferenceCategory?.isVisible = true
|
|
|
|
preferenceSignIn?.isVisible = false
|
|
preferenceSignIn?.onPreferenceClickListener = null
|
|
|
|
preferenceFirefoxAccountAuthError?.email = profile?.email
|
|
|
|
// Signed-out.
|
|
} else {
|
|
preferenceSignIn?.isVisible = true
|
|
preferenceSignIn?.onPreferenceClickListener = getClickListenerForSignIn()
|
|
preferenceFirefoxAccount?.isVisible = false
|
|
preferenceFirefoxAccountAuthError?.isVisible = false
|
|
accountPreferenceCategory?.isVisible = false
|
|
}
|
|
}
|
|
}
|