Skip to content

Commit

Permalink
Launch import flow from Password management screen
Browse files Browse the repository at this point in the history
  • Loading branch information
CDRussell committed Nov 14, 2024
1 parent 14e9701 commit 7fbdaee
Show file tree
Hide file tree
Showing 26 changed files with 1,436 additions and 29 deletions.
1 change: 1 addition & 0 deletions autofill/autofill-impl/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ dependencies {
implementation AndroidX.biometric

implementation "net.zetetic:android-database-sqlcipher:_"
implementation "com.facebook.shimmer:shimmer:_"

// Testing dependencies
testImplementation "org.mockito.kotlin:mockito-kotlin:_"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ interface AutofillAuthorizationGracePeriod {
*/
fun recordSuccessfulAuthorization()

/**
* Requests an extended grace period. This may extend the grace period to a longer duration.
*/
fun requestExtendedGracePeriod()

/**
* Removes the request for an extended grace period
*/
fun removeRequestForExtendedGracePeriod()

/**
* Invalidates the grace period, so that the next call to [isAuthRequired] will return true
*/
Expand All @@ -53,12 +63,21 @@ class AutofillTimeBasedAuthorizationGracePeriod @Inject constructor(
) : AutofillAuthorizationGracePeriod {

private var lastSuccessfulAuthTime: Long? = null
private var extendedGraceTimeRequested: Long? = null

override fun recordSuccessfulAuthorization() {
lastSuccessfulAuthTime = timeProvider.currentTimeMillis()
Timber.v("Recording timestamp of successful auth")
}

override fun requestExtendedGracePeriod() {
extendedGraceTimeRequested = timeProvider.currentTimeMillis()
}

override fun removeRequestForExtendedGracePeriod() {
extendedGraceTimeRequested = null
}

override fun isAuthRequired(): Boolean {
lastSuccessfulAuthTime?.let { lastAuthTime ->
val timeSinceLastAuth = timeProvider.currentTimeMillis() - lastAuthTime
Expand All @@ -67,17 +86,35 @@ class AutofillTimeBasedAuthorizationGracePeriod @Inject constructor(
Timber.v("Within grace period; auth not required")
return false
}

if (inExtendedGracePeriod()) {
Timber.v("Within extended grace period; auth not required")
return false
}
}

Timber.v("No last auth time recorded or outside grace period; auth required")

return true
}

private fun inExtendedGracePeriod(): Boolean {
val extendedRequest = extendedGraceTimeRequested
if (extendedRequest == null) {
return false
} else {
val timeSinceExtendedGrace = timeProvider.currentTimeMillis() - extendedRequest
return timeSinceExtendedGrace <= AUTH_GRACE_EXTENDED_PERIOD_MS
}
}

override fun invalidate() {
lastSuccessfulAuthTime = null
removeRequestForExtendedGracePeriod()
}

companion object {
private const val AUTH_GRACE_PERIOD_MS = 15_000
private const val AUTH_GRACE_EXTENDED_PERIOD_MS = 180_000
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ import com.duckduckgo.autofill.impl.databinding.FragmentImportGooglePasswordsWeb
import com.duckduckgo.autofill.impl.importing.blob.GooglePasswordBlobConsumer
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordResult.Companion.RESULT_KEY
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordResult.Companion.RESULT_KEY_DETAILS
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.*
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.Initializing
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.LoadStartPage
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.NavigatingBack
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.UserCancelledImportFlow
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.UserFinishedCannotImport
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.UserFinishedImportFlow
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowViewModel.ViewState.WebContentShowing
import com.duckduckgo.autofill.impl.importing.gpm.webflow.ImportGooglePasswordsWebFlowWebViewClient.NewPageCallback
import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.NoOpAutofillCallback
import com.duckduckgo.autofill.impl.importing.gpm.webflow.autofill.NoOpAutofillEventListener
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENGAGEMENT
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENGAGEMENT_ONBOARDED_USER
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENGAGEMENT_STACKED_LOGINS
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_COPIED_DESKTOP_LINK
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_CTA_BUTTON
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_GET_DESKTOP_BROWSER
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_OVERFLOW_MENU
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_SHARED_DESKTOP_LINK
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_SYNC_WITH_DESKTOP
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_USER_JOURNEY_RESTARTED
Expand All @@ -43,6 +41,8 @@ import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SITE_BREAK
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_CONFIRMED
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_DISMISSED
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_DISPLAYED
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SYNC_DESKTOP_PASSWORDS_CTA_BUTTON
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SYNC_DESKTOP_PASSWORDS_OVERFLOW_MENU
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.EMAIL_TOOLTIP_DISMISSED
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.EMAIL_USE_ADDRESS
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.EMAIL_USE_ALIAS
Expand Down Expand Up @@ -142,8 +142,8 @@ enum class AutofillPixelNames(override val pixelName: String) : Pixel.PixelName
AUTOFILL_TOGGLED_ON_SEARCH("m_autofill_toggled_on"),
AUTOFILL_TOGGLED_OFF_SEARCH("m_autofill_toggled_off"),

AUTOFILL_IMPORT_PASSWORDS_CTA_BUTTON("m_autofill_logins_import_no_passwords"),
AUTOFILL_IMPORT_PASSWORDS_OVERFLOW_MENU("m_autofill_logins_import"),
AUTOFILL_SYNC_DESKTOP_PASSWORDS_CTA_BUTTON("m_autofill_logins_import_no_passwords"),
AUTOFILL_SYNC_DESKTOP_PASSWORDS_OVERFLOW_MENU("m_autofill_logins_import"),
AUTOFILL_IMPORT_PASSWORDS_GET_DESKTOP_BROWSER("m_autofill_logins_import_get_desktop"),
AUTOFILL_IMPORT_PASSWORDS_SYNC_WITH_DESKTOP("m_autofill_logins_import_sync"),
AUTOFILL_IMPORT_PASSWORDS_USER_TOOK_NO_ACTION("m_autofill_logins_import_no-action"),
Expand Down Expand Up @@ -177,8 +177,8 @@ object AutofillPixelsRequiringDataCleaning : PixelParamRemovalPlugin {
AUTOFILL_ENGAGEMENT_ONBOARDED_USER.pixelName to PixelParameter.removeAtb(),
AUTOFILL_ENGAGEMENT_STACKED_LOGINS.pixelName to PixelParameter.removeAtb(),

AUTOFILL_IMPORT_PASSWORDS_CTA_BUTTON.pixelName to PixelParameter.removeAtb(),
AUTOFILL_IMPORT_PASSWORDS_OVERFLOW_MENU.pixelName to PixelParameter.removeAtb(),
AUTOFILL_SYNC_DESKTOP_PASSWORDS_CTA_BUTTON.pixelName to PixelParameter.removeAtb(),
AUTOFILL_SYNC_DESKTOP_PASSWORDS_OVERFLOW_MENU.pixelName to PixelParameter.removeAtb(),
AUTOFILL_IMPORT_PASSWORDS_GET_DESKTOP_BROWSER.pixelName to PixelParameter.removeAtb(),
AUTOFILL_IMPORT_PASSWORDS_SYNC_WITH_DESKTOP.pixelName to PixelParameter.removeAtb(),
AUTOFILL_IMPORT_PASSWORDS_USER_TOOK_NO_ACTION.pixelName to PixelParameter.removeAtb(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@ import android.util.Patterns
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.duckduckgo.anvil.annotations.ContributesViewModel
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability.DocumentStartJavaScript
import com.duckduckgo.app.browser.api.WebViewCapabilityChecker.WebViewCapability.WebMessageListener
import com.duckduckgo.app.browser.favicon.FaviconManager
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.autofill.api.AutofillFeature
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource.BrowserOverflow
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource.BrowserSnackbar
Expand Down Expand Up @@ -89,6 +93,7 @@ import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsVie
import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.ListModeCommand.LaunchResetNeverSaveListConfirmation
import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.ListModeCommand.PromptUserToAuthenticateMassDeletion
import com.duckduckgo.autofill.impl.ui.credential.management.AutofillSettingsViewModel.ListModeCommand.ReevalutePromotions
import com.duckduckgo.autofill.impl.ui.credential.management.importpassword.ImportPasswordsManagementViewState
import com.duckduckgo.autofill.impl.ui.credential.management.neversaved.NeverSavedSitesViewState
import com.duckduckgo.autofill.impl.ui.credential.management.searching.CredentialListFilter
import com.duckduckgo.autofill.impl.ui.credential.management.viewing.duckaddress.DuckAddressIdentifier
Expand Down Expand Up @@ -133,6 +138,8 @@ class AutofillSettingsViewModel @Inject constructor(
private val autofillBreakageReportSender: AutofillBreakageReportSender,
private val autofillBreakageReportDataStore: AutofillSiteBreakageReportingDataStore,
private val autofillBreakageReportCanShowRules: AutofillBreakageReportCanShowRules,
private val autofillFeature: AutofillFeature,
private val webViewCapabilityChecker: WebViewCapabilityChecker,
) : ViewModel() {

private val _viewState = MutableStateFlow(ViewState())
Expand All @@ -141,6 +148,9 @@ class AutofillSettingsViewModel @Inject constructor(
private val _neverSavedSitesViewState = MutableStateFlow(NeverSavedSitesViewState())
val neverSavedSitesViewState: StateFlow<NeverSavedSitesViewState> = _neverSavedSitesViewState

private val _importPasswordsViewState = MutableStateFlow(ImportPasswordsManagementViewState())
val importPasswordsViewState: StateFlow<ImportPasswordsManagementViewState> = _importPasswordsViewState

private val _commands = MutableStateFlow<List<Command>>(emptyList())
val commands: StateFlow<List<Command>> = _commands

Expand Down Expand Up @@ -431,6 +441,14 @@ class AutofillSettingsViewModel @Inject constructor(
_neverSavedSitesViewState.value = NeverSavedSitesViewState(showOptionToReset = count > 0)
}
}

viewModelScope.launch(dispatchers.io()) {
val gpmImport = autofillFeature.self().isEnabled() && autofillFeature.canImportFromGooglePasswordManager().isEnabled()
val webViewWebMessageSupport = webViewCapabilityChecker.isSupported(WebMessageListener)
val webViewDocumentStartJavascript = webViewCapabilityChecker.isSupported(DocumentStartJavaScript)
val canImport = gpmImport && webViewWebMessageSupport && webViewDocumentStartJavascript
_importPasswordsViewState.value = ImportPasswordsManagementViewState(canImport)
}
}

private suspend fun isBreakageReportingAllowed(): Boolean {
Expand Down Expand Up @@ -690,7 +708,9 @@ class AutofillSettingsViewModel @Inject constructor(
}

fun onImportPasswords() {
addCommand(LaunchImportPasswords)
viewModelScope.launch(dispatchers.io()) {
addCommand(LaunchImportPasswords)
}
}

fun onReportBreakageClicked() {
Expand All @@ -702,7 +722,10 @@ class AutofillSettingsViewModel @Inject constructor(
}
}

fun updateCurrentSite(currentUrl: String?, privacyProtectionEnabled: Boolean?) {
fun updateCurrentSite(
currentUrl: String?,
privacyProtectionEnabled: Boolean?,
) {
val updatedReportBreakageState = _viewState.value.reportBreakageState.copy(
currentUrl = currentUrl,
privacyProtectionEnabled = privacyProtectionEnabled,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2024 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.autofill.impl.ui.credential.management.importpassword

data class ImportPasswordsManagementViewState(
val canImportFromGooglePasswords: Boolean = false,
)
Loading

0 comments on commit 7fbdaee

Please sign in to comment.