1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt

431 lines
19 KiB
Kotlin
Raw Normal View History

/* 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.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.provider.Settings
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections
2019-12-08 05:25:32 +01:00
import androidx.navigation.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver
2019-09-04 22:56:22 +02:00
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import mozilla.components.support.ktx.android.content.hasCamera
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.application
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
2019-11-25 21:36:47 +01:00
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.account.AccountUiView
import kotlin.system.exitProcess
@Suppress("LargeClass", "TooManyFunctions")
2019-12-08 05:25:32 +01:00
class SettingsFragment : PreferenceFragmentCompat() {
private lateinit var accountUiView: AccountUiView
2019-12-08 05:25:32 +01:00
private val accountObserver = object : AccountObserver {
private fun updateAccountUi(profile: Profile? = null) {
val context = context ?: return
lifecycleScope.launch {
accountUiView.updateAccountUIState(
2019-12-08 05:25:32 +01:00
context = context,
profile = profile
?: context.components.backgroundServices.accountManager.accountProfile()
2019-12-08 05:25:32 +01:00
)
}
}
2019-12-08 05:25:32 +01:00
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) = updateAccountUi()
override fun onLoggedOut() = updateAccountUi()
override fun onProfileUpdated(profile: Profile) = updateAccountUi(profile)
override fun onAuthenticationProblems() = updateAccountUi()
}
// A flag used to track if we're going through the onCreate->onStart->onResume lifecycle chain.
// If it's set to `true`, code in `onResume` can assume that `onCreate` executed a moment prior.
// This flag is set to `false` at the end of `onResume`.
private var creatingFragment = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
accountUiView = AccountUiView(
fragment = this,
2020-07-27 22:16:20 +02:00
scope = lifecycleScope,
accountManager = requireComponents.backgroundServices.accountManager,
httpClient = requireComponents.core.client,
updateFxASyncOverrideMenu = ::updateFxASyncOverrideMenu
)
// Observe account changes to keep the UI up-to-date.
requireComponents.backgroundServices.accountManager.register(
2019-12-08 05:25:32 +01:00
accountObserver,
owner = this,
autoPause = true
)
// It's important to update the account UI state in onCreate since that ensures we'll never
// display an incorrect state in the UI. We take care to not also call it as part of onResume
// if it was just called here (via the 'creatingFragment' flag).
// 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.
accountUiView.updateAccountUIState(
requireContext(),
requireComponents.backgroundServices.accountManager.accountProfile()
)
2019-12-08 05:25:32 +01:00
preferenceManager.sharedPreferences
.registerOnSharedPreferenceChangeListener(this) { sharedPreferences, key ->
try {
context?.let { context ->
context.components.analytics.metrics.track(
Event.PreferenceToggled(
key,
sharedPreferences.getBoolean(key, false),
context
)
2019-12-08 05:25:32 +01:00
)
}
} catch (e: IllegalArgumentException) {
// The event is not tracked
} catch (e: ClassCastException) {
// The setting is not a boolean, not tracked
}
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
updateMakeDefaultBrowserPreference()
}
override fun onResume() {
super.onResume()
2019-11-25 21:36:47 +01:00
showToolbar(getString(R.string.settings_title))
// Account UI state is updated as part of `onCreate`. To not do it twice in a row, we only
// update it here if we're not going through the `onCreate->onStart->onResume` lifecycle chain.
update(shouldUpdateAccountUIState = !creatingFragment)
requireView().findViewById<RecyclerView>(R.id.recycler_view)
?.hideInitialScrollBar(viewLifecycleOwner.lifecycleScope)
// Consider finish of `onResume` to be the point at which we consider this fragment as 'created'.
creatingFragment = false
}
2020-07-27 22:16:20 +02:00
override fun onDestroyView() {
super.onDestroyView()
accountUiView.cancel()
}
private fun update(shouldUpdateAccountUIState: Boolean) {
val trackingProtectionPreference =
2020-06-15 20:24:14 +02:00
requirePreference<Preference>(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)
}
}
2020-06-15 20:24:14 +02:00
val aboutPreference = requirePreference<Preference>(R.string.pref_key_about)
val appName = getString(R.string.app_name)
2020-06-15 20:24:14 +02:00
aboutPreference.title = getString(R.string.preferences_about, appName)
val deleteBrowsingDataPreference =
2020-06-15 20:24:14 +02:00
requirePreference<Preference>(R.string.pref_key_delete_browsing_data_on_quit_preference)
deleteBrowsingDataPreference.summary = context?.let {
if (it.settings().shouldDeleteBrowsingDataOnQuit) {
getString(R.string.delete_browsing_data_quit_on)
} else {
getString(R.string.delete_browsing_data_quit_off)
}
}
setupPreferences()
if (shouldUpdateAccountUIState) {
accountUiView.updateAccountUIState(
requireContext(),
requireComponents.backgroundServices.accountManager.accountProfile()
)
}
updateMakeDefaultBrowserPreference()
2019-10-24 18:29:41 +02:00
}
@Suppress("ComplexMethod", "LongMethod")
override fun onPreferenceTreeClick(preference: Preference): Boolean {
// Hide the scrollbar so the animation looks smoother
val recyclerView = requireView().findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.isVerticalScrollBarEnabled = false
2019-12-08 05:25:32 +01:00
val directions: NavDirections? = when (preference.key) {
resources.getString(R.string.pref_key_sign_in) -> {
// App can be installed on devices with no camera modules. Like Android TV boxes.
// Let's skip presenting the option to sign in by scanning a qr code in this case
// and default to login with email and password.
if (requireContext().hasCamera()) {
SettingsFragmentDirections.actionSettingsFragmentToTurnOnSyncFragment()
} else {
requireComponents.services.accountsAuthFeature.beginAuthentication(requireContext())
requireComponents.analytics.metrics.track(Event.SyncAuthUseEmail)
null
}
}
resources.getString(R.string.pref_key_search_settings) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
}
resources.getString(R.string.pref_key_tracking_protection_settings) -> {
requireContext().metrics.track(Event.TrackingProtectionSettings)
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToTrackingProtectionFragment()
}
resources.getString(R.string.pref_key_site_permissions) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToSitePermissionsFragment()
}
resources.getString(R.string.pref_key_private_browsing) -> {
SettingsFragmentDirections.actionSettingsFragmentToPrivateBrowsingFragment()
}
resources.getString(R.string.pref_key_accessibility) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToAccessibilityFragment()
}
resources.getString(R.string.pref_key_language) -> {
SettingsFragmentDirections.actionSettingsFragmentToLocaleSettingsFragment()
}
resources.getString(R.string.pref_key_addons) -> {
requireContext().metrics.track(Event.AddonsOpenInSettings)
SettingsFragmentDirections.actionSettingsFragmentToAddonsFragment()
}
resources.getString(R.string.pref_key_data_choices) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToDataChoicesFragment()
}
resources.getString(R.string.pref_key_help) -> {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(
requireContext(),
SupportUtils.SumoTopic.HELP
),
newTab = true,
from = BrowserDirection.FromSettings
)
2019-12-08 05:25:32 +01:00
null
}
resources.getString(R.string.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
)
}
2019-12-08 05:25:32 +01:00
null
}
resources.getString(R.string.pref_key_passwords) -> {
SettingsFragmentDirections.actionSettingsFragmentToSavedLoginsAuthFragment()
2019-10-24 18:29:41 +02:00
}
resources.getString(R.string.pref_key_about) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToAboutFragment()
}
resources.getString(R.string.pref_key_account) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToAccountSettingsFragment()
}
resources.getString(R.string.pref_key_account_auth_error) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToAccountProblemFragment()
}
resources.getString(R.string.pref_key_delete_browsing_data) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToDeleteBrowsingDataFragment()
}
resources.getString(R.string.pref_key_delete_browsing_data_on_quit_preference) -> {
2019-12-08 05:25:32 +01:00
SettingsFragmentDirections.actionSettingsFragmentToDeleteBrowsingDataOnQuitFragment()
}
resources.getString(R.string.pref_key_customize) -> {
SettingsFragmentDirections.actionSettingsFragmentToCustomizationFragment()
}
resources.getString(R.string.pref_key_privacy_link) -> {
2019-12-08 05:25:32 +01:00
val intent = SupportUtils.createCustomTabIntent(
requireContext(),
SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE)
2019-12-08 05:25:32 +01:00
)
startActivity(intent)
null
}
resources.getString(R.string.pref_key_your_rights) -> {
2019-12-08 05:25:32 +01:00
val context = requireContext()
val intent = SupportUtils.createCustomTabIntent(
context,
SupportUtils.getSumoURLForTopic(context, SupportUtils.SumoTopic.YOUR_RIGHTS)
)
startActivity(intent)
null
2019-05-24 20:19:06 +02:00
}
resources.getString(R.string.pref_key_debug_settings) -> {
SettingsFragmentDirections.actionSettingsFragmentToSecretSettingsFragment()
}
2019-12-08 05:25:32 +01:00
else -> null
}
2019-12-08 05:25:32 +01:00
directions?.let { navigateFromSettings(directions) }
return super.onPreferenceTreeClick(preference)
}
private fun setupPreferences() {
val leakKey = getPreferenceKey(R.string.pref_key_leakcanary)
val debuggingKey = getPreferenceKey(R.string.pref_key_remote_debugging)
val preferenceExternalDownloadManager = requirePreference<Preference>(R.string.pref_key_make_default_browser)
val preferenceLeakCanary = findPreference<Preference>(leakKey)
val preferenceRemoteDebugging = findPreference<Preference>(debuggingKey)
2020-06-15 20:24:14 +02:00
val preferenceMakeDefaultBrowser = requirePreference<Preference>(R.string.pref_key_make_default_browser)
if (!Config.channel.isReleased) {
preferenceLeakCanary?.setOnPreferenceChangeListener { _, newValue ->
val isEnabled = newValue == true
context?.application?.updateLeakCanaryState(isEnabled)
true
}
}
preferenceExternalDownloadManager.isVisible = FeatureFlags.externalDownloadManager
preferenceRemoteDebugging?.setOnPreferenceChangeListener<Boolean> { preference, newValue ->
2019-10-24 18:29:41 +02:00
preference.context.settings().preferences.edit()
.putBoolean(preference.key, newValue).apply()
requireComponents.core.engine.settings.remoteDebuggingEnabled = newValue
true
}
2020-06-15 20:24:14 +02:00
preferenceMakeDefaultBrowser.onPreferenceClickListener =
getClickListenerForMakeDefaultBrowser()
val preferenceFxAOverride =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_override_fxa_server))
val preferenceSyncOverride =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_override_sync_tokenserver))
val syncFxAOverrideUpdater = object : StringSharedPreferenceUpdater() {
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
return super.onPreferenceChange(preference, newValue).also {
updateFxASyncOverrideMenu()
Toast.makeText(
context,
getString(R.string.toast_override_fxa_sync_server_done),
Toast.LENGTH_LONG
).show()
Handler().postDelayed({
exitProcess(0)
}, FXA_SYNC_OVERRIDE_EXIT_DELAY)
}
}
}
preferenceFxAOverride?.onPreferenceChangeListener = syncFxAOverrideUpdater
preferenceSyncOverride?.onPreferenceChangeListener = syncFxAOverrideUpdater
findPreference<Preference>(
getPreferenceKey(R.string.pref_key_debug_settings)
)?.isVisible = requireContext().settings().showSecretDebugMenuThisSession
}
private fun getClickListenerForMakeDefaultBrowser(): Preference.OnPreferenceClickListener {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Preference.OnPreferenceClickListener {
val intent = Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
startActivity(intent)
true
}
} else {
Preference.OnPreferenceClickListener {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(
requireContext(),
SupportUtils.SumoTopic.SET_AS_DEFAULT_BROWSER
),
newTab = true,
from = BrowserDirection.FromSettings
)
true
}
}
}
private fun updateMakeDefaultBrowserPreference() {
2020-06-15 20:24:14 +02:00
requirePreference<DefaultBrowserPreference>(R.string.pref_key_make_default_browser).updateSwitch()
}
private fun navigateFromSettings(directions: NavDirections) {
2019-12-08 05:25:32 +01:00
view?.findNavController()?.let { navController ->
if (navController.currentDestination?.id == R.id.settingsFragment) {
navController.navigate(directions)
}
}
}
// Extension function for hiding the scroll bar on initial loading. We must do this so the
// animation to the next screen doesn't animate the initial scroll bar (it ignores
// isVerticalScrollBarEnabled being set to false).
private fun RecyclerView.hideInitialScrollBar(scope: CoroutineScope) {
scope.launch {
val originalSize = scrollBarSize
scrollBarSize = 0
delay(SCROLL_INDICATOR_DELAY)
scrollBarSize = originalSize
}
}
private fun updateFxASyncOverrideMenu() {
val preferenceFxAOverride =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_override_fxa_server))
val preferenceSyncOverride =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_override_sync_tokenserver))
val settings = requireContext().settings()
val show = settings.overrideFxAServer.isNotEmpty() ||
settings.overrideSyncTokenServer.isNotEmpty() ||
settings.showSecretDebugMenuThisSession
// Only enable changes to these prefs when the user isn't connected to an account.
val enabled =
requireComponents.backgroundServices.accountManager.authenticatedAccount() == null
preferenceFxAOverride?.apply {
isVisible = show
isEnabled = enabled
summary = settings.overrideFxAServer.ifEmpty { null }
}
preferenceSyncOverride?.apply {
isVisible = show
isEnabled = enabled
summary = settings.overrideSyncTokenServer.ifEmpty { null }
}
}
companion object {
private const val SCROLL_INDICATOR_DELAY = 10L
private const val FXA_SYNC_OVERRIDE_EXIT_DELAY = 2000L
}
}