@ -35,6 +35,7 @@ import mozilla.components.service.fxa.sync.SyncReason
import mozilla.components.service.fxa.sync.SyncStatusObserver
import mozilla.components.service.fxa.sync.SyncStatusObserver
import mozilla.components.service.fxa.sync.getLastSynced
import mozilla.components.service.fxa.sync.getLastSynced
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.StoreProvider
@ -164,22 +165,22 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
updateSyncEngineStates ( )
updateSyncEngineStates ( )
setDisabledWhileSyncing ( accountManager . isSyncActive ( ) )
setDisabledWhileSyncing ( accountManager . isSyncActive ( ) )
fun updateSyncEngineState ( context : Context , engine : SyncEngine , newState : Boolean ) {
SyncEnginesStorage ( context ) . setStatus ( engine , newState )
viewLifecycleOwner . lifecycleScope . launch {
context . components . backgroundServices . accountManager . syncNow ( SyncReason . EngineChange )
}
}
fun SyncEngine . prefId ( ) : Int = when ( this ) {
fun SyncEngine . prefId ( ) : Int = when ( this ) {
SyncEngine . History -> R . string . pref _key _sync _history
SyncEngine . History -> R . string . pref _key _sync _history
SyncEngine . Bookmarks -> R . string . pref _key _sync _bookmarks
SyncEngine . Bookmarks -> R . string . pref _key _sync _bookmarks
SyncEngine . Passwords -> R . string . pref _key _sync _logins
SyncEngine . Passwords -> R . string . pref _key _sync _logins
SyncEngine . Tabs -> R . string . pref _key _sync _tabs
SyncEngine . Tabs -> R . string . pref _key _sync _tabs
SyncEngine . CreditCards -> R . string . pref _key _sync _credit _cards
SyncEngine . Addresses -> R . string . pref _key _sync _address
else -> throw IllegalStateException ( " Accessing internal sync engines " )
else -> throw IllegalStateException ( " Accessing internal sync engines " )
}
}
listOf ( SyncEngine . History , SyncEngine . Bookmarks , SyncEngine . Tabs ) . forEach {
listOf (
SyncEngine . History ,
SyncEngine . Bookmarks ,
SyncEngine . Tabs ,
SyncEngine . Addresses
) . forEach {
requirePreference < CheckBoxPreference > ( it . prefId ( ) ) . apply {
requirePreference < CheckBoxPreference > ( it . prefId ( ) ) . apply {
setOnPreferenceChangeListener { _ , newValue ->
setOnPreferenceChangeListener { _ , newValue ->
updateSyncEngineState ( context , it , newValue as Boolean )
updateSyncEngineState ( context , it , newValue as Boolean )
@ -188,19 +189,17 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
}
}
}
}
// 'Passwords' listener is special, since we also display a pin protection warning.
// 'Passwords' and 'Credit card' listeners are special, since we also display a pin protection warning.
requirePreference < CheckBoxPreference > ( SyncEngine . Passwords . prefId ( ) ) . apply {
requirePreference < CheckBoxPreference > ( SyncEngine . Passwords . prefId ( ) ) . apply {
setOnPreferenceChangeListener { _ , newValue ->
setOnPreferenceChangeListener { _ , newValue ->
val manager =
updateSyncEngineStateWithPinWarning ( SyncEngine . Passwords , newValue as Boolean )
activity ?. getSystemService ( Context . KEYGUARD _SERVICE ) as KeyguardManager
true
if ( manager . isKeyguardSecure ||
}
newValue == false ||
}
! context . settings ( ) . shouldShowSecurityPinWarningSync
) {
requirePreference < CheckBoxPreference > ( SyncEngine . CreditCards . prefId ( ) ) . apply {
updateSyncEngineState ( context , SyncEngine . Passwords , newValue as Boolean )
setOnPreferenceChangeListener { _ , newValue ->
} else {
updateSyncEngineStateWithPinWarning ( SyncEngine . CreditCards , newValue as Boolean )
showPinDialogWarning ( newValue as Boolean )
}
true
true
}
}
}
}
@ -218,7 +217,57 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
)
)
}
}
private fun showPinDialogWarning ( newValue : Boolean ) {
/ * *
* Prompts the user if they do not have a password / pin set up to secure their device , and
* updates the state of the sync engine with the new checkbox value .
*
* Currently used for logins and credit cards .
*
* @param engine the sync engine whose preference has changed .
* @param newValue the value denoting whether or not to sync the specified preference .
* /
private fun CheckBoxPreference . updateSyncEngineStateWithPinWarning (
syncEngine : SyncEngine ,
newValue : Boolean
) {
val manager = activity ?. getSystemService ( Context . KEYGUARD _SERVICE ) as KeyguardManager
if ( manager . isKeyguardSecure ||
! newValue ||
! requireContext ( ) . settings ( ) . shouldShowSecurityPinWarningSync
) {
updateSyncEngineState ( context , syncEngine , newValue )
} else {
showPinDialogWarning ( syncEngine , newValue )
}
}
/ * *
* Updates the sync engine status with the new state of the preference and triggers a sync
* event .
*
* @param engine the sync engine whose preference has changed .
* @param newValue the new value of the sync preference , where true indicates sync for that
* preference and false indicates not synced .
* /
private fun updateSyncEngineState ( context : Context , engine : SyncEngine , newValue : Boolean ) {
SyncEnginesStorage ( context ) . setStatus ( engine , newValue )
viewLifecycleOwner . lifecycleScope . launch {
context . components . backgroundServices . accountManager . syncNow ( SyncReason . EngineChange )
}
}
/ * *
* Creates and shows a warning dialog that prompts the user to create a pin / password to
* secure their device when none is detected . The user has the option to continue with
* updating their sync preferences ( updates the [ SyncEngine ] state ) or navigating to
* device security settings to create a pin / password .
*
* @param syncEngine the sync engine whose preference has changed .
* @param newValue the new value of the sync preference , where true indicates sync for that
* preference and false indicates not synced .
* /
private fun showPinDialogWarning ( syncEngine : SyncEngine , newValue : Boolean ) {
context ?. let {
context ?. let {
AlertDialog . Builder ( it ) . apply {
AlertDialog . Builder ( it ) . apply {
setTitle ( getString ( R . string . logins _warning _dialog _title ) )
setTitle ( getString ( R . string . logins _warning _dialog _title ) )
@ -227,11 +276,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
)
)
setNegativeButton ( getString ( R . string . logins _warning _dialog _later ) ) { _ : DialogInterface , _ ->
setNegativeButton ( getString ( R . string . logins _warning _dialog _later ) ) { _ : DialogInterface , _ ->
SyncEnginesStorage ( context ) . setStatus ( SyncEngine . Passwords , newValue )
updateSyncEngineState ( context , syncEngine , newValue )
// Use fragment's lifecycle; the view may be gone by the time dialog is interacted with.
lifecycleScope . launch {
context . components . backgroundServices . accountManager . syncNow ( SyncReason . EngineChange )
}
}
}
setPositiveButton ( getString ( R . string . logins _warning _dialog _set _up _now ) ) { it : DialogInterface , _ ->
setPositiveButton ( getString ( R . string . logins _warning _dialog _set _up _now ) ) { it : DialogInterface , _ ->
@ -247,12 +292,20 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
}
}
}
}
/ * *
* Updates the status of all [ SyncEngine ] states .
* /
private fun updateSyncEngineStates ( ) {
private fun updateSyncEngineStates ( ) {
val syncEnginesStatus = SyncEnginesStorage ( requireContext ( ) ) . getStatus ( )
val syncEnginesStatus = SyncEnginesStorage ( requireContext ( ) ) . getStatus ( )
requirePreference < CheckBoxPreference > ( R . string . pref _key _sync _bookmarks ) . apply {
requirePreference < CheckBoxPreference > ( R . string . pref _key _sync _bookmarks ) . apply {
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . Bookmarks )
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . Bookmarks )
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . Bookmarks ) { true }
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . Bookmarks ) { true }
}
}
requirePreference < CheckBoxPreference > ( R . string . pref _key _sync _credit _cards ) . apply {
isVisible = FeatureFlags . creditCardsFeature
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . CreditCards )
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . CreditCards ) { true }
}
requirePreference < CheckBoxPreference > ( R . string . pref _key _sync _history ) . apply {
requirePreference < CheckBoxPreference > ( R . string . pref _key _sync _history ) . apply {
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . History )
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . History )
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . History ) { true }
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . History ) { true }
@ -265,8 +318,17 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . Tabs )
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . Tabs )
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . Tabs ) { true }
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . Tabs ) { true }
}
}
requirePreference < CheckBoxPreference > ( R . string . pref _key _sync _address ) . apply {
isVisible = FeatureFlags . addressesFeature
isEnabled = syncEnginesStatus . containsKey ( SyncEngine . Addresses )
isChecked = syncEnginesStatus . getOrElse ( SyncEngine . Addresses ) { true }
}
}
}
/ * *
* Manual sync triggered by the user . This also checks account authentication and refreshes the
* device list .
* /
private fun syncNow ( ) {
private fun syncNow ( ) {
viewLifecycleOwner . lifecycleScope . launch {
viewLifecycleOwner . lifecycleScope . launch {
requireComponents . analytics . metrics . track ( Event . SyncAccountSyncNow )
requireComponents . analytics . metrics . track ( Event . SyncAccountSyncNow )
@ -281,8 +343,13 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
}
}
}
}
private fun syncDeviceName ( newValue : String ) : Boolean {
/ * *
if ( newValue . trim ( ) . isEmpty ( ) ) {
* Takes a non - empty value and sets the device name . May fail due to authentication .
*
* @param newDeviceName the new name of the device . Cannot be an empty string .
* /
private fun syncDeviceName ( newDeviceName : String ) : Boolean {
if ( newDeviceName . trim ( ) . isEmpty ( ) ) {
return false
return false
}
}
// This may fail, and we'll have a disparity in the UI until `updateDeviceName` is called.
// This may fail, and we'll have a disparity in the UI until `updateDeviceName` is called.
@ -290,7 +357,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
context ?. let {
context ?. let {
accountManager . authenticatedAccount ( )
accountManager . authenticatedAccount ( )
?. deviceConstellation ( )
?. deviceConstellation ( )
?. setDeviceName ( new Valu e, it )
?. setDeviceName ( new DeviceNam e, it )
}
}
}
}
return true
return true
@ -409,6 +476,5 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
companion object {
companion object {
private const val DEVICE _NAME _MAX _LENGTH = 128
private const val DEVICE _NAME _MAX _LENGTH = 128
private const val DEVICE _NAME _EDIT _TEXT _MIN _HEIGHT _DP = 48
}
}
}
}