1
0
Fork 0

For #5545 For #5542 Closes #6696 Integrate logins API, adds Settings for Autofilling/Saving Logins

master
ekager 2020-01-13 16:18:41 -08:00 committed by Emily Kager
parent f93437c509
commit 0777fb3bbe
21 changed files with 214 additions and 51 deletions

View File

@ -6,7 +6,9 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.lib.crash.handler.CrashHandlerService import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.service.experiments.Experiments import mozilla.components.service.experiments.Experiments
import mozilla.components.service.sync.logins.AsyncLoginsStorage
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntime
@ -17,7 +19,12 @@ object GeckoProvider {
private var runtime: GeckoRuntime? = null private var runtime: GeckoRuntime? = null
@Synchronized @Synchronized
fun getOrCreateRuntime(context: Context): GeckoRuntime { @Suppress("UNUSED_PARAMETER") // API not yet landed in GV beta
fun getOrCreateRuntime(
context: Context,
storage: AsyncLoginsStorage,
securePreferences: SecureAbove22Preferences
): GeckoRuntime {
if (runtime == null) { if (runtime == null) {
runtime = createRuntime(context) runtime = createRuntime(context)
} }

View File

@ -4,9 +4,14 @@
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.lib.crash.handler.CrashHandlerService import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.service.sync.logins.AsyncLoginsStorage
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings import org.mozilla.geckoview.GeckoRuntimeSettings
@ -16,15 +21,23 @@ object GeckoProvider {
private var runtime: GeckoRuntime? = null private var runtime: GeckoRuntime? = null
@Synchronized @Synchronized
fun getOrCreateRuntime(context: Context): GeckoRuntime { fun getOrCreateRuntime(
context: Context,
storage: AsyncLoginsStorage,
securePreferences: SecureAbove22Preferences
): GeckoRuntime {
if (runtime == null) { if (runtime == null) {
runtime = createRuntime(context) runtime = createRuntime(context, storage, securePreferences)
} }
return runtime!! return runtime!!
} }
private fun createRuntime(context: Context): GeckoRuntime { private fun createRuntime(
context: Context,
storage: AsyncLoginsStorage,
securePreferences: SecureAbove22Preferences
): GeckoRuntime {
val builder = GeckoRuntimeSettings.Builder() val builder = GeckoRuntimeSettings.Builder()
testConfig?.let { testConfig?.let {
@ -46,6 +59,14 @@ object GeckoProvider {
runtimeSettings.fontSizeFactor = fontSize runtimeSettings.fontSizeFactor = fontSize
} }
return GeckoRuntime.create(context, runtimeSettings) val geckoRuntime = GeckoRuntime.create(context, runtimeSettings)
val loginStorageDelegate = GeckoLoginStorageDelegate(
storage,
securePreferences,
{ context.settings().shouldAutofillLogins && context.settings().shouldPromptToSaveLogins }
)
geckoRuntime.loginStorageDelegate = GeckoLoginDelegateWrapper(loginStorageDelegate)
return geckoRuntime
} }
} }

View File

@ -52,6 +52,7 @@ import mozilla.components.feature.session.SwipeRefreshFeature
import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsFeature import mozilla.components.feature.sitepermissions.SitePermissionsFeature
import mozilla.components.feature.sitepermissions.SitePermissionsRules import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
import mozilla.components.support.base.feature.PermissionsFeature import mozilla.components.support.base.feature.PermissionsFeature
import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
@ -317,6 +318,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
store = store, store = store,
customTabId = customTabSessionId, customTabId = customTabSessionId,
fragmentManager = parentFragmentManager, fragmentManager = parentFragmentManager,
loginValidationDelegate = DefaultLoginValidationDelegate(
context.components.core.asyncPasswordsStorage,
context.components.core.getSecureAbove22Preferences()
),
isSaveLoginEnabled = {
context.settings().shouldPromptToSaveLogins
},
shareDelegate = object : ShareDelegate { shareDelegate = object : ShareDelegate {
override fun showShareSheet( override fun showShareSheet(
context: Context, context: Context,

View File

@ -20,7 +20,7 @@ class Components(private val context: Context) {
analytics.crashReporter, analytics.crashReporter,
core.historyStorage, core.historyStorage,
core.bookmarksStorage, core.bookmarksStorage,
core.passwordsStorage, core.syncablePasswordsStorage,
core.getSecureAbove22Preferences() core.getSecureAbove22Preferences()
) )
} }

View File

@ -35,9 +35,9 @@ import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager import mozilla.components.feature.pwa.WebAppShortcutManager
import mozilla.components.feature.session.HistoryDelegate import mozilla.components.feature.session.HistoryDelegate
import mozilla.components.feature.webcompat.WebCompatFeature import mozilla.components.feature.webcompat.WebCompatFeature
import mozilla.components.feature.webnotifications.WebNotificationFeature
import mozilla.components.lib.dataprotect.SecureAbove22Preferences import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.lib.dataprotect.generateEncryptionKey import mozilla.components.lib.dataprotect.generateEncryptionKey
import mozilla.components.feature.webnotifications.WebNotificationFeature
import mozilla.components.service.sync.logins.AsyncLoginsStorageAdapter import mozilla.components.service.sync.logins.AsyncLoginsStorageAdapter
import mozilla.components.service.sync.logins.SyncableLoginsStore import mozilla.components.service.sync.logins.SyncableLoginsStore
import org.mozilla.fenix.AppRequestInterceptor import org.mozilla.fenix.AppRequestInterceptor
@ -73,7 +73,13 @@ class Core(private val context: Context) {
forceUserScalableContent = context.settings().forceEnableZoom forceUserScalableContent = context.settings().forceEnableZoom
) )
GeckoEngine(context, defaultSettings, GeckoProvider.getOrCreateRuntime(context)).also { GeckoEngine(
context,
defaultSettings,
GeckoProvider.getOrCreateRuntime(
context, asyncPasswordsStorage, getSecureAbove22Preferences()
)
).also {
WebCompatFeature.install(it) WebCompatFeature.install(it)
} }
} }
@ -82,7 +88,14 @@ class Core(private val context: Context) {
* [Client] implementation to be used for code depending on `concept-fetch`` * [Client] implementation to be used for code depending on `concept-fetch``
*/ */
val client: Client by lazy { val client: Client by lazy {
GeckoViewFetchClient(context, GeckoProvider.getOrCreateRuntime(context)) GeckoViewFetchClient(
context,
GeckoProvider.getOrCreateRuntime(
context,
asyncPasswordsStorage,
getSecureAbove22Preferences()
)
)
} }
val sessionStorage: SessionStorage by lazy { val sessionStorage: SessionStorage by lazy {
@ -143,8 +156,10 @@ class Core(private val context: Context) {
MediaFeature(context).enable() MediaFeature(context).enable()
} }
WebNotificationFeature(context, engine, icons, R.drawable.ic_status_logo, WebNotificationFeature(
HomeActivity::class.java) context, engine, icons, R.drawable.ic_status_logo,
HomeActivity::class.java
)
} }
} }
@ -181,14 +196,18 @@ class Core(private val context: Context) {
val webAppManifestStorage by lazy { ManifestStorage(context) } val webAppManifestStorage by lazy { ManifestStorage(context) }
val passwordsStorage by lazy { val asyncPasswordsStorage by lazy {
AsyncLoginsStorageAdapter.forDatabase(
File(
context.filesDir,
"logins.sqlite"
).canonicalPath
)
}
val syncablePasswordsStorage by lazy {
SyncableLoginsStore( SyncableLoginsStore(
AsyncLoginsStorageAdapter.forDatabase( asyncPasswordsStorage
File(
context.filesDir,
"logins.sqlite"
).canonicalPath
)
) { ) {
CompletableDeferred(passwordsEncryptionKey) CompletableDeferred(passwordsEncryptionKey)
} }

View File

@ -20,6 +20,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AccountObserver
@ -36,7 +37,7 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import java.util.concurrent.Executors import java.util.concurrent.Executors
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions", "LargeClass")
class LoginsFragment : PreferenceFragmentCompat(), AccountObserver { class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
@TargetApi(M) @TargetApi(M)
@ -83,10 +84,32 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
.build() .build()
} }
@Suppress("ComplexMethod")
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
showToolbar(getString(R.string.preferences_passwords_logins_and_passwords)) showToolbar(getString(R.string.preferences_passwords_logins_and_passwords))
val saveLoginsSettingKey = getPreferenceKey(R.string.pref_key_save_logins_settings)
findPreference<Preference>(saveLoginsSettingKey)?.apply {
summary = getString(
if (context.settings().shouldPromptToSaveLogins)
R.string.preferences_passwords_save_logins_ask_to_save else
R.string.preferences_passwords_save_logins_never_save
)
setOnPreferenceClickListener {
navigateToSaveLoginSettingFragment()
true
}
}
val autofillPreferenceKey = getPreferenceKey(R.string.pref_key_autofill_logins)
findPreference<SwitchPreference>(autofillPreferenceKey)?.apply {
isEnabled = context.settings().shouldPromptToSaveLogins
isChecked =
context.settings().shouldAutofillLogins && context.settings().shouldPromptToSaveLogins
onPreferenceChangeListener = SharedPreferenceUpdater()
}
val savedLoginsKey = getPreferenceKey(R.string.pref_key_saved_logins) val savedLoginsKey = getPreferenceKey(R.string.pref_key_saved_logins)
findPreference<Preference>(savedLoginsKey)?.setOnPreferenceClickListener { findPreference<Preference>(savedLoginsKey)?.setOnPreferenceClickListener {
if (Build.VERSION.SDK_INT >= M && isHardwareAvailable && hasBiometricEnrolled) { if (Build.VERSION.SDK_INT >= M && isHardwareAvailable && hasBiometricEnrolled) {
@ -235,33 +258,31 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
} }
private fun navigateToSavedLoginsFragment() { private fun navigateToSavedLoginsFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) { context?.components?.analytics?.metrics?.track(Event.OpenLogins)
context?.components?.analytics?.metrics?.track(Event.OpenLogins) val directions = LoginsFragmentDirections.actionLoginsFragmentToSavedLoginsFragment()
val directions = LoginsFragmentDirections.actionLoginsFragmentToSavedLoginsFragment() findNavController().navigate(directions)
findNavController().navigate(directions)
}
} }
private fun navigateToAccountSettingsFragment() { private fun navigateToAccountSettingsFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) { val directions =
val directions = LoginsFragmentDirections.actionLoginsFragmentToAccountSettingsFragment()
LoginsFragmentDirections.actionLoginsFragmentToAccountSettingsFragment() findNavController().navigate(directions)
findNavController().navigate(directions)
}
} }
private fun navigateToAccountProblemFragment() { private fun navigateToAccountProblemFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) { val directions = LoginsFragmentDirections.actionLoginsFragmentToAccountProblemFragment()
val directions = LoginsFragmentDirections.actionLoginsFragmentToAccountProblemFragment() findNavController().navigate(directions)
findNavController().navigate(directions)
}
} }
private fun navigateToTurnOnSyncFragment() { private fun navigateToTurnOnSyncFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) { val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment()
val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment() findNavController().navigate(directions)
findNavController().navigate(directions) }
}
private fun navigateToSaveLoginSettingFragment() {
val directions =
LoginsFragmentDirections.actionLoginsFragmentToSaveLoginSettingFragment()
findNavController().navigate(directions)
} }
companion object { companion object {

View File

@ -0,0 +1,43 @@
/* 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.logins
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.RadioButtonPreference
class SaveLoginSettingFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.save_logins_preferences, rootKey)
}
override fun onResume() {
super.onResume()
showToolbar(getString(R.string.preferences_passwords_save_logins))
val save = bindSave()
val neverSave = bindNeverSave()
setupRadioGroups(save, neverSave)
}
private fun bindSave(): RadioButtonPreference {
val keyStrict = getString(R.string.pref_key_save_logins)
return requireNotNull(findPreference(keyStrict))
}
private fun bindNeverSave(): RadioButtonPreference {
val keyStandard = getString(R.string.pref_key_never_save_logins)
return requireNotNull(findPreference(keyStandard))
}
private fun setupRadioGroups(
radioNeverSave: RadioButtonPreference,
radioSave: RadioButtonPreference
) {
radioNeverSave.addToRadioGroup(radioSave)
radioSave.addToRadioGroup(radioNeverSave)
}
}

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -88,7 +88,7 @@ class SavedLoginsFragment : Fragment() {
private suspend fun loadAndMapLogins() { private suspend fun loadAndMapLogins() {
val syncedLogins = withContext(IO) { val syncedLogins = withContext(IO) {
requireContext().components.core.passwordsStorage.withUnlocked { requireContext().components.core.syncablePasswordsStorage.withUnlocked {
it.list().await().map { item -> it.list().await().map { item ->
SavedLoginsItem(item.hostname, item.username, item.password) SavedLoginsItem(item.hostname, item.username, item.password)
} }

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
import android.os.Parcelable import android.os.Parcelable
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize

View File

@ -2,7 +2,7 @@
License, v. 2.0. If a copy of the MPL was not distributed with this 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/. */ file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
/** /**
* Interactor for the saved logins screen * Interactor for the saved logins screen

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView

View File

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup

View File

@ -386,6 +386,16 @@ class Settings private constructor(
) )
} }
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 fxaSignedIn by booleanPreference( var fxaSignedIn by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_fxa_signed_in), appContext.getPreferenceKey(R.string.pref_key_fxa_signed_in),
default = true default = true

View File

@ -7,4 +7,4 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
tools:context="org.mozilla.fenix.logins.SavedLoginsFragment" /> tools:context="org.mozilla.fenix.settings.logins.SavedLoginsFragment" />

View File

@ -340,6 +340,9 @@
<action <action
android:id="@+id/action_loginsFragment_to_turnOnSyncFragment" android:id="@+id/action_loginsFragment_to_turnOnSyncFragment"
app:destination="@id/turnOnSyncFragment" /> app:destination="@id/turnOnSyncFragment" />
<action
android:id="@+id/action_loginsFragment_to_saveLoginSettingFragment"
app:destination="@id/saveLoginSettingFragment" />
</fragment> </fragment>
<fragment <fragment
@ -650,7 +653,7 @@
</fragment> </fragment>
<fragment <fragment
android:id="@+id/savedLoginsFragment" android:id="@+id/savedLoginsFragment"
android:name="org.mozilla.fenix.logins.SavedLoginsFragment" android:name="org.mozilla.fenix.settings.logins.SavedLoginsFragment"
tools:layout="@layout/fragment_saved_logins"> tools:layout="@layout/fragment_saved_logins">
<action <action
android:id="@+id/action_savedLoginsFragment_to_savedLoginSiteInfoFragment" android:id="@+id/action_savedLoginsFragment_to_savedLoginSiteInfoFragment"
@ -658,11 +661,11 @@
</fragment> </fragment>
<fragment <fragment
android:id="@+id/savedLoginSiteInfoFragment" android:id="@+id/savedLoginSiteInfoFragment"
android:name="org.mozilla.fenix.logins.SavedLoginSiteInfoFragment" android:name="org.mozilla.fenix.settings.logins.SavedLoginSiteInfoFragment"
tools:layout="@layout/fragment_saved_login_site_info"> tools:layout="@layout/fragment_saved_login_site_info">
<argument <argument
android:name="savedLoginItem" android:name="savedLoginItem"
app:argType="org.mozilla.fenix.logins.SavedLoginsItem" /> app:argType="org.mozilla.fenix.settings.logins.SavedLoginsItem" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/addSearchEngineFragment" android:id="@+id/addSearchEngineFragment"
@ -680,4 +683,8 @@
<fragment <fragment
android:id="@+id/localeSettingsFragment" android:id="@+id/localeSettingsFragment"
android:name="org.mozilla.fenix.settings.advanced.LocaleSettingsFragment" /> android:name="org.mozilla.fenix.settings.advanced.LocaleSettingsFragment" />
<fragment
android:id="@+id/saveLoginSettingFragment"
android:name="org.mozilla.fenix.settings.logins.SaveLoginSettingFragment"
android:label="SaveLoginSettingFragment" />
</navigation> </navigation>

View File

@ -114,6 +114,10 @@
<string name="pref_key_tracking_protection_onboarding" translatable="false">pref_key_tracking_protection_onboarding</string> <string name="pref_key_tracking_protection_onboarding" translatable="false">pref_key_tracking_protection_onboarding</string>
<!-- Logins Settings --> <!-- Logins Settings -->
<string name="pref_key_save_logins_settings" translatable="false">pref_key_save_logins_settings</string>
<string name="pref_key_save_logins" translatable="false">pref_key_save_logins</string>
<string name="pref_key_autofill_logins" translatable="false">pref_key_autofill_logins</string>
<string name="pref_key_never_save_logins" translatable="false">pref_key_never_save_logins</string>
<string name="pref_key_saved_logins" translatable="false">pref_key_saved_logins</string> <string name="pref_key_saved_logins" translatable="false">pref_key_saved_logins</string>
<string name="pref_key_password_sync_logins" translatable="false">pref_key_password_sync_logins</string> <string name="pref_key_password_sync_logins" translatable="false">pref_key_password_sync_logins</string>
<string name="pref_key_logins_secure_warning_sync" translatable="false">pref_key_logins_secure_warning_sync</string> <string name="pref_key_logins_secure_warning_sync" translatable="false">pref_key_logins_secure_warning_sync</string>

View File

@ -1,7 +1,16 @@
<?xml version="1.0" encoding="utf-8"?><!-- This Source Code Form is subject to the terms of the Mozilla Public <?xml version="1.0" encoding="utf-8"?>
<!-- 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 - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.preference.Preference
android:key="@string/pref_key_save_logins_settings"
android:summary="@string/preferences_passwords_save_logins_ask_to_save"
android:title="@string/preferences_passwords_save_logins" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/pref_key_autofill_logins"
android:title="@string/preferences_passwords_autofill" />
<androidx.preference.Preference <androidx.preference.Preference
android:key="@string/pref_key_password_sync_logins" android:key="@string/pref_key_password_sync_logins"
android:summary="@string/preferences_passwords_sync_logins_off" android:summary="@string/preferences_passwords_sync_logins_off"

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<org.mozilla.fenix.settings.RadioButtonPreference
android:defaultValue="true"
android:key="@string/pref_key_save_logins"
android:title="@string/preferences_passwords_save_logins_ask_to_save" />
<org.mozilla.fenix.settings.RadioButtonPreference
android:defaultValue="false"
android:key="@string/pref_key_never_save_logins"
android:title="@string/preferences_passwords_save_logins_never_save" />
</PreferenceScreen>

View File

@ -2,7 +2,7 @@
License, v. 2.0. If a copy of the MPL was not distributed with this 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/. */ file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.logins package org.mozilla.fenix.settings.logins
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify