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 mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.lib.dataprotect.SecureAbove22Preferences
import mozilla.components.service.experiments.Experiments
import mozilla.components.service.sync.logins.AsyncLoginsStorage
import org.mozilla.fenix.Config
import org.mozilla.fenix.utils.Settings
import org.mozilla.geckoview.GeckoRuntime
@ -17,7 +19,12 @@ object GeckoProvider {
private var runtime: GeckoRuntime? = null
@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) {
runtime = createRuntime(context)
}

View File

@ -4,9 +4,14 @@
import android.content.Context
import android.os.Bundle
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
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.ext.settings
import org.mozilla.fenix.utils.Settings
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoRuntimeSettings
@ -16,15 +21,23 @@ object GeckoProvider {
private var runtime: GeckoRuntime? = null
@Synchronized
fun getOrCreateRuntime(context: Context): GeckoRuntime {
fun getOrCreateRuntime(
context: Context,
storage: AsyncLoginsStorage,
securePreferences: SecureAbove22Preferences
): GeckoRuntime {
if (runtime == null) {
runtime = createRuntime(context)
runtime = createRuntime(context, storage, securePreferences)
}
return runtime!!
}
private fun createRuntime(context: Context): GeckoRuntime {
private fun createRuntime(
context: Context,
storage: AsyncLoginsStorage,
securePreferences: SecureAbove22Preferences
): GeckoRuntime {
val builder = GeckoRuntimeSettings.Builder()
testConfig?.let {
@ -46,6 +59,14 @@ object GeckoProvider {
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.SitePermissionsFeature
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.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
@ -317,6 +318,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
store = store,
customTabId = customTabSessionId,
fragmentManager = parentFragmentManager,
loginValidationDelegate = DefaultLoginValidationDelegate(
context.components.core.asyncPasswordsStorage,
context.components.core.getSecureAbove22Preferences()
),
isSaveLoginEnabled = {
context.settings().shouldPromptToSaveLogins
},
shareDelegate = object : ShareDelegate {
override fun showShareSheet(
context: Context,

View File

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

View File

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

View File

@ -20,6 +20,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver
@ -36,7 +37,7 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import java.util.concurrent.Executors
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LargeClass")
class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
@TargetApi(M)
@ -83,10 +84,32 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
.build()
}
@Suppress("ComplexMethod")
override fun onResume() {
super.onResume()
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)
findPreference<Preference>(savedLoginsKey)?.setOnPreferenceClickListener {
if (Build.VERSION.SDK_INT >= M && isHardwareAvailable && hasBiometricEnrolled) {
@ -235,33 +258,31 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
}
private fun navigateToSavedLoginsFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) {
context?.components?.analytics?.metrics?.track(Event.OpenLogins)
val directions = LoginsFragmentDirections.actionLoginsFragmentToSavedLoginsFragment()
findNavController().navigate(directions)
}
context?.components?.analytics?.metrics?.track(Event.OpenLogins)
val directions = LoginsFragmentDirections.actionLoginsFragmentToSavedLoginsFragment()
findNavController().navigate(directions)
}
private fun navigateToAccountSettingsFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) {
val directions =
LoginsFragmentDirections.actionLoginsFragmentToAccountSettingsFragment()
findNavController().navigate(directions)
}
val directions =
LoginsFragmentDirections.actionLoginsFragmentToAccountSettingsFragment()
findNavController().navigate(directions)
}
private fun navigateToAccountProblemFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) {
val directions = LoginsFragmentDirections.actionLoginsFragmentToAccountProblemFragment()
findNavController().navigate(directions)
}
val directions = LoginsFragmentDirections.actionLoginsFragmentToAccountProblemFragment()
findNavController().navigate(directions)
}
private fun navigateToTurnOnSyncFragment() {
if (findNavController().currentDestination?.id == R.id.loginsFragment) {
val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment()
findNavController().navigate(directions)
}
val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment()
findNavController().navigate(directions)
}
private fun navigateToSaveLoginSettingFragment() {
val directions =
LoginsFragmentDirections.actionLoginsFragmentToSaveLoginSettingFragment()
findNavController().navigate(directions)
}
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
* 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.text.InputType

View File

@ -2,7 +2,7 @@
* 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.logins
package org.mozilla.fenix.settings.logins
import android.view.LayoutInflater
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
* 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.view.LayoutInflater
@ -88,7 +88,7 @@ class SavedLoginsFragment : Fragment() {
private suspend fun loadAndMapLogins() {
val syncedLogins = withContext(IO) {
requireContext().components.core.passwordsStorage.withUnlocked {
requireContext().components.core.syncablePasswordsStorage.withUnlocked {
it.list().await().map { item ->
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
* 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 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
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

View File

@ -2,7 +2,7 @@
* 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.logins
package org.mozilla.fenix.settings.logins
import android.view.View
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
* 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.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(
appContext.getPreferenceKey(R.string.pref_key_fxa_signed_in),
default = true

View File

@ -7,4 +7,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
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
android:id="@+id/action_loginsFragment_to_turnOnSyncFragment"
app:destination="@id/turnOnSyncFragment" />
<action
android:id="@+id/action_loginsFragment_to_saveLoginSettingFragment"
app:destination="@id/saveLoginSettingFragment" />
</fragment>
<fragment
@ -650,7 +653,7 @@
</fragment>
<fragment
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">
<action
android:id="@+id/action_savedLoginsFragment_to_savedLoginSiteInfoFragment"
@ -658,11 +661,11 @@
</fragment>
<fragment
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">
<argument
android:name="savedLoginItem"
app:argType="org.mozilla.fenix.logins.SavedLoginsItem" />
app:argType="org.mozilla.fenix.settings.logins.SavedLoginsItem" />
</fragment>
<fragment
android:id="@+id/addSearchEngineFragment"
@ -680,4 +683,8 @@
<fragment
android:id="@+id/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>

View File

@ -114,6 +114,10 @@
<string name="pref_key_tracking_protection_onboarding" translatable="false">pref_key_tracking_protection_onboarding</string>
<!-- 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_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>

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
- 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.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
android:key="@string/pref_key_password_sync_logins"
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
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.verify