Skip to content

Commit 01bf909

Browse files
Autofill Feature Flag to enable to existing users (#4829)
Task/Issue URL: https://app.asana.com/0/414730916066338/1207502275219273/f ### Description Introduces new feature flag that will enable autofill to users that had it disabled by default and never enabled it manually. Integrates feature flag in `RealAutofillDefaultStateDecider` ### Steps to test this PR _Feature 1_ - [ ] Install old version where autofill was disabled by default (e.g: 5.178.0, you have a download link inside Asana task) - [ ] open the app and ensure autofill is disabled - [ ] checkout this branch - [ ] Go to `RealAutofillDefaultStateDecider`, and comment logic in line 35 (to avoid being enabled because internal build) - [ ] Go to `AndroidUserBrowserProperties` and return 10 in line `daysSinceInstalled` (simulate existing user) - [ ] install new version - [ ] go to passwords, ensure autofill is now enabled ### UI changes | Before | After | | ------ | ----- | !(Upload before screenshot)|(Upload after screenshot)| --------- Co-authored-by: Craig Russell <[email protected]>
1 parent 45f7066 commit 01bf909

File tree

62 files changed

+1314
-288
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1314
-288
lines changed

autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillFeature.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.duckduckgo.autofill.api
1818

1919
import com.duckduckgo.feature.toggles.api.Toggle
20+
import com.duckduckgo.feature.toggles.api.Toggle.InternalAlwaysEnabled
2021

2122
/**
2223
* This is the class that represents the autofill feature flags
@@ -72,4 +73,21 @@ interface AutofillFeature {
7273
*/
7374
@Toggle.DefaultValue(false)
7475
fun onByDefault(): Toggle
76+
77+
/**
78+
* Remote Flag to control logic that decides if existing users that had autofill disabled by default, should have it enabled
79+
* @return `true` when the remote config has the global "onForExistingUsers" autofill sub-feature flag enabled
80+
* If the remote feature is not present defaults to `false`
81+
*/
82+
@Toggle.DefaultValue(false)
83+
@InternalAlwaysEnabled
84+
fun onForExistingUsers(): Toggle
85+
86+
/**
87+
* Remote Flag that enables the old dialog prompt to disable autofill
88+
* @return `true` when the remote config has the global "allowToDisableAutofill" autofill sub-feature flag enabled
89+
* If the remote feature is not present defaults to `false`
90+
*/
91+
@Toggle.DefaultValue(false)
92+
fun showDisableDialogAutofillPrompt(): Toggle
7593
}

autofill/autofill-api/src/main/java/com/duckduckgo/autofill/api/AutofillScreens.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ enum class AutofillSettingsLaunchSource {
5858
InternalDevSettings,
5959
Unknown,
6060
NewTabShortcut,
61+
DisableInSettingsPrompt,
6162
}

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/SecureStoreBackedAutofillStore.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.duckduckgo.autofill.impl.securestorage.SecureStorage
2626
import com.duckduckgo.autofill.impl.securestorage.WebsiteLoginDetails
2727
import com.duckduckgo.autofill.impl.securestorage.WebsiteLoginDetailsWithCredentials
2828
import com.duckduckgo.autofill.impl.store.InternalAutofillStore
29+
import com.duckduckgo.autofill.impl.ui.credential.saving.declines.AutofillDeclineStore
2930
import com.duckduckgo.autofill.impl.urlmatcher.AutofillUrlMatcher
3031
import com.duckduckgo.autofill.store.AutofillPrefsStore
3132
import com.duckduckgo.autofill.store.LastUpdatedTimeProvider
@@ -44,7 +45,8 @@ import kotlinx.coroutines.withContext
4445
import timber.log.Timber
4546

4647
@SingleInstanceIn(AppScope::class)
47-
@ContributesBinding(AppScope::class)
48+
@ContributesBinding(AppScope::class, AutofillDeclineStore::class)
49+
@ContributesBinding(AppScope::class, InternalAutofillStore::class)
4850
class SecureStoreBackedAutofillStore @Inject constructor(
4951
private val secureStorage: SecureStorage,
5052
private val lastUpdatedTimeProvider: LastUpdatedTimeProvider,
@@ -53,7 +55,7 @@ class SecureStoreBackedAutofillStore @Inject constructor(
5355
private val autofillUrlMatcher: AutofillUrlMatcher,
5456
private val syncCredentialsListener: SyncCredentialsListener,
5557
passwordStoreEventListenersPlugins: PluginPoint<PasswordStoreEventListener>,
56-
) : InternalAutofillStore {
58+
) : InternalAutofillStore, AutofillDeclineStore {
5759

5860
private val passwordStoreEventListeners = passwordStoreEventListenersPlugins.getPlugins()
5961

@@ -75,12 +77,14 @@ class SecureStoreBackedAutofillStore @Inject constructor(
7577
override var autofillDeclineCount: Int
7678
get() = autofillPrefsStore.autofillDeclineCount
7779
set(value) {
80+
Timber.i("Autofill: Setting autofillDeclineCount to %d", value)
7881
autofillPrefsStore.autofillDeclineCount = value
7982
}
8083

8184
override var monitorDeclineCounts: Boolean
8285
get() = autofillPrefsStore.monitorDeclineCounts
8386
set(value) {
87+
Timber.i("Autofill: Setting monitorDeclineCounts to %b", value)
8488
autofillPrefsStore.monitorDeclineCounts = value
8589
}
8690

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/pixel/AutofillPixelNames.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.duckduckgo.autofill.impl.pixel
1818

1919
import com.duckduckgo.app.statistics.pixels.Pixel
20+
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SNACKBAR_OPEN_SETTINGS
21+
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SNACKBAR_SHOWN
2022
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENGAGEMENT_ACTIVE_USER
2123
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENGAGEMENT_ENABLED_USER
2224
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ENGAGEMENT_ONBOARDED_USER
@@ -32,6 +34,10 @@ import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PAS
3234
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_USER_JOURNEY_SUCCESSFUL
3335
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_USER_JOURNEY_UNSUCCESSFUL
3436
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_IMPORT_PASSWORDS_USER_TOOK_NO_ACTION
37+
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ONBOARDING_SAVE_PROMPT_DISMISSED
38+
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ONBOARDING_SAVE_PROMPT_EXCLUDE
39+
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ONBOARDING_SAVE_PROMPT_SAVED
40+
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_ONBOARDING_SAVE_PROMPT_SHOWN
3541
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SITE_BREAKAGE_REPORT
3642
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SITE_BREAKAGE_REPORT_AVAILABLE
3743
import com.duckduckgo.autofill.impl.pixel.AutofillPixelNames.AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_CONFIRMED
@@ -46,6 +52,11 @@ import com.duckduckgo.di.scopes.AppScope
4652
import com.squareup.anvil.annotations.ContributesMultibinding
4753

4854
enum class AutofillPixelNames(override val pixelName: String) : Pixel.PixelName {
55+
AUTOFILL_ONBOARDING_SAVE_PROMPT_SHOWN("autofill_logins_save_login_inline_onboarding_displayed"),
56+
AUTOFILL_ONBOARDING_SAVE_PROMPT_DISMISSED("autofill_logins_save_login_inline_onboarding_dismissed"),
57+
AUTOFILL_ONBOARDING_SAVE_PROMPT_SAVED("autofill_logins_save_login_inline_onboarding_confirmed"),
58+
AUTOFILL_ONBOARDING_SAVE_PROMPT_EXCLUDE("autofill_logins_save_login_onboarding_exclude_site_confirmed"),
59+
4960
AUTOFILL_SAVE_LOGIN_PROMPT_SHOWN("m_autofill_logins_save_login_inline_displayed"),
5061
AUTOFILL_SAVE_LOGIN_PROMPT_DISMISSED("m_autofill_logins_save_login_inline_dismissed"),
5162
AUTOFILL_SAVE_LOGIN_PROMPT_SAVED("m_autofill_logins_save_login_inline_confirmed"),
@@ -75,6 +86,9 @@ enum class AutofillPixelNames(override val pixelName: String) : Pixel.PixelName
7586
AUTOFILL_AUTHENTICATION_TO_AUTOFILL_AUTH_FAILURE("m_autofill_logins_fill_login_inline_authentication_device-auth_failed"),
7687
AUTOFILL_AUTHENTICATION_TO_AUTOFILL_AUTH_CANCELLED("m_autofill_logins_fill_login_inline_authentication_device-auth_cancelled"),
7788

89+
AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SNACKBAR_SHOWN("autofill_logins_save_disable_snackbar_shown"),
90+
AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SNACKBAR_OPEN_SETTINGS("autofill_logins_save_disable_snackbar_open_settings"),
91+
7892
AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SHOWN("m_autofill_logins_save_disable-prompt_shown"),
7993
AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_KEEP_USING("m_autofill_logins_save_disable-prompt_autofill-kept"),
8094
AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_DISABLE("m_autofill_logins_save_disable-prompt_autofill-disabled"),
@@ -184,6 +198,14 @@ object AutofillPixelsRequiringDataCleaning : PixelParamRemovalPlugin {
184198
AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_DISPLAYED.pixelName to PixelParameter.removeAtb(),
185199
AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_DISMISSED.pixelName to PixelParameter.removeAtb(),
186200
AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_CONFIRMED.pixelName to PixelParameter.removeAtb(),
201+
202+
AUTOFILL_ONBOARDING_SAVE_PROMPT_SHOWN.pixelName to PixelParameter.removeAtb(),
203+
AUTOFILL_ONBOARDING_SAVE_PROMPT_DISMISSED.pixelName to PixelParameter.removeAtb(),
204+
AUTOFILL_ONBOARDING_SAVE_PROMPT_SAVED.pixelName to PixelParameter.removeAtb(),
205+
AUTOFILL_ONBOARDING_SAVE_PROMPT_EXCLUDE.pixelName to PixelParameter.removeAtb(),
206+
207+
AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SNACKBAR_SHOWN.pixelName to PixelParameter.removeAtb(),
208+
AUTOFILL_DECLINE_PROMPT_TO_DISABLE_AUTOFILL_SNACKBAR_OPEN_SETTINGS.pixelName to PixelParameter.removeAtb(),
187209
)
188210
}
189211
}

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/store/InternalAutofillStore.kt

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,6 @@ interface InternalAutofillStore : AutofillStore {
4040
*/
4141
var hasEverBeenPromptedToSaveLogin: Boolean
4242

43-
/**
44-
* Whether to monitor autofill decline counts or not
45-
* Used to determine whether we should actively detect when a user new to autofill doesn't appear to want it enabled
46-
*/
47-
var monitorDeclineCounts: Boolean
48-
49-
/**
50-
* A count of the number of autofill declines the user has made, persisted across all sessions.
51-
* Used to determine whether we should prompt a user new to autofill to disable it if they don't appear to want it enabled
52-
*/
53-
var autofillDeclineCount: Int
54-
5543
/**
5644
* Find saved credential for the given id
5745
* @param id of the saved credential

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillManagementActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,11 @@ class AutofillManagementActivity : DuckDuckGoActivity() {
249249
resetToolbar()
250250
val currentUrl = extractSuggestionsUrl()
251251
val privacyProtectionStatus = extractPrivacyProtectionEnabled()
252+
val launchSource = extractLaunchSource()
252253
Timber.v("showListMode. currentUrl is %s", currentUrl)
253254

254255
supportFragmentManager.commitNow {
255-
val fragment = AutofillManagementListMode.instance(currentUrl, privacyProtectionStatus)
256+
val fragment = AutofillManagementListMode.instance(currentUrl, privacyProtectionStatus, launchSource)
256257
replace(R.id.fragment_container_view, fragment, TAG_ALL_CREDENTIALS)
257258
}
258259
}

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/AutofillSettingsViewModel.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.duckduckgo.app.browser.favicon.FaviconManager
2424
import com.duckduckgo.app.statistics.pixels.Pixel
2525
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource
2626
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource.BrowserOverflow
27+
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource.DisableInSettingsPrompt
2728
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource.SettingsActivity
2829
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource.Sync
2930
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
@@ -537,11 +538,11 @@ class AutofillSettingsViewModel @Inject constructor(
537538
pixel.fire(AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_ENABLED)
538539
}
539540

540-
fun onDisableAutofill() {
541+
fun onDisableAutofill(autofillSettingsLaunchSource: AutofillSettingsLaunchSource?) {
541542
autofillStore.autofillEnabled = false
542543
_viewState.value = viewState.value.copy(autofillEnabled = false)
543544

544-
pixel.fire(AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED)
545+
pixel.fire(AUTOFILL_ENABLE_AUTOFILL_TOGGLE_MANUALLY_DISABLED, mapOf("source" to autofillSettingsLaunchSource?.asString().orEmpty()))
545546
}
546547

547548
fun onSearchQueryChanged(searchText: String) {
@@ -646,12 +647,7 @@ class AutofillSettingsViewModel @Inject constructor(
646647
fun sendLaunchPixel(launchSource: AutofillSettingsLaunchSource) {
647648
Timber.v("Opened autofill management screen from from %s", launchSource)
648649

649-
val source = when (launchSource) {
650-
SettingsActivity -> "settings"
651-
BrowserOverflow -> "overflow_menu"
652-
Sync -> "sync"
653-
else -> null
654-
}
650+
val source = launchSource.asString()
655651

656652
if (source != null) {
657653
pixel.fire(AUTOFILL_MANAGEMENT_SCREEN_OPENED, mapOf("source" to source))
@@ -760,6 +756,16 @@ class AutofillSettingsViewModel @Inject constructor(
760756
pixel.fire(AUTOFILL_SITE_BREAKAGE_REPORT_CONFIRMATION_DISMISSED)
761757
}
762758

759+
private fun AutofillSettingsLaunchSource.asString(): String? {
760+
return when (this) {
761+
SettingsActivity -> "settings"
762+
BrowserOverflow -> "overflow_menu"
763+
Sync -> "sync"
764+
DisableInSettingsPrompt -> "disable_prompt"
765+
else -> null
766+
}
767+
}
768+
763769
data class ViewState(
764770
val autofillEnabled: Boolean = true,
765771
val showAutofillEnabledToggle: Boolean = true,

autofill/autofill-impl/src/main/java/com/duckduckgo/autofill/impl/ui/credential/management/viewing/AutofillManagementListMode.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import com.duckduckgo.anvil.annotations.InjectWith
3636
import com.duckduckgo.app.browser.favicon.FaviconManager
3737
import com.duckduckgo.app.statistics.pixels.Pixel
3838
import com.duckduckgo.app.tabs.BrowserNav
39+
import com.duckduckgo.autofill.api.AutofillSettingsLaunchSource
3940
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
4041
import com.duckduckgo.autofill.impl.R
4142
import com.duckduckgo.autofill.impl.databinding.FragmentAutofillManagementListModeBinding
@@ -133,7 +134,7 @@ class AutofillManagementListMode : DuckDuckGoFragment(R.layout.fragment_autofill
133134

134135
private val globalAutofillToggleListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
135136
if (!lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) return@OnCheckedChangeListener
136-
if (isChecked) viewModel.onEnableAutofill() else viewModel.onDisableAutofill()
137+
if (isChecked) viewModel.onEnableAutofill() else viewModel.onDisableAutofill(getAutofillSettingsLaunchSource())
137138
}
138139

139140
private fun configureToggle() {
@@ -246,6 +247,8 @@ class AutofillManagementListMode : DuckDuckGoFragment(R.layout.fragment_autofill
246247

247248
private fun getCurrentSiteUrl() = arguments?.getString(ARG_CURRENT_URL, null)
248249
private fun getPrivacyProtectionEnabled() = arguments?.getBoolean(ARG_PRIVACY_PROTECTION_STATUS)
250+
private fun getAutofillSettingsLaunchSource(): AutofillSettingsLaunchSource? =
251+
arguments?.getSerializable(ARG_AUTOFILL_SETTINGS_LAUNCH_SOURCE) as AutofillSettingsLaunchSource?
249252

250253
private fun parentBinding() = parentActivity()?.binding
251254
private fun parentActivity() = (activity as AutofillManagementActivity?)
@@ -553,19 +556,24 @@ class AutofillManagementListMode : DuckDuckGoFragment(R.layout.fragment_autofill
553556
}
554557

555558
companion object {
556-
fun instance(currentUrl: String? = null, privacyProtectionEnabled: Boolean?) =
559+
fun instance(currentUrl: String? = null, privacyProtectionEnabled: Boolean?, source: AutofillSettingsLaunchSource? = null) =
557560
AutofillManagementListMode().apply {
558561
arguments = Bundle().apply {
559562
putString(ARG_CURRENT_URL, currentUrl)
560563

561564
if (privacyProtectionEnabled != null) {
562565
putBoolean(ARG_PRIVACY_PROTECTION_STATUS, privacyProtectionEnabled)
563566
}
567+
568+
if (source != null) {
569+
putSerializable(ARG_AUTOFILL_SETTINGS_LAUNCH_SOURCE, source)
570+
}
564571
}
565572
}
566573

567574
private const val ARG_CURRENT_URL = "ARG_CURRENT_URL"
568575
private const val ARG_PRIVACY_PROTECTION_STATUS = "ARG_PRIVACY_PROTECTION_STATUS"
576+
private const val ARG_AUTOFILL_SETTINGS_LAUNCH_SOURCE = "ARG_AUTOFILL_SETTINGS_LAUNCH_SOURCE"
569577
}
570578
}
571579

0 commit comments

Comments
 (0)