diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java index 8787c419ea..8e1261899d 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; @@ -26,15 +27,20 @@ import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultRepository; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.UUID; public class ImportEntriesActivity extends AegisActivity { + private View _view; private Menu _menu; private ImportEntriesAdapter _adapter; private FabScrollHelper _fabScrollHelper; @@ -48,6 +54,8 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_import_entries); setSupportActionBar(findViewById(R.id.toolbar)); + _view = findViewById(R.id.importEntriesRootView); + ActionBar bar = getSupportActionBar(); bar.setHomeAsUpIndicator(R.drawable.ic_close); bar.setDisplayHomeAsUpEnabled(true); @@ -180,6 +188,7 @@ public void onCanceled() { } private void importDatabase(DatabaseImporter.State state) { + List importEntries = new ArrayList<>(); DatabaseImporter.Result result; try { result = state.convert(); @@ -191,7 +200,9 @@ private void importDatabase(DatabaseImporter.State state) { UUIDMap entries = result.getEntries(); for (VaultEntry entry : entries.getValues()) { - _adapter.addEntry(new ImportEntry(entry)); + ImportEntry importEntry = new ImportEntry(entry); + _adapter.addEntry(importEntry); + importEntries.add(importEntry); } List errors = result.getErrors(); @@ -199,6 +210,8 @@ private void importDatabase(DatabaseImporter.State state) { String message = getResources().getQuantityString(R.plurals.import_error_dialog, errors.size(), errors.size()); Dialogs.showMultiErrorDialog(this, R.string.import_error_title, message, errors, null); } + + findDuplicates(importEntries); } private void showWipeEntriesDialog() { @@ -236,6 +249,33 @@ private void saveAndFinish(boolean wipeEntries) { } } + private void findDuplicates(List importEntries) { + List duplicateEntries = new ArrayList<>(); + for (ImportEntry importEntry: importEntries) { + boolean exists = _vaultManager.getVault().getEntries().stream().anyMatch(item -> + item.getIssuer().equals(importEntry.getEntry().getIssuer()) && + Arrays.equals(item.getInfo().getSecret(), importEntry.getEntry().getInfo().getSecret())); + + if (exists) { + duplicateEntries.add(importEntry.getEntry().getUUID()); + } + } + + if (duplicateEntries.size() == 0) { + return; + } + + _adapter.setCheckboxStates(duplicateEntries, false); + Snackbar snackbar = Snackbar.make(_view, getResources().getQuantityString(R.plurals.import_duplicate_toast, duplicateEntries.size(), duplicateEntries.size()), Snackbar.LENGTH_INDEFINITE); + snackbar.setAction(R.string.undo, new View.OnClickListener() { + @Override + public void onClick(View v) { + _adapter.setCheckboxStates(duplicateEntries, true); + } + }); + snackbar.show(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { _menu = menu; diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/ImportEntriesAdapter.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/ImportEntriesAdapter.java index 024472b116..5f18160f0c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/ImportEntriesAdapter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/ImportEntriesAdapter.java @@ -12,6 +12,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.UUID; public class ImportEntriesAdapter extends RecyclerView.Adapter { private List _entries; @@ -67,6 +68,14 @@ public List getCheckedEntries() { return entries; } + public void setCheckboxStates(List uuids, boolean state) { + for (ImportEntry entry : _entries) { + if(uuids.contains(entry.getEntry().getUUID())) { + entry.setIsChecked(state); + } + } + } + public void toggleCheckboxes() { int checkedEntries = getCheckedEntries().size(); if (checkedEntries == 0 || checkedEntries != _entries.size()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65323954ba..65204e976c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -159,6 +159,7 @@ Share Yes No + Undo Unlock Advanced Counter @@ -469,6 +470,10 @@ Delete vault when a panic trigger is received from Ripple Import vault + + Unchecked %d potential duplicate. Please review the list of entries. + Unchecked %d potential duplicates. Please review the list of entries. + Supply a 2FAS Authenticator backup file. Supply an Aegis export/backup file. Supply an Authenticator Plus export file obtained through Settings -> Backup & Restore -> Export as Text and HTML.