Skip to content

Commit

Permalink
Add dev settings for quickly adding Logins for testing (including dup…
Browse files Browse the repository at this point in the history
…licates) (#3734)

<!--
Note: This checklist is a reminder of our shared engineering
expectations.
The items in Bold are required
If your PR involves UI changes:
1. Upload screenshots or screencasts that illustrate the changes before
/ after
2. Add them under the UI changes section (feel free to add more columns
if needed)
If your PR does not involve UI changes, you can remove the **UI
changes** section

At a minimum, make sure your changes are tested in API 23 and one of the
more recent API levels available.
-->

Task/Issue URL: https://app.asana.com/0/0/1205768484548248/f

### Description
Adds dev settings for autofill logins, allowing for quickly:

- adding some sample logins
- adding duplicate logins for the same domain
- adding duplicate logins for same e-TLD+1 but different subdomains
- deleting all saved logins

### Steps to test this PR

- [ ] Visit Settings->Autofill Dev Settings
- [ ] Tap `Add a few sample logins`, tap `View saved logins`, and verify
you see 4 logins
- [ ] Revisit dev settings, and tap `Delete all saved logins` and
confirm. Verify `Number of saved logins: ` shows 0
- [ ] Tap `Add duplicate logins (same domain)`, tap `View saved logins`,
and verify you see 3 identical logins
- [ ] Revisit dev settings, and tap `Delete all saved logins` and
confirm.
- [ ] Tap `Add duplicate logins (same domain)`, tap `View saved logins`,
and verify you see 3 logins, which differ only because of their
subdomains


![combined](https://github.com/duckduckgo/Android/assets/1336281/cf1d80f0-c3d7-4ba5-80c6-1cc353616e4e)
  • Loading branch information
CDRussell committed Oct 26, 2023
1 parent 68ed6f2 commit 41fd748
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,26 @@ import androidx.lifecycle.repeatOnLifecycle
import com.duckduckgo.anvil.annotations.InjectWith
import com.duckduckgo.app.global.DispatcherProvider
import com.duckduckgo.app.global.DuckDuckGoActivity
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
import com.duckduckgo.autofill.api.email.EmailManager
import com.duckduckgo.autofill.api.store.AutofillStore
import com.duckduckgo.autofill.impl.email.incontext.store.EmailProtectionInContextDataStore
import com.duckduckgo.autofill.impl.ui.credential.management.AutofillManagementActivity
import com.duckduckgo.autofill.internal.R.string
import com.duckduckgo.autofill.internal.databinding.ActivityAutofillInternalSettingsBinding
import com.duckduckgo.browser.api.UserBrowserProperties
import com.duckduckgo.di.scopes.ActivityScope
import com.duckduckgo.mobile.android.ui.view.dialog.RadioListAlertDialogBuilder
import com.duckduckgo.mobile.android.ui.view.dialog.RadioListAlertDialogBuilder.EventListener
import com.duckduckgo.mobile.android.ui.view.dialog.TextAlertDialogBuilder
import com.duckduckgo.mobile.android.ui.viewbinding.viewBinding
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import logcat.logcat

@InjectWith(ActivityScope::class)
class AutofillInternalSettingsActivity : DuckDuckGoActivity() {
Expand All @@ -54,6 +64,11 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() {
@Inject
lateinit var dispatchers: DispatcherProvider

@Inject
lateinit var autofillStore: AutofillStore

private val dateFormatter = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.MEDIUM, SimpleDateFormat.MEDIUM)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
Expand All @@ -64,29 +79,119 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() {
}

private fun configureUiEventHandlers() {
configureEmailProtectionUiEventHandlers()
configureLoginsUiEventHandlers()
}

private fun configureLoginsUiEventHandlers() {
binding.addSampleLoginsButton.setClickListener {
val timestamp = dateFormatter.format(System.currentTimeMillis())
lifecycleScope.launch(dispatchers.io()) {
sampleCredentials(domain = "fill.dev", username = "alice-$timestamp", password = "alice-$timestamp").save()
sampleCredentials(domain = "fill.dev", username = "bob-$timestamp", password = "bob-$timestamp").save()
sampleCredentials(domain = "subdomain1.fill.dev", username = "charlie-$timestamp", password = "charlie-$timestamp").save()
sampleCredentials(domain = "subdomain2.fill.dev", username = "daniel-$timestamp", password = "daniel-$timestamp").save()
}
}

binding.addSampleLoginsContainingDuplicatesSameDomainButton.setClickListener {
lifecycleScope.launch(dispatchers.io()) {
repeat(3) { sampleCredentials(domain = "fill.dev", username = "user").save() }
}
}

binding.addSampleLoginsContainingDuplicatesAcrossSubdomainsButton.setClickListener {
lifecycleScope.launch(dispatchers.io()) {
repeat(3) { sampleCredentials("https://subdomain$it.fill.dev", username = "user").save() }
}
}

binding.clearAllSavedLoginsButton.setClickListener {
lifecycleScope.launch(dispatchers.io()) {
val count = autofillStore.getCredentialCount().first()
withContext(dispatchers.main()) {
confirmLoginDeletion(count)
}
}
}

// keep the number of saved logins up-to-date
lifecycleScope.launch(dispatchers.main()) {
repeatOnLifecycle(Lifecycle.State.STARTED) {
autofillStore.getCredentialCount().collect { count ->
binding.clearAllSavedLoginsButton.isEnabled = count > 0
binding.clearAllSavedLoginsButton.setSecondaryText(getString(string.autofillDevSettingsClearLoginsSubtitle, count))
}
}
}

binding.viewSavedLoginsButton.setClickListener {
startActivity(AutofillManagementActivity.intentDefaultList(this))
}
}

private fun confirmLoginDeletion(count: Int) {
TextAlertDialogBuilder(this@AutofillInternalSettingsActivity)
.setTitle(string.autofillDevSettingsClearLogins)
.setMessage(getString(string.autofillDevSettingsClearLoginsConfirmationMessage, count))
.setDestructiveButtons(true)
.setPositiveButton(string.autofillDevSettingsClearLoginsDeleteButton)
.setNegativeButton(string.autofillDevSettingsClearLoginsCancelButton)
.addEventListener(
object : TextAlertDialogBuilder.EventListener() {
override fun onPositiveButtonClicked() {
onUserChoseToClearSavedLogins()
}
},
)
.show()
}

private fun onUserChoseToClearSavedLogins() {
lifecycleScope.launch(dispatchers.io()) {
val idsToDelete = mutableListOf<Long>()
autofillStore.getAllCredentials().first().forEach { login ->
login.id?.let {
idsToDelete.add(it)
}
}

logcat { "There are ${idsToDelete.size} logins to delete" }

idsToDelete.forEach {
autofillStore.deleteCredentials(it)
}

withContext(dispatchers.main()) {
Toast.makeText(this@AutofillInternalSettingsActivity, "Deleted %d logins".format(idsToDelete.size), Toast.LENGTH_SHORT).show()
}
}
}

private fun configureEmailProtectionUiEventHandlers() {
binding.emailProtectionClearNeverAskAgainButton.setClickListener {
lifecycleScope.launch(dispatchers.io()) {
inContextDataStore.resetNeverAskAgainChoice()
getString(R.string.autofillDevSettingsEmailProtectionNeverAskAgainChoiceCleared).showToast()
getString(string.autofillDevSettingsEmailProtectionNeverAskAgainChoiceCleared).showToast()
}
}

binding.emailProtectionSignOutButton.setClickListener {
lifecycleScope.launch(dispatchers.io()) {
emailManager.signOut()
getString(R.string.autofillDevSettingsEmailProtectionSignedOut).showToast()
getString(string.autofillDevSettingsEmailProtectionSignedOut).showToast()
}
}

@Suppress("DEPRECATION")
binding.configureDaysFromInstallValue.setClickListener {
RadioListAlertDialogBuilder(this)
.setTitle(R.string.autofillDevSettingsOverrideMaxInstallDialogTitle)
.setTitle(string.autofillDevSettingsOverrideMaxInstallDialogTitle)
.setOptions(daysInstalledOverrideOptions().map { it.first })
.setPositiveButton(R.string.autofillDevSettingsOverrideMaxInstallDialogOkButtonText)
.setNegativeButton(R.string.autofillDevSettingsOverrideMaxInstallDialogCancelButtonText)
.setPositiveButton(string.autofillDevSettingsOverrideMaxInstallDialogOkButtonText)
.setNegativeButton(string.autofillDevSettingsOverrideMaxInstallDialogCancelButtonText)
.addEventListener(
object : RadioListAlertDialogBuilder.EventListener() {
object : EventListener() {
override fun onPositiveButtonClicked(selectedItem: Int) {
val daysInstalledOverrideChosen = daysInstalledOverrideOptions()[selectedItem - 1].second

Expand Down Expand Up @@ -156,6 +261,20 @@ class AutofillInternalSettingsActivity : DuckDuckGoActivity() {
)
}

private suspend fun LoginCredentials.save() {
withContext(dispatchers.io()) {
autofillStore.saveCredentials(this@save.domain ?: "", this@save)
}
}

private fun sampleCredentials(
domain: String = "fill.dev",
username: String,
password: String = "password-123",
): LoginCredentials {
return LoginCredentials(username = username, password = password, domain = domain)
}

companion object {
fun intent(context: Context): Intent {
return Intent(context, AutofillInternalSettingsActivity::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,51 @@
android:id="@+id/includeToolbar"
layout="@layout/include_default_toolbar" />

<com.duckduckgo.mobile.android.ui.view.listitem.SectionHeaderListItem
android:id="@+id/autofillLoginsSectionTitle"
android:layout_width="match_parent"
android:layout_marginTop="20dp"
android:layout_height="wrap_content"
app:primaryText="@string/autofillDevSettingsLoginsSectionTitle" />

<com.duckduckgo.mobile.android.ui.view.listitem.TwoLineListItem
android:id="@+id/addSampleLoginsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/autofillDevSettingsAddSampleLogins"
app:secondaryText="@string/autofillDevSettingsAddSampleLoginsSubtitle" />

<com.duckduckgo.mobile.android.ui.view.listitem.TwoLineListItem
android:id="@+id/addSampleLoginsContainingDuplicatesSameDomainButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/autofillDevSettingsAddSampleLoginsSameSubdomain"
app:secondaryText="@string/autofillDevSettingsAddSampleLoginsSameSubdomainSubtitle" />

<com.duckduckgo.mobile.android.ui.view.listitem.TwoLineListItem
android:id="@+id/addSampleLoginsContainingDuplicatesAcrossSubdomainsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/autofillDevSettingsAddSampleLoginsMultipleSubdomains"
app:secondaryText="@string/autofillDevSettingsAddSampleLoginsMultipleSubdomainsSubtitle" />

<com.duckduckgo.mobile.android.ui.view.listitem.TwoLineListItem
android:id="@+id/clearAllSavedLoginsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/autofillDevSettingsClearLogins"
tools:secondaryText="@string/autofillDevSettingsClearLoginsSubtitle" />

<com.duckduckgo.mobile.android.ui.view.listitem.OneLineListItem
android:id="@+id/viewSavedLoginsButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primaryText="@string/autofillDevSettingsViewSavedLogins" />

<com.duckduckgo.mobile.android.ui.view.divider.HorizontalDivider
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<com.duckduckgo.mobile.android.ui.view.listitem.SectionHeaderListItem
android:id="@+id/emailProtectionInContextSectionTitle"
android:layout_width="match_parent"
Expand Down
15 changes: 15 additions & 0 deletions autofill/autofill-internal/src/main/res/values/donottranslate.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,19 @@

<string name="autofillDevSettingsDaysSinceInstall" instruction="%1$d is number of days since app since app was app was installed">Days since app was installed: %1$d</string>
<string name="autofillDevSettingsOverrideTapToChange">Tap to change this rule</string>

<string name="autofillDevSettingsLoginsSectionTitle">Logins</string>
<string name="autofillDevSettingsClearLogins">Delete all saved logins</string>
<string name="autofillDevSettingsClearLoginsConfirmationMessage" instruction="%1$d is number of saved logins">Are you sure you want to delete %1$d logins?</string>
<string name="autofillDevSettingsClearLoginsDeleteButton">Delete</string>
<string name="autofillDevSettingsClearLoginsCancelButton">Cancel</string>
<string name="autofillDevSettingsClearLoginsSubtitle" instruction="%1$d is number of saved logins">Number of saved logins: %1$d</string>
<string name="autofillDevSettingsViewSavedLogins">View saved logins</string>

<string name="autofillDevSettingsAddSampleLogins">Add a few sample logins</string>
<string name="autofillDevSettingsAddSampleLoginsSubtitle">Adds a few logins for https://fill.dev</string>
<string name="autofillDevSettingsAddSampleLoginsSameSubdomain">Add duplicate logins (same domain)</string>
<string name="autofillDevSettingsAddSampleLoginsSameSubdomainSubtitle">Adds multiple logins for https://fill.dev with same username and password</string>
<string name="autofillDevSettingsAddSampleLoginsMultipleSubdomains">Add duplicate logins (across different subdomains)</string>
<string name="autofillDevSettingsAddSampleLoginsMultipleSubdomainsSubtitle">Adds multiple logins for https://fill.dev with same username and password but across different subdomains</string>
</resources>

0 comments on commit 41fd748

Please sign in to comment.