From ae71febf100420a7c5bd79a446db37082c815224 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 24 Jan 2021 20:19:29 +0100 Subject: [PATCH] Move import logic to separate activity to fix a couple of issues Fixes #456. Fixes #670. --- app/src/main/AndroidManifest.xml | 4 +- .../aegis/importers/AegisImporter.java | 24 ++ .../aegis/importers/AndOtpImporter.java | 2 +- .../importers/AuthenticatorPlusImporter.java | 2 +- .../aegis/importers/AuthyImporter.java | 2 +- .../aegis/importers/DatabaseImporter.java | 7 +- .../importers/TotpAuthenticatorImporter.java | 2 +- .../com/beemdevelopment/aegis/ui/Dialogs.java | 25 +- .../aegis/ui/ImportEntriesActivity.java | 263 ++++++++++++++++++ .../aegis/ui/SelectEntriesActivity.java | 154 ---------- .../ImportExportPreferencesFragment.java | 195 ++----------- .../ui/fragments/PreferencesFragment.java | 5 +- .../aegis/ui/models/ImportEntry.java | 21 +- .../aegis/ui/views/ImportEntriesAdapter.java | 8 +- .../aegis/ui/views/ImportEntryHolder.java | 18 +- ...ntries.xml => activity_import_entries.xml} | 2 +- app/src/main/res/layout/card_import_entry.xml | 101 +++---- ...ct_entries.xml => menu_import_entries.xml} | 0 app/src/main/res/values-cs-rCZ/strings.xml | 2 +- app/src/main/res/values-de-rDE/strings.xml | 2 +- app/src/main/res/values-el-rGR/strings.xml | 2 +- app/src/main/res/values-es-rES/strings.xml | 2 +- app/src/main/res/values-eu-rES/strings.xml | 2 +- app/src/main/res/values-fa-rIR/strings.xml | 2 +- app/src/main/res/values-fi-rFI/strings.xml | 2 +- app/src/main/res/values-fr-rFR/strings.xml | 2 +- app/src/main/res/values-hi-rIN/strings.xml | 2 +- app/src/main/res/values-in-rID/strings.xml | 2 +- app/src/main/res/values-it-rIT/strings.xml | 2 +- app/src/main/res/values-ja-rJP/strings.xml | 2 +- app/src/main/res/values-nl-rNL/strings.xml | 2 +- app/src/main/res/values-pl-rPL/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt-rPT/strings.xml | 2 +- app/src/main/res/values-ro-rRO/strings.xml | 2 +- app/src/main/res/values-ru-rRU/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- 39 files changed, 415 insertions(+), 462 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java delete mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/SelectEntriesActivity.java rename app/src/main/res/layout/{activity_select_entries.xml => activity_import_entries.xml} (96%) rename app/src/main/res/menu/{menu_select_entries.xml => menu_import_entries.xml} (100%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eb1ea9b4..1d12f571 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,8 +30,8 @@ android:name=".ui.AboutActivity" android:label="@string/title_activity_about" /> + android:name=".ui.ImportEntriesActivity" + android:label="@string/title_activity_import_entries" /> diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java index e767b0be..706cb1c2 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java @@ -1,9 +1,14 @@ package com.beemdevelopment.aegis.importers; import android.content.Context; +import android.content.DialogInterface; + +import androidx.lifecycle.Lifecycle; import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.helpers.ContextHelper; import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.ui.Dialogs; import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.vault.VaultEntry; @@ -79,7 +84,26 @@ public class AegisImporter extends DatabaseImporter { @Override public void decrypt(Context context, DecryptListener listener) { + Dialogs.showPasswordInputDialog(context, (Dialogs.TextInputListener) password -> { + List slots = getSlots().findAll(PasswordSlot.class); + PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password); + PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(context, result -> { + try { + if (result == null) { + throw new DatabaseImporterException("Password incorrect"); + } + VaultFileCredentials creds = new VaultFileCredentials(result.getKey(), getSlots()); + State state = decrypt(creds); + listener.onStateDecrypted(state); + } catch (DatabaseImporterException e) { + listener.onError(e); + } + }); + + Lifecycle lifecycle = ContextHelper.getLifecycle(context); + task.execute(lifecycle, params); + }, (DialogInterface.OnCancelListener) dialog -> listener.onCanceled()); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java index 9049f28a..17a43c9b 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java @@ -190,7 +190,7 @@ public class AndOtpImporter extends DatabaseImporter { } catch (DatabaseImporterException e) { listener.onError(e); } - }); + }, dialog1 -> listener.onCanceled()); }) .create()); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorPlusImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorPlusImporter.java index 9f49cc10..9b4df7cd 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorPlusImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthenticatorPlusImporter.java @@ -71,7 +71,7 @@ public class AuthenticatorPlusImporter extends DatabaseImporter { } catch (DatabaseImporterException e) { listener.onError(e); } - }); + }, dialog1 -> listener.onCanceled()); } } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java index ec99294a..3e80e30d 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AuthyImporter.java @@ -204,7 +204,7 @@ public class AuthyImporter extends DatabaseImporter { } catch (DatabaseImporterException e) { listener.onError(e); } - }); + }, dialog1 -> listener.onCanceled()); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java index 9ea576c8..f3f58864 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -167,8 +167,9 @@ public abstract class DatabaseImporter { } } - public interface DecryptListener { - void onStateDecrypted(State state); - void onError(Exception e); + public static abstract class DecryptListener { + protected abstract void onStateDecrypted(State state); + protected abstract void onError(Exception e); + protected abstract void onCanceled(); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/TotpAuthenticatorImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/TotpAuthenticatorImporter.java index b042541e..8b29f372 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/TotpAuthenticatorImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/TotpAuthenticatorImporter.java @@ -159,7 +159,7 @@ public class TotpAuthenticatorImporter extends DatabaseImporter { .setPositiveButton(R.string.yes, (dialog, which) -> { Dialogs.showPasswordInputDialog(context, password -> { decrypt(password, listener); - }); + }, dialog1 -> listener.onCanceled()); }) .setNegativeButton(R.string.no, (dialog, which) -> { decrypt(PASSWORD, listener); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/Dialogs.java b/app/src/main/java/com/beemdevelopment/aegis/ui/Dialogs.java index ed7eb818..61891f74 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/Dialogs.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/Dialogs.java @@ -188,7 +188,7 @@ public class Dialogs { showSecureDialog(dialog); } - private static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int messageId, @StringRes int hintId, TextInputListener listener, DialogInterface.OnDismissListener dismissListener, boolean isSecret) { + private static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int messageId, @StringRes int hintId, TextInputListener listener, DialogInterface.OnCancelListener cancelListener, boolean isSecret) { View view = LayoutInflater.from(context).inflate(R.layout.dialog_text_input, null); TextInputEditText input = view.findViewById(R.id.text_input); TextInputLayout inputLayout = view.findViewById(R.id.text_input_layout); @@ -205,8 +205,8 @@ public class Dialogs { listener.onTextInputResult(text); }); - if (dismissListener != null) { - builder.setOnDismissListener(dismissListener); + if (cancelListener != null) { + builder.setOnCancelListener(cancelListener); } if (messageId != 0) { @@ -214,6 +214,7 @@ public class Dialogs { } AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(true); dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); showSecureDialog(dialog); } @@ -230,12 +231,20 @@ public class Dialogs { showTextInputDialog(context, R.string.set_password, R.string.password, listener, true); } + public static void showPasswordInputDialog(Context context, TextInputListener listener, DialogInterface.OnCancelListener cancelListener) { + showTextInputDialog(context, R.string.set_password, 0, R.string.password, listener, cancelListener, true); + } + public static void showPasswordInputDialog(Context context, @StringRes int messageId, TextInputListener listener) { showTextInputDialog(context, R.string.set_password, messageId, R.string.password, listener, null, true); } - public static void showPasswordInputDialog(Context context, @StringRes int setPasswordMessageId, @StringRes int messageId, TextInputListener listener, DialogInterface.OnDismissListener dismissListener) { - showTextInputDialog(context, setPasswordMessageId, messageId, R.string.password, listener, dismissListener, true); + public static void showPasswordInputDialog(Context context, @StringRes int messageId, TextInputListener listener, DialogInterface.OnCancelListener cancelListener) { + showTextInputDialog(context, R.string.set_password, messageId, R.string.password, listener, cancelListener, true); + } + + public static void showPasswordInputDialog(Context context, @StringRes int titleId, @StringRes int messageId, TextInputListener listener, DialogInterface.OnCancelListener cancelListener) { + showTextInputDialog(context, titleId, messageId, R.string.password, listener, cancelListener, true); } public static void showCheckboxDialog(Context context, @StringRes int titleId, @StringRes int messageId, @StringRes int checkboxMessageId, CheckboxInputListener listener) { @@ -334,6 +343,7 @@ public class Dialogs { AlertDialog dialog = new AlertDialog.Builder(context) .setTitle(R.string.error_occurred) .setView(view) + .setCancelable(false) .setPositiveButton(android.R.string.ok, (dialog1, which) -> { if (listener != null) { listener.onClick(dialog1, which); @@ -342,11 +352,6 @@ public class Dialogs { .setNeutralButton(R.string.details, (dialog1, which) -> { textDetails.setVisibility(View.VISIBLE); }) - .setOnDismissListener(dialog12 -> { - if (listener != null) { - listener.onClick(dialog12, -1); - } - }) .create(); dialog.setOnShowListener(d -> { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java new file mode 100644 index 00000000..10986050 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java @@ -0,0 +1,263 @@ +package com.beemdevelopment.aegis.ui; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.helpers.FabScrollHelper; +import com.beemdevelopment.aegis.importers.DatabaseImporter; +import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException; +import com.beemdevelopment.aegis.importers.DatabaseImporterException; +import com.beemdevelopment.aegis.ui.models.ImportEntry; +import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter; +import com.beemdevelopment.aegis.util.UUIDMap; +import com.beemdevelopment.aegis.vault.VaultEntry; +import com.beemdevelopment.aegis.vault.VaultManager; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.topjohnwu.superuser.Shell; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class ImportEntriesActivity extends AegisActivity { + private ImportEntriesAdapter _adapter; + private FabScrollHelper _fabScrollHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_import_entries); + setSupportActionBar(findViewById(R.id.toolbar)); + + ActionBar bar = getSupportActionBar(); + bar.setHomeAsUpIndicator(R.drawable.ic_close); + bar.setDisplayHomeAsUpEnabled(true); + + _adapter = new ImportEntriesAdapter(); + RecyclerView entriesView = findViewById(R.id.list_entries); + entriesView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + _fabScrollHelper.onScroll(dx, dy); + } + }); + + LinearLayoutManager layoutManager = new LinearLayoutManager(this); + entriesView.setLayoutManager(layoutManager); + entriesView.setAdapter(_adapter); + entriesView.setNestedScrollingEnabled(false); + + FloatingActionButton fab = findViewById(R.id.fab); + fab.setOnClickListener(v -> { + if (getApp().getVaultManager().getEntries().size() > 0) { + showWipeEntriesDialog(); + } else { + saveAndFinish(false); + } + }); + _fabScrollHelper = new FabScrollHelper(fab); + + Class importerType = (Class) getIntent().getSerializableExtra("importerType"); + startImport(importerType, getIntent().getStringExtra("fileUri")); + } + + private void startImport(@NonNull Class importerType, @Nullable String fileUri) { + if (fileUri == null) { + startImportApp(importerType); + } else { + startImportFile(importerType, Uri.parse(fileUri)); + } + } + + private void startImportFile(@NonNull Class importerType, @NonNull Uri fileUri) { + try (InputStream stream = getContentResolver().openInputStream(fileUri)) { + DatabaseImporter importer = DatabaseImporter.create(this, importerType); + DatabaseImporter.State state = importer.read(stream); + processImporterState(state); + } catch (FileNotFoundException e) { + Toast.makeText(this, R.string.file_not_found, Toast.LENGTH_SHORT).show(); + } catch (DatabaseImporterException | IOException e) { + e.printStackTrace(); + Dialogs.showErrorDialog(this, R.string.reading_file_error, e, (dialog, which) -> finish()); + } + } + + private void startImportApp(@NonNull Class importerType) { + DatabaseImporter importer = DatabaseImporter.create(this, importerType); + + // obtain the global root shell and close it immediately after we're done + // TODO: find a way to use SuFileInputStream with Shell.newInstance() + try (Shell shell = Shell.getShell()) { + if (!shell.isRoot()) { + Toast.makeText(this, R.string.root_error, Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + DatabaseImporter.State state = importer.readFromApp(); + processImporterState(state); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + Toast.makeText(this, R.string.app_lookup_error, Toast.LENGTH_SHORT).show(); + finish(); + } catch (IOException | DatabaseImporterException e) { + e.printStackTrace(); + Dialogs.showErrorDialog(this, R.string.reading_file_error, e, (dialog, which) -> finish()); + } + } + + private void processImporterState(DatabaseImporter.State state) { + try { + if (state.isEncrypted()) { + state.decrypt(this, new DatabaseImporter.DecryptListener() { + @Override + public void onStateDecrypted(DatabaseImporter.State state) { + importDatabase(state); + } + + @Override + public void onError(Exception e) { + e.printStackTrace(); + Dialogs.showErrorDialog(ImportEntriesActivity.this, R.string.decryption_error, e, (dialog, which) -> finish()); + } + + @Override + public void onCanceled() { + finish(); + } + }); + } else { + importDatabase(state); + } + } catch (DatabaseImporterException e) { + e.printStackTrace(); + Dialogs.showErrorDialog(this, R.string.parsing_file_error, e, (dialog, which) -> finish()); + } + } + + private void importDatabase(DatabaseImporter.State state) { + DatabaseImporter.Result result; + try { + result = state.convert(); + } catch (DatabaseImporterException e) { + e.printStackTrace(); + Dialogs.showErrorDialog(this, R.string.parsing_file_error, e, (dialog, which) -> finish()); + return; + } + + UUIDMap entries = result.getEntries(); + for (VaultEntry entry : entries.getValues()) { + _adapter.addEntry(new ImportEntry(entry)); + } + + List errors = result.getErrors(); + if (errors.size() > 0) { + showErrorDialog(errors); + } + } + + private void showErrorDialog(List errors) { + Dialogs.showSecureDialog(new AlertDialog.Builder(this) + .setTitle(R.string.import_error_title) + .setMessage(getResources().getQuantityString(R.plurals.import_error_dialog, errors.size(), errors.size())) + .setPositiveButton(android.R.string.ok, null) + .setNeutralButton(getString(R.string.details), (dialog, which) -> showDetailedErrorDialog(errors)) + .create()); + } + + private void showDetailedErrorDialog(List errors) { + List messages = new ArrayList<>(); + for (DatabaseImporterEntryException e : errors) { + messages.add(e.getMessage()); + } + + String message = TextUtils.join("\n\n", messages); + Dialogs.showSecureDialog(new AlertDialog.Builder(this) + .setTitle(R.string.import_error_title) + .setMessage(message) + .setPositiveButton(android.R.string.ok, null) + .setNeutralButton(android.R.string.copy, (dialog2, which2) -> { + ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("text/plain", message); + clipboard.setPrimaryClip(clip); + Toast.makeText(this, R.string.errors_copied, Toast.LENGTH_SHORT).show(); + }) + .create()); + } + + private void showWipeEntriesDialog() { + Dialogs.showCheckboxDialog(this, R.string.dialog_wipe_entries_title, + R.string.dialog_wipe_entries_message, + R.string.dialog_wipe_entries_checkbox, + this::saveAndFinish + ); + } + + private void saveAndFinish(boolean wipeEntries) { + VaultManager vault = getApp().getVaultManager(); + if (wipeEntries) { + vault.wipeEntries(); + } + + List selectedEntries = _adapter.getCheckedEntries(); + for (ImportEntry selectedEntry : selectedEntries) { + VaultEntry entry = selectedEntry.getEntry(); + + // temporary: randomize the UUID of duplicate entries and add them anyway + if (vault.isEntryDuplicate(entry)) { + entry.resetUUID(); + } + + vault.addEntry(entry); + } + + if (saveVault(true)) { + String toastMessage = getResources().getQuantityString(R.plurals.imported_entries_count, selectedEntries.size(), selectedEntries.size()); + Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show(); + + setResult(RESULT_OK, null); + finish(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_import_entries, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + break; + case R.id.toggle_checkboxes: + _adapter.toggleCheckboxes(); + break; + default: + return super.onOptionsItemSelected(item); + } + + return true; + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/SelectEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/SelectEntriesActivity.java deleted file mode 100644 index 69ea33bc..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/SelectEntriesActivity.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.beemdevelopment.aegis.ui; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.helpers.FabScrollHelper; -import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException; -import com.beemdevelopment.aegis.ui.models.ImportEntry; -import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter; -import com.google.android.material.floatingactionbutton.FloatingActionButton; - -import java.util.ArrayList; -import java.util.List; - -public class SelectEntriesActivity extends AegisActivity { - private ImportEntriesAdapter _adapter; - private FabScrollHelper _fabScrollHelper; - private boolean _vaultContainsEntries; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_select_entries); - setSupportActionBar(findViewById(R.id.toolbar)); - - ActionBar bar = getSupportActionBar(); - bar.setHomeAsUpIndicator(R.drawable.ic_close); - bar.setDisplayHomeAsUpEnabled(true); - - _adapter = new ImportEntriesAdapter(); - RecyclerView entriesView = findViewById(R.id.list_entries); - entriesView.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { - super.onScrolled(recyclerView, dx, dy); - onScroll(dx, dy); - } - }); - - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - entriesView.setLayoutManager(layoutManager); - entriesView.setAdapter(_adapter); - entriesView.setNestedScrollingEnabled(false); - - Intent intent = getIntent(); - List entries = (ArrayList) intent.getSerializableExtra("entries"); - List errors = (ArrayList) intent.getSerializableExtra("errors"); - _vaultContainsEntries = intent.getBooleanExtra("vaultContainsEntries", false); - - for (ImportEntry entry : entries) { - _adapter.addEntry(entry); - } - - if (errors.size() > 0) { - showErrorDialog(errors); - } - - FloatingActionButton fab = findViewById(R.id.fab); - fab.setOnClickListener(v -> { - if (_vaultContainsEntries) { - showWipeEntriesDialog(); - } else { - returnSelectedEntries(false); - } - }); - _fabScrollHelper = new FabScrollHelper(fab); - } - - private void showErrorDialog(List errors) { - Dialogs.showSecureDialog(new AlertDialog.Builder(this) - .setTitle(R.string.import_error_title) - .setMessage(getResources().getQuantityString(R.plurals.import_error_dialog, errors.size(), errors.size())) - .setPositiveButton(android.R.string.ok, null) - .setNeutralButton(getString(R.string.details), (dialog, which) -> showDetailedErrorDialog(errors)) - .create()); - } - - private void showDetailedErrorDialog(List errors) { - List messages = new ArrayList<>(); - for (DatabaseImporterEntryException e : errors) { - messages.add(e.getMessage()); - } - - String message = TextUtils.join("\n\n", messages); - Dialogs.showSecureDialog(new AlertDialog.Builder(this) - .setTitle(R.string.import_error_title) - .setMessage(message) - .setPositiveButton(android.R.string.ok, null) - .setNeutralButton(android.R.string.copy, (dialog2, which2) -> { - ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("text/plain", message); - clipboard.setPrimaryClip(clip); - Toast.makeText(this, R.string.errors_copied, Toast.LENGTH_SHORT).show(); - }) - .create()); - } - - private void showWipeEntriesDialog() { - Dialogs.showCheckboxDialog(this, R.string.dialog_wipe_entries_title, - R.string.dialog_wipe_entries_message, - R.string.dialog_wipe_entries_checkbox, - this::returnSelectedEntries - ); - } - - private void returnSelectedEntries(boolean wipeEntries) { - Intent intent = new Intent(); - List entries = _adapter.getCheckedEntries(); - intent.putExtra("entries", (ArrayList) entries); - intent.putExtra("wipeEntries", wipeEntries); - setResult(RESULT_OK, intent); - finish(); - } - - public void onScroll(int dx, int dy) { - _fabScrollHelper.onScroll(dx, dy); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_select_entries, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - break; - case R.id.toggle_checkboxes: - _adapter.toggleCheckboxes(); - break; - default: - return super.onOptionsItemSelected(item); - } - - return true; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/ImportExportPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/ImportExportPreferencesFragment.java index 9330b3b8..ce916cf4 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/ImportExportPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/ImportExportPreferencesFragment.java @@ -2,7 +2,6 @@ package com.beemdevelopment.aegis.ui.fragments; import android.app.Activity; import android.content.Intent; -import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.view.LayoutInflater; @@ -14,6 +13,7 @@ import android.widget.TextView; import android.widget.Toast; import androidx.annotation.ArrayRes; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.FileProvider; import androidx.preference.Preference; @@ -21,47 +21,37 @@ import androidx.preference.Preference; import com.beemdevelopment.aegis.BuildConfig; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.helpers.DropdownHelper; -import com.beemdevelopment.aegis.importers.AegisImporter; import com.beemdevelopment.aegis.importers.DatabaseImporter; -import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException; -import com.beemdevelopment.aegis.importers.DatabaseImporterException; -import com.beemdevelopment.aegis.ui.AuthActivity; import com.beemdevelopment.aegis.ui.Dialogs; -import com.beemdevelopment.aegis.ui.SelectEntriesActivity; -import com.beemdevelopment.aegis.ui.models.ImportEntry; +import com.beemdevelopment.aegis.ui.ImportEntriesActivity; import com.beemdevelopment.aegis.ui.tasks.ExportTask; -import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.vault.VaultBackupManager; -import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultFileCredentials; import com.beemdevelopment.aegis.vault.VaultManager; import com.beemdevelopment.aegis.vault.VaultManagerException; import com.beemdevelopment.aegis.vault.slots.Slot; import com.beemdevelopment.aegis.vault.slots.SlotException; -import com.topjohnwu.superuser.Shell; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; import javax.crypto.Cipher; public class ImportExportPreferencesFragment extends PreferencesFragment { - // keep a reference to the type of database converter the user selected + // keep a reference to the type of database converter that was selected private Class _importerType; - private AegisImporter.State _importerState; - private UUIDMap _importerEntries; @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); addPreferencesFromResource(R.xml.preferences_import_export); + if (savedInstanceState != null) { + _importerType = (Class) savedInstanceState.getSerializable("importerType"); + } + Preference importPreference = findPreference("pref_import"); importPreference.setOnPreferenceClickListener(preference -> { Dialogs.showImportersDialog(getContext(), false, definition -> { @@ -69,7 +59,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); - startActivityForResult(intent, CODE_IMPORT); + startActivityForResult(intent, CODE_IMPORT_SELECT); }); return true; }); @@ -77,8 +67,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { Preference importAppPreference = findPreference("pref_import_app"); importAppPreference.setOnPreferenceClickListener(preference -> { Dialogs.showImportersDialog(getContext(), true, definition -> { - DatabaseImporter importer = DatabaseImporter.create(getContext(), definition.getType()); - importApp(importer); + startImportEntriesActivity(definition.getType(), null); }); return true; }); @@ -90,18 +79,20 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { }); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putSerializable("importerType", _importerType); + } + @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (data != null) { + if (requestCode == CODE_IMPORT) { + getResult().putExtra("needsRecreate", true); + } else if (data != null) { switch (requestCode) { - case CODE_IMPORT: - onImportResult(resultCode, data); - break; - case CODE_IMPORT_DECRYPT: - onImportDecryptResult(resultCode, data); - break; - case CODE_SELECT_ENTRIES: - onSelectEntriesResult(resultCode, data); + case CODE_IMPORT_SELECT: + onImportSelectResult(resultCode, data); break; case CODE_EXPORT: // intentional fallthrough @@ -114,118 +105,20 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { } } - private void importApp(DatabaseImporter importer) { - // obtain the global root shell and close it immediately after we're done - // TODO: find a way to use SuFileInputStream with Shell.newInstance() - try (Shell shell = Shell.getShell()) { - if (!shell.isRoot()) { - Toast.makeText(getActivity(), R.string.root_error, Toast.LENGTH_SHORT).show(); - return; - } - - DatabaseImporter.State state = importer.readFromApp(); - processImporterState(state); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - Toast.makeText(getActivity(), R.string.app_lookup_error, Toast.LENGTH_SHORT).show(); - } catch (IOException | DatabaseImporterException e) { - e.printStackTrace(); - Toast.makeText(getActivity(), R.string.reading_file_error, Toast.LENGTH_SHORT).show(); - } - } - - private void processImporterState(DatabaseImporter.State state) { - try { - if (state.isEncrypted()) { - // temporary special case for encrypted Aegis vaults - if (state instanceof AegisImporter.EncryptedState) { - _importerState = state; - - Intent intent = new Intent(getActivity(), AuthActivity.class); - intent.putExtra("slots", ((AegisImporter.EncryptedState) state).getSlots()); - startActivityForResult(intent, CODE_IMPORT_DECRYPT); - } else { - state.decrypt(getActivity(), new DatabaseImporter.DecryptListener() { - @Override - public void onStateDecrypted(DatabaseImporter.State state) { - importDatabase(state); - } - - @Override - public void onError(Exception e) { - e.printStackTrace(); - Dialogs.showErrorDialog(getContext(), R.string.decryption_error, e); - } - }); - } - } else { - importDatabase(state); - } - } catch (DatabaseImporterException e) { - e.printStackTrace(); - Dialogs.showErrorDialog(getContext(), R.string.parsing_file_error, e); - } - } - - private void onImportDecryptResult(int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - _importerState = null; - return; - } - - VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds"); - DatabaseImporter.State state; - try { - state = ((AegisImporter.EncryptedState) _importerState).decrypt(creds); - } catch (DatabaseImporterException e) { - e.printStackTrace(); - Dialogs.showErrorDialog(getContext(), R.string.decryption_error, e); - return; - } - - importDatabase(state); - _importerState = null; - } - - private void onImportResult(int resultCode, Intent data) { + private void onImportSelectResult(int resultCode, Intent data) { Uri uri = data.getData(); if (resultCode != Activity.RESULT_OK || uri == null) { return; } - try (InputStream stream = getContext().getContentResolver().openInputStream(uri)) { - DatabaseImporter importer = DatabaseImporter.create(getContext(), _importerType); - DatabaseImporter.State state = importer.read(stream); - processImporterState(state); - } catch (FileNotFoundException e) { - Toast.makeText(getActivity(), R.string.file_not_found, Toast.LENGTH_SHORT).show(); - } catch (DatabaseImporterException | IOException e) { - e.printStackTrace(); - Dialogs.showErrorDialog(getContext(), R.string.reading_file_error, e); - } + startImportEntriesActivity(_importerType, uri); } - private void importDatabase(DatabaseImporter.State state) { - DatabaseImporter.Result result; - try { - result = state.convert(); - } catch (DatabaseImporterException e) { - e.printStackTrace(); - Dialogs.showErrorDialog(getContext(), R.string.parsing_file_error, e); - return; - } - - _importerEntries = result.getEntries(); - List entries = new ArrayList<>(); - for (VaultEntry entry : _importerEntries) { - entries.add(new ImportEntry(entry)); - } - - Intent intent = new Intent(getActivity(), SelectEntriesActivity.class); - intent.putExtra("entries", (ArrayList) entries); - intent.putExtra("errors", (ArrayList) result.getErrors()); - intent.putExtra("vaultContainsEntries", getVault().getEntries().size() > 0); - startActivityForResult(intent, CODE_SELECT_ENTRIES); + private void startImportEntriesActivity(Class importerType, Uri fileUri) { + Intent intent = new Intent(getActivity(), ImportEntriesActivity.class); + intent.putExtra("importerType", importerType); + intent.putExtra("fileUri", fileUri == null ? null : fileUri.toString()); + startActivityForResult(intent, CODE_IMPORT); } private void startExport() { @@ -326,39 +219,6 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { Dialogs.showSecureDialog(dialog); } - private void onSelectEntriesResult(int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - - boolean wipeEntries = data.getBooleanExtra("wipeEntries", false); - if (wipeEntries) { - getVault().wipeEntries(); - } - - List selectedEntries = (ArrayList) data.getSerializableExtra("entries"); - for (ImportEntry selectedEntry : selectedEntries) { - VaultEntry savedEntry = _importerEntries.getByUUID(selectedEntry.getUUID()); - - // temporary: randomize the UUID of duplicate entries and add them anyway - if (getVault().isEntryDuplicate(savedEntry)) { - savedEntry.resetUUID(); - } - - getVault().addEntry(savedEntry); - } - - _importerEntries = null; - if (!saveVault()) { - return; - } - - String toastMessage = getResources().getQuantityString(R.plurals.imported_entries_count, selectedEntries.size(), selectedEntries.size()); - Toast.makeText(getContext(), toastMessage, Toast.LENGTH_SHORT).show(); - - getResult().putExtra("needsRecreate", true); - } - private static int getExportRequestCode(int spinnerPos, boolean encrypt) { if (spinnerPos == 0) { return encrypt ? CODE_EXPORT : CODE_EXPORT_PLAIN; @@ -433,7 +293,6 @@ public class ImportExportPreferencesFragment extends PreferencesFragment { return; } - startExportVault(requestCode, cb -> { File file; OutputStream outStream = null; diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/PreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/PreferencesFragment.java index c79e4aab..e8ce9777 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/PreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/PreferencesFragment.java @@ -16,11 +16,10 @@ import com.beemdevelopment.aegis.vault.VaultManagerException; public abstract class PreferencesFragment extends PreferenceFragmentCompat { // activity request codes - public static final int CODE_IMPORT = 0; - public static final int CODE_IMPORT_DECRYPT = 1; + public static final int CODE_IMPORT_SELECT = 0; public static final int CODE_SLOTS = 2; public static final int CODE_GROUPS = 3; - public static final int CODE_SELECT_ENTRIES = 4; + public static final int CODE_IMPORT = 4; public static final int CODE_EXPORT = 5; public static final int CODE_EXPORT_PLAIN = 6; public static final int CODE_EXPORT_GOOGLE_URI = 7; diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/models/ImportEntry.java b/app/src/main/java/com/beemdevelopment/aegis/ui/models/ImportEntry.java index 507039c0..be40d86a 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/models/ImportEntry.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/models/ImportEntry.java @@ -3,32 +3,19 @@ package com.beemdevelopment.aegis.ui.models; import com.beemdevelopment.aegis.vault.VaultEntry; import java.io.Serializable; -import java.util.UUID; public class ImportEntry implements Serializable { - private UUID _uuid; - private String _name; - private String _issuer; + private final VaultEntry _entry; private transient Listener _listener; private boolean _isChecked = true; public ImportEntry(VaultEntry entry) { - _uuid = entry.getUUID(); - _name = entry.getName(); - _issuer = entry.getIssuer(); + _entry = entry; } - public UUID getUUID() { - return _uuid; - } - - public String getName() { - return _name; - } - - public String getIssuer() { - return _issuer; + public VaultEntry getEntry() { + return _entry; } public void setOnCheckedChangedListener(Listener listener) { 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 cc64a799..024472b1 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 @@ -4,15 +4,15 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.ui.models.ImportEntry; import java.util.ArrayList; import java.util.List; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - public class ImportEntriesAdapter extends RecyclerView.Adapter { private List _entries; @@ -47,7 +47,7 @@ public class ImportEntriesAdapter extends RecyclerView.Adapter _entry.setIsChecked(!_entry.isChecked())); + view.setOnClickListener(v -> _data.setIsChecked(!_data.isChecked())); } - public void setData(ImportEntry entry) { - _entry = entry; + public void setData(ImportEntry data) { + _data = data; Context context = itemView.getContext(); - _issuer.setText(!entry.getIssuer().isEmpty() ? entry.getIssuer() : context.getString(R.string.unknown_issuer)); - _accountName.setText(!entry.getName().isEmpty() ? entry.getName() : context.getString(R.string.unknown_account_name)); - _checkbox.setChecked(entry.isChecked()); + _issuer.setText(!_data.getEntry().getIssuer().isEmpty() ? _data.getEntry().getIssuer() : context.getString(R.string.unknown_issuer)); + _accountName.setText(!_data.getEntry().getName().isEmpty() ? _data.getEntry().getName() : context.getString(R.string.unknown_account_name)); + _checkbox.setChecked(_data.isChecked()); } - public ImportEntry getEntry() { - return _entry; + public ImportEntry getData() { + return _data; } @Override diff --git a/app/src/main/res/layout/activity_select_entries.xml b/app/src/main/res/layout/activity_import_entries.xml similarity index 96% rename from app/src/main/res/layout/activity_select_entries.xml rename to app/src/main/res/layout/activity_import_entries.xml index e687dfe9..79f47a72 100644 --- a/app/src/main/res/layout/activity_select_entries.xml +++ b/app/src/main/res/layout/activity_import_entries.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent" android:background="?attr/background" android:id="@+id/importEntriesRootView" - tools:context="com.beemdevelopment.aegis.ui.SelectEntriesActivity"> + tools:context="com.beemdevelopment.aegis.ui.ImportEntriesActivity"> - + android:orientation="horizontal"> - - + - - - - - - - - - + android:fontFamily="sans-serif-light" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textColor="?attr/primaryText" + android:textSize="18sp" + android:textStyle="normal|bold" + android:gravity="center_vertical" + tools:text="@string/issuer" /> + - + diff --git a/app/src/main/res/menu/menu_select_entries.xml b/app/src/main/res/menu/menu_import_entries.xml similarity index 100% rename from app/src/main/res/menu/menu_select_entries.xml rename to app/src/main/res/menu/menu_import_entries.xml diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 833ee490..050fe3ec 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -296,7 +296,7 @@ Upravit položku Naskenovat QR kód Spravovat sloty klíčů - Vybrat položky + Vybrat položky Vyčistit položky Váš trezor už obsahuje záznamy. Chcete tyto záznamy smazat před importem tohoto souboru?\n\nPřístup ke všem stávajícím položkám v trezoru trvale ztratíte. Vymazat obsah trezoru diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index cecab5d2..e74ba250 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -292,7 +292,7 @@ Eintrag bearbeiten Scanne einen QR-Code Schlüsselplätze verwalten - Einträge auswählen + Einträge auswählen Einträge löschen Deine Datenbank enthält bereits Einträge. Möchtest du diese Einträge entfernen, bevor diese Datei importiert wird?\n\nWenn du dies tust, verlierst du dauerhaft den Zugriff auf die vorhandenen Einträge in der Datenbank. Datenbankinhalt löschen diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index 65aee027..8ca68070 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -292,7 +292,7 @@ Επεξεργασία καταχώρησης Σάρωση κωδικού QR Διαχείριση κλειδιών υποδοχής - Επιλέξτε καταχωρήσεις + Επιλέξτε καταχωρήσεις Διαγραφή καταχωρήσεων Η κρύπτη σας περιέχει ήδη καταχωρήσεις. Θέλετε να καταργήσετε αυτές τις καταχωρήσεις πριν από την εισαγωγή αυτού του αρχείου;\n\n Με αυτόν τον τρόπο, θα χάσετε μόνιμα την πρόσβαση στις υπάρχουσες καταχωρήσεις στην κρύπτη. Διαγραφή περιεχομένων κρύπτης diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 7f95ff5a..0c34d96d 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -263,7 +263,7 @@ Acerca de Editar entrada Escanear un código QR - Seleccionar entradas + Seleccionar entradas Limpiar entradas Su caja fuerte ya contiene entradas. ¿Desea eliminarlas antes de importar este archivo?\n\nAl hacerlo, perderá permanentemente el acceso a las entradas existentes en la caja fuerte. Limpiar el contenido de la caja fuerte diff --git a/app/src/main/res/values-eu-rES/strings.xml b/app/src/main/res/values-eu-rES/strings.xml index 094efa2c..97d80b61 100644 --- a/app/src/main/res/values-eu-rES/strings.xml +++ b/app/src/main/res/values-eu-rES/strings.xml @@ -286,7 +286,7 @@ Editatu sarrera Eskaneatu QR kodea Kudeatu arteka giltzarriak - Aukeratu sarrerak + Aukeratu sarrerak Garbitu sarrerak Zure biltegiak sarrerak ditu dagoeneko. Sarrera horiek ezabatu nahi dituzu fitxategi hau inportatu aurretik?\n\nHori egitean, biltegiko sarreretarako sarbidea galduko da betirako. Garbitu biltegiaren sarrerak diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index ec83dcdc..21cfa81a 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -288,7 +288,7 @@ ویرایش آیتم اسکن بارکد دوبعدی مدیریت کلید‌ها - انتخاب آیتم‌ها + انتخاب آیتم‌ها پاک کردن موارد مخزن شما در حال حاضر شامل آیتم می‌باشد. آیا مطمئن به حذف این آیتم‌ها قبل از وارد کردن می‌باشید؟\n\nبعد از این کار شما به هیچ وجه دسترسی به آیتم های قبلی نخواهید داشت. پاک کردن اطلاعات مخزن diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 472be8cc..74961762 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -288,7 +288,7 @@ Muokkaa kohdetta Skannaa QR-koodi Hallitse avainpaikkoja - Valitse kohteet + Valitse kohteet Tyhjennä kohteet Holvisi sisältää jo kohteita. Haluatko poistaa nämä kohteet ennen tämän tiedoston tuontia?\n\nTehdessäsi näin menetät pysyvästi pääsyn holvin olemassa oleviin kohteisiin. Tyhjennä holvin sisältö diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 48b127a7..7b19d332 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -292,7 +292,7 @@ Modifier l\'entrée Scanner un code QR Gérer les emplacements de clés - Sélectionner les entrées + Sélectionner les entrées Effacer les entrées Votre coffre-fort contient déjà des entrées. Voulez-vous supprimer ces entrées avant d\'importer ce fichier ?\n\nEn faisant cela, vous perdrez définitivement l\'accès aux entrées existantes dans le coffre-fort. Effacer le contenu du coffre-fort diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index bef7a4a7..2c90abfd 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -283,7 +283,7 @@ प्रविष्टि संपादित करें क्यूआर कोड स्कैन करें कुंजी स्लॉट प्रबंधन करें - प्रविष्टियाँ चयन करें + प्रविष्टियाँ चयन करें प्रविष्टियों को साफ करें आपके वॉल्ट में पहले से ही प्रविष्टियाँ हैं। क्या आप इस फ़ाइल को आयात करने से पहले इन प्रविष्टियों को निकालना चाहते हैं?\n\n ऐसा करने पर, आप स्थायी रूप से वॉल्ट में मौजूदा प्रविष्टियों तक पहुंच खो देंगे। वॉल्ट सामग्री साफ करें diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml index 36916936..5ad210f7 100644 --- a/app/src/main/res/values-in-rID/strings.xml +++ b/app/src/main/res/values-in-rID/strings.xml @@ -284,7 +284,7 @@ Sunting catatan Pindai kode QR Atur slot kunci - Pilih catatan + Pilih catatan Hapus catatan Brankas Anda sudah memiliki catatan. Apakah Anda ingin menghapus catatan ini tanpa mengimpor berkas ini?\n\nJika ini dilakukan, Anda akan kehilangan akses secara permanen ke catatan yang ada di brankas. Hapus konten brankas diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 69778ff3..35802679 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -292,7 +292,7 @@ Modifica voce Scansiona un codice QR Gestisci slot di chiavi - Seleziona voci + Seleziona voci Cancella voci La tua cassaforte contiene già delle voci. Rimuovere queste voci prima di importare questo file?\n\nIn questo modo, perderai definitivamente l\'accesso alle voci esistenti nella cassaforte. Cancella contenuto della cassaforte diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index a35e1f23..9fe70bee 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -284,7 +284,7 @@ エントリーを編集 QRコードをスキャン キースロットの管理 - 項目を選択 + 項目を選択 項目を消去 保管庫の内容を消去 イージスはパニックトリガーを受信しましたが、設定は無効になっています。 diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index 8ad9d7cd..770812fe 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -292,7 +292,7 @@ Item bewerken Scan een QR-code Beheer sleutelslots - Geselecteerde items + Geselecteerde items Items verwijderen Je kluis bevat al items. Wilt u deze invoergegevens verwijderen voordat u dit bestand importeert?\n\nHierdoor verliest u permanent toegang tot de bestaande items in de kluis. Kluis inhoud wissen diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index fede510e..1cffa135 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -304,7 +304,7 @@ Edytuj wpis Skanuj kod QR Zarządzaj slotami klucza - Zaznacz wpisy + Zaznacz wpisy Wyczyść wpisy Twój sejf zawiera już wpisy. Czy chcesz usunąć te wpisy przed zaimportowaniem tego pliku?\n\nW ten sposób trwale stracisz dostęp do obecnych wpisów w sejfie. Wyczyść zawartość sejfu diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 0e9c2aa9..513e0877 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -292,7 +292,7 @@ Editar entrada Escanear QR code Gerenciar slots de chaves - Selecionar entradas + Selecionar entradas Limpar entradas O seu cofre já contém entradas. Você quer remover essas entradas antes de importar este arquivo?\n\nAo fazer isso, você perderá permanentemente o acesso às entradas existentes no cofre. Limpar conteúdo do cofre diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index 858470f9..be0d1ce3 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -292,7 +292,7 @@ Editar entrada Escanear um código QR Gerenciar slots de chaves - Selecionar entradas + Selecionar entradas Limpar entradas O seu cofre já contém entradas. Você quer remover essas entradas antes de importar este arquivo?\n\nAo fazer isso, você perderá permanentemente o acesso às entradas existentes no cofre. Limpar conteúdo do cofre diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index f4d58306..ccf85f42 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -296,7 +296,7 @@ Editați intrarea Scanează cod QR Gestionare sloturi cheie - Selectați intrările + Selectați intrările Ştergeţi intrările Seiful dvs. conține deja intrări. Doriţi să ştergeţi aceste intrări înainte de a importa acest fişier?\n\nFăcând acest lucru, vei pierde permanent accesul la intrările existente din seif. Ştergeţi conţinutul seifului diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index df98b467..0426ec51 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -304,7 +304,7 @@ Редактировать запись Сканировать QR-код Управление ключевыми слотами - Выбрать записи + Выбрать записи Очистить записи В вашем хранилище уже есть записи. Вы хотите удалить эти записи перед импортом файла?\n\nПри этом вы навсегда потеряете доступ к существующим записям в хранилище. Очистить содержимое хранилища diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 06a79493..f96b8d76 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -286,7 +286,7 @@ 编辑项目 扫描二维码 管理密钥槽位 - 选择项目 + 选择项目 擦除条目 您的密码库已经包含条目。 您想要在导入此文件之前删除这些条目吗?\n\n这样做,您将永久失去对密码库中现有条目的访问权限。 擦除密码库内容 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index e45deed4..ee2f2b11 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -276,7 +276,7 @@ 編輯條目 掃描QR碼 管理密鎖槽位 - 選擇條目 + 選擇條目 清除條目 您的數據庫已包含某些條目,您是否想在匯入這個檔案前,先移除這些重複的條目?\n\n如果繼續,您將會失去之前擁有的重複條目。 清除條目的內容 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4fb376a7..8119d063 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -344,7 +344,7 @@ Edit entry Scan a QR code Manage key slots - Select entries + Import entries Wipe entries Your vault already contains entries. Do you want to remove these entries before importing this file?\n\nIn doing so, you will permanently lose access to the existing entries in the vault. Wipe vault contents