Move import logic to separate activity to fix a couple of issues

Fixes #456.
Fixes #670.
This commit is contained in:
Alexander Bakker 2021-01-24 20:19:29 +01:00
parent 7be1a74cfd
commit ae71febf10
39 changed files with 415 additions and 462 deletions

View file

@ -30,8 +30,8 @@
android:name=".ui.AboutActivity" android:name=".ui.AboutActivity"
android:label="@string/title_activity_about" /> android:label="@string/title_activity_about" />
<activity <activity
android:name=".ui.SelectEntriesActivity" android:name=".ui.ImportEntriesActivity"
android:label="@string/title_activity_select_entries" /> android:label="@string/title_activity_import_entries" />
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
android:label="${title}"> android:label="${title}">

View file

@ -1,9 +1,14 @@
package com.beemdevelopment.aegis.importers; package com.beemdevelopment.aegis.importers;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import androidx.lifecycle.Lifecycle;
import com.beemdevelopment.aegis.encoding.EncodingException; import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.helpers.ContextHelper;
import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.ui.Dialogs;
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.util.IOUtils;
import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultEntry;
@ -79,7 +84,26 @@ public class AegisImporter extends DatabaseImporter {
@Override @Override
public void decrypt(Context context, DecryptListener listener) { public void decrypt(Context context, DecryptListener listener) {
Dialogs.showPasswordInputDialog(context, (Dialogs.TextInputListener) password -> {
List<PasswordSlot> 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());
} }
} }

View file

@ -190,7 +190,7 @@ public class AndOtpImporter extends DatabaseImporter {
} catch (DatabaseImporterException e) { } catch (DatabaseImporterException e) {
listener.onError(e); listener.onError(e);
} }
}); }, dialog1 -> listener.onCanceled());
}) })
.create()); .create());
} }

View file

@ -71,7 +71,7 @@ public class AuthenticatorPlusImporter extends DatabaseImporter {
} catch (DatabaseImporterException e) { } catch (DatabaseImporterException e) {
listener.onError(e); listener.onError(e);
} }
}); }, dialog1 -> listener.onCanceled());
} }
} }
} }

View file

@ -204,7 +204,7 @@ public class AuthyImporter extends DatabaseImporter {
} catch (DatabaseImporterException e) { } catch (DatabaseImporterException e) {
listener.onError(e); listener.onError(e);
} }
}); }, dialog1 -> listener.onCanceled());
} }
} }

View file

@ -167,8 +167,9 @@ public abstract class DatabaseImporter {
} }
} }
public interface DecryptListener { public static abstract class DecryptListener {
void onStateDecrypted(State state); protected abstract void onStateDecrypted(State state);
void onError(Exception e); protected abstract void onError(Exception e);
protected abstract void onCanceled();
} }
} }

View file

@ -159,7 +159,7 @@ public class TotpAuthenticatorImporter extends DatabaseImporter {
.setPositiveButton(R.string.yes, (dialog, which) -> { .setPositiveButton(R.string.yes, (dialog, which) -> {
Dialogs.showPasswordInputDialog(context, password -> { Dialogs.showPasswordInputDialog(context, password -> {
decrypt(password, listener); decrypt(password, listener);
}); }, dialog1 -> listener.onCanceled());
}) })
.setNegativeButton(R.string.no, (dialog, which) -> { .setNegativeButton(R.string.no, (dialog, which) -> {
decrypt(PASSWORD, listener); decrypt(PASSWORD, listener);

View file

@ -188,7 +188,7 @@ public class Dialogs {
showSecureDialog(dialog); 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); View view = LayoutInflater.from(context).inflate(R.layout.dialog_text_input, null);
TextInputEditText input = view.findViewById(R.id.text_input); TextInputEditText input = view.findViewById(R.id.text_input);
TextInputLayout inputLayout = view.findViewById(R.id.text_input_layout); TextInputLayout inputLayout = view.findViewById(R.id.text_input_layout);
@ -205,8 +205,8 @@ public class Dialogs {
listener.onTextInputResult(text); listener.onTextInputResult(text);
}); });
if (dismissListener != null) { if (cancelListener != null) {
builder.setOnDismissListener(dismissListener); builder.setOnCancelListener(cancelListener);
} }
if (messageId != 0) { if (messageId != 0) {
@ -214,6 +214,7 @@ public class Dialogs {
} }
AlertDialog dialog = builder.create(); AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(true);
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
showSecureDialog(dialog); showSecureDialog(dialog);
} }
@ -230,12 +231,20 @@ public class Dialogs {
showTextInputDialog(context, R.string.set_password, R.string.password, listener, true); 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) { public static void showPasswordInputDialog(Context context, @StringRes int messageId, TextInputListener listener) {
showTextInputDialog(context, R.string.set_password, messageId, R.string.password, listener, null, true); 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) { public static void showPasswordInputDialog(Context context, @StringRes int messageId, TextInputListener listener, DialogInterface.OnCancelListener cancelListener) {
showTextInputDialog(context, setPasswordMessageId, messageId, R.string.password, listener, dismissListener, true); 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) { 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) AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(R.string.error_occurred) .setTitle(R.string.error_occurred)
.setView(view) .setView(view)
.setCancelable(false)
.setPositiveButton(android.R.string.ok, (dialog1, which) -> { .setPositiveButton(android.R.string.ok, (dialog1, which) -> {
if (listener != null) { if (listener != null) {
listener.onClick(dialog1, which); listener.onClick(dialog1, which);
@ -342,11 +352,6 @@ public class Dialogs {
.setNeutralButton(R.string.details, (dialog1, which) -> { .setNeutralButton(R.string.details, (dialog1, which) -> {
textDetails.setVisibility(View.VISIBLE); textDetails.setVisibility(View.VISIBLE);
}) })
.setOnDismissListener(dialog12 -> {
if (listener != null) {
listener.onClick(dialog12, -1);
}
})
.create(); .create();
dialog.setOnShowListener(d -> { dialog.setOnShowListener(d -> {

View file

@ -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<? extends DatabaseImporter> importerType = (Class<? extends DatabaseImporter>) getIntent().getSerializableExtra("importerType");
startImport(importerType, getIntent().getStringExtra("fileUri"));
}
private void startImport(@NonNull Class<? extends DatabaseImporter> importerType, @Nullable String fileUri) {
if (fileUri == null) {
startImportApp(importerType);
} else {
startImportFile(importerType, Uri.parse(fileUri));
}
}
private void startImportFile(@NonNull Class<? extends DatabaseImporter> 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<? extends DatabaseImporter> 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<VaultEntry> entries = result.getEntries();
for (VaultEntry entry : entries.getValues()) {
_adapter.addEntry(new ImportEntry(entry));
}
List<DatabaseImporterEntryException> errors = result.getErrors();
if (errors.size() > 0) {
showErrorDialog(errors);
}
}
private void showErrorDialog(List<DatabaseImporterEntryException> 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<DatabaseImporterEntryException> errors) {
List<String> 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<ImportEntry> 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;
}
}

View file

@ -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<ImportEntry> entries = (ArrayList<ImportEntry>) intent.getSerializableExtra("entries");
List<DatabaseImporterEntryException> errors = (ArrayList<DatabaseImporterEntryException>) 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<DatabaseImporterEntryException> 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<DatabaseImporterEntryException> errors) {
List<String> 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<ImportEntry> entries = _adapter.getCheckedEntries();
intent.putExtra("entries", (ArrayList<ImportEntry>) 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;
}
}

View file

@ -2,7 +2,6 @@ package com.beemdevelopment.aegis.ui.fragments;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -14,6 +13,7 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.ArrayRes; import androidx.annotation.ArrayRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.preference.Preference; import androidx.preference.Preference;
@ -21,47 +21,37 @@ import androidx.preference.Preference;
import com.beemdevelopment.aegis.BuildConfig; import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.helpers.DropdownHelper; import com.beemdevelopment.aegis.helpers.DropdownHelper;
import com.beemdevelopment.aegis.importers.AegisImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporter; 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.Dialogs;
import com.beemdevelopment.aegis.ui.SelectEntriesActivity; import com.beemdevelopment.aegis.ui.ImportEntriesActivity;
import com.beemdevelopment.aegis.ui.models.ImportEntry;
import com.beemdevelopment.aegis.ui.tasks.ExportTask; import com.beemdevelopment.aegis.ui.tasks.ExportTask;
import com.beemdevelopment.aegis.util.UUIDMap;
import com.beemdevelopment.aegis.vault.VaultBackupManager; import com.beemdevelopment.aegis.vault.VaultBackupManager;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultFileCredentials; import com.beemdevelopment.aegis.vault.VaultFileCredentials;
import com.beemdevelopment.aegis.vault.VaultManager; import com.beemdevelopment.aegis.vault.VaultManager;
import com.beemdevelopment.aegis.vault.VaultManagerException; import com.beemdevelopment.aegis.vault.VaultManagerException;
import com.beemdevelopment.aegis.vault.slots.Slot; import com.beemdevelopment.aegis.vault.slots.Slot;
import com.beemdevelopment.aegis.vault.slots.SlotException; import com.beemdevelopment.aegis.vault.slots.SlotException;
import com.topjohnwu.superuser.Shell;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher; import javax.crypto.Cipher;
public class ImportExportPreferencesFragment extends PreferencesFragment { 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<? extends DatabaseImporter> _importerType; private Class<? extends DatabaseImporter> _importerType;
private AegisImporter.State _importerState;
private UUIDMap<VaultEntry> _importerEntries;
@Override @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
super.onCreatePreferences(savedInstanceState, rootKey); super.onCreatePreferences(savedInstanceState, rootKey);
addPreferencesFromResource(R.xml.preferences_import_export); addPreferencesFromResource(R.xml.preferences_import_export);
if (savedInstanceState != null) {
_importerType = (Class<? extends DatabaseImporter>) savedInstanceState.getSerializable("importerType");
}
Preference importPreference = findPreference("pref_import"); Preference importPreference = findPreference("pref_import");
importPreference.setOnPreferenceClickListener(preference -> { importPreference.setOnPreferenceClickListener(preference -> {
Dialogs.showImportersDialog(getContext(), false, definition -> { Dialogs.showImportersDialog(getContext(), false, definition -> {
@ -69,7 +59,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*"); intent.setType("*/*");
startActivityForResult(intent, CODE_IMPORT); startActivityForResult(intent, CODE_IMPORT_SELECT);
}); });
return true; return true;
}); });
@ -77,8 +67,7 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
Preference importAppPreference = findPreference("pref_import_app"); Preference importAppPreference = findPreference("pref_import_app");
importAppPreference.setOnPreferenceClickListener(preference -> { importAppPreference.setOnPreferenceClickListener(preference -> {
Dialogs.showImportersDialog(getContext(), true, definition -> { Dialogs.showImportersDialog(getContext(), true, definition -> {
DatabaseImporter importer = DatabaseImporter.create(getContext(), definition.getType()); startImportEntriesActivity(definition.getType(), null);
importApp(importer);
}); });
return true; 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 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) { 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) { switch (requestCode) {
case CODE_IMPORT: case CODE_IMPORT_SELECT:
onImportResult(resultCode, data); onImportSelectResult(resultCode, data);
break;
case CODE_IMPORT_DECRYPT:
onImportDecryptResult(resultCode, data);
break;
case CODE_SELECT_ENTRIES:
onSelectEntriesResult(resultCode, data);
break; break;
case CODE_EXPORT: case CODE_EXPORT:
// intentional fallthrough // intentional fallthrough
@ -114,118 +105,20 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
} }
} }
private void importApp(DatabaseImporter importer) { private void onImportSelectResult(int resultCode, Intent data) {
// 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) {
Uri uri = data.getData(); Uri uri = data.getData();
if (resultCode != Activity.RESULT_OK || uri == null) { if (resultCode != Activity.RESULT_OK || uri == null) {
return; return;
} }
try (InputStream stream = getContext().getContentResolver().openInputStream(uri)) { startImportEntriesActivity(_importerType, 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);
}
} }
private void importDatabase(DatabaseImporter.State state) { private void startImportEntriesActivity(Class<? extends DatabaseImporter> importerType, Uri fileUri) {
DatabaseImporter.Result result; Intent intent = new Intent(getActivity(), ImportEntriesActivity.class);
try { intent.putExtra("importerType", importerType);
result = state.convert(); intent.putExtra("fileUri", fileUri == null ? null : fileUri.toString());
} catch (DatabaseImporterException e) { startActivityForResult(intent, CODE_IMPORT);
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.parsing_file_error, e);
return;
}
_importerEntries = result.getEntries();
List<ImportEntry> entries = new ArrayList<>();
for (VaultEntry entry : _importerEntries) {
entries.add(new ImportEntry(entry));
}
Intent intent = new Intent(getActivity(), SelectEntriesActivity.class);
intent.putExtra("entries", (ArrayList<ImportEntry>) entries);
intent.putExtra("errors", (ArrayList<DatabaseImporterEntryException>) result.getErrors());
intent.putExtra("vaultContainsEntries", getVault().getEntries().size() > 0);
startActivityForResult(intent, CODE_SELECT_ENTRIES);
} }
private void startExport() { private void startExport() {
@ -326,39 +219,6 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
Dialogs.showSecureDialog(dialog); 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<ImportEntry> selectedEntries = (ArrayList<ImportEntry>) 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) { private static int getExportRequestCode(int spinnerPos, boolean encrypt) {
if (spinnerPos == 0) { if (spinnerPos == 0) {
return encrypt ? CODE_EXPORT : CODE_EXPORT_PLAIN; return encrypt ? CODE_EXPORT : CODE_EXPORT_PLAIN;
@ -433,7 +293,6 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
return; return;
} }
startExportVault(requestCode, cb -> { startExportVault(requestCode, cb -> {
File file; File file;
OutputStream outStream = null; OutputStream outStream = null;

View file

@ -16,11 +16,10 @@ import com.beemdevelopment.aegis.vault.VaultManagerException;
public abstract class PreferencesFragment extends PreferenceFragmentCompat { public abstract class PreferencesFragment extends PreferenceFragmentCompat {
// activity request codes // activity request codes
public static final int CODE_IMPORT = 0; public static final int CODE_IMPORT_SELECT = 0;
public static final int CODE_IMPORT_DECRYPT = 1;
public static final int CODE_SLOTS = 2; public static final int CODE_SLOTS = 2;
public static final int CODE_GROUPS = 3; 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 = 5;
public static final int CODE_EXPORT_PLAIN = 6; public static final int CODE_EXPORT_PLAIN = 6;
public static final int CODE_EXPORT_GOOGLE_URI = 7; public static final int CODE_EXPORT_GOOGLE_URI = 7;

View file

@ -3,32 +3,19 @@ package com.beemdevelopment.aegis.ui.models;
import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultEntry;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID;
public class ImportEntry implements Serializable { public class ImportEntry implements Serializable {
private UUID _uuid; private final VaultEntry _entry;
private String _name;
private String _issuer;
private transient Listener _listener; private transient Listener _listener;
private boolean _isChecked = true; private boolean _isChecked = true;
public ImportEntry(VaultEntry entry) { public ImportEntry(VaultEntry entry) {
_uuid = entry.getUUID(); _entry = entry;
_name = entry.getName();
_issuer = entry.getIssuer();
} }
public UUID getUUID() { public VaultEntry getEntry() {
return _uuid; return _entry;
}
public String getName() {
return _name;
}
public String getIssuer() {
return _issuer;
} }
public void setOnCheckedChangedListener(Listener listener) { public void setOnCheckedChangedListener(Listener listener) {

View file

@ -4,15 +4,15 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.ui.models.ImportEntry; import com.beemdevelopment.aegis.ui.models.ImportEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public class ImportEntriesAdapter extends RecyclerView.Adapter<ImportEntryHolder> { public class ImportEntriesAdapter extends RecyclerView.Adapter<ImportEntryHolder> {
private List<ImportEntry> _entries; private List<ImportEntry> _entries;
@ -47,7 +47,7 @@ public class ImportEntriesAdapter extends RecyclerView.Adapter<ImportEntryHolder
@Override @Override
public void onViewRecycled(@NonNull ImportEntryHolder holder) { public void onViewRecycled(@NonNull ImportEntryHolder holder) {
holder.getEntry().setOnCheckedChangedListener(null); holder.getData().setOnCheckedChangedListener(null);
} }
@Override @Override

View file

@ -15,7 +15,7 @@ public class ImportEntryHolder extends RecyclerView.ViewHolder implements Import
private TextView _accountName; private TextView _accountName;
private CheckBox _checkbox; private CheckBox _checkbox;
private ImportEntry _entry; private ImportEntry _data;
public ImportEntryHolder(final View view) { public ImportEntryHolder(final View view) {
super(view); super(view);
@ -23,20 +23,20 @@ public class ImportEntryHolder extends RecyclerView.ViewHolder implements Import
_issuer = view.findViewById(R.id.profile_issuer); _issuer = view.findViewById(R.id.profile_issuer);
_accountName = view.findViewById(R.id.profile_account_name); _accountName = view.findViewById(R.id.profile_account_name);
_checkbox = view.findViewById(R.id.checkbox_import_entry); _checkbox = view.findViewById(R.id.checkbox_import_entry);
view.setOnClickListener(v -> _entry.setIsChecked(!_entry.isChecked())); view.setOnClickListener(v -> _data.setIsChecked(!_data.isChecked()));
} }
public void setData(ImportEntry entry) { public void setData(ImportEntry data) {
_entry = entry; _data = data;
Context context = itemView.getContext(); Context context = itemView.getContext();
_issuer.setText(!entry.getIssuer().isEmpty() ? entry.getIssuer() : context.getString(R.string.unknown_issuer)); _issuer.setText(!_data.getEntry().getIssuer().isEmpty() ? _data.getEntry().getIssuer() : context.getString(R.string.unknown_issuer));
_accountName.setText(!entry.getName().isEmpty() ? entry.getName() : context.getString(R.string.unknown_account_name)); _accountName.setText(!_data.getEntry().getName().isEmpty() ? _data.getEntry().getName() : context.getString(R.string.unknown_account_name));
_checkbox.setChecked(entry.isChecked()); _checkbox.setChecked(_data.isChecked());
} }
public ImportEntry getEntry() { public ImportEntry getData() {
return _entry; return _data;
} }
@Override @Override

View file

@ -6,7 +6,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/background" android:background="?attr/background"
android:id="@+id/importEntriesRootView" android:id="@+id/importEntriesRootView"
tools:context="com.beemdevelopment.aegis.ui.SelectEntriesActivity"> tools:context="com.beemdevelopment.aegis.ui.ImportEntriesActivity">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -2,75 +2,44 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:foreground="?android:attr/selectableItemBackground" android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:layout_height="wrap_content" android:orientation="horizontal">
android:orientation="vertical">
<LinearLayout <LinearLayout
android:orientation="horizontal" android:layout_weight="1"
android:background="?attr/cardBackground"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:orientation="vertical"
android:layout_alignParentBottom="false" android:padding="16dp"
android:layout_alignParentTop="false" android:layout_gravity="center_vertical">
android:layout_alignWithParentIfMissing="false"
android:gravity="bottom"
android:id="@+id/relativeLayout"
android:layout_alignParentStart="false"
android:paddingTop="12dp"
android:paddingBottom="16dp"
android:paddingEnd="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingStart="16dp">
<TextView <TextView
android:id="@+id/profile_issuer" android:id="@+id/profile_issuer"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="0dp"
android:fontFamily="sans-serif-light" android:fontFamily="sans-serif-light"
android:includeFontPadding="false"
android:textAppearance="?android:attr/textAppearanceMedium" android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?attr/primaryText" android:textColor="?attr/primaryText"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="normal|bold" android:textStyle="normal|bold"
android:gravity="center_vertical"
tools:text="@string/issuer" /> tools:text="@string/issuer" />
<TextView <TextView
android:id="@+id/profile_account_name" android:id="@+id/profile_account_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/profile_issuer"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:ellipsize="end" android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/extra_info_text" android:textColor="@color/extra_info_text"
android:textSize="14sp" android:textSize="14sp"
tools:text="AccountName" /> tools:text="AccountName" />
</LinearLayout>
<CheckBox <CheckBox
android:id="@+id/checkbox_import_entry" android:id="@+id/checkbox_import_entry"
android:clickable="false" android:clickable="false"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentTop="true" android:layout_gravity="center_vertical"/>
android:layout_alignParentEnd="true"
android:layout_marginTop="6dp"
android:layout_marginEnd="6dp" />
</RelativeLayout>
</LinearLayout>
</LinearLayout> </LinearLayout>

View file

@ -296,7 +296,7 @@
<string name="title_activity_edit_entry">Upravit položku</string> <string name="title_activity_edit_entry">Upravit položku</string>
<string name="title_activity_scan_qr">Naskenovat QR kód</string> <string name="title_activity_scan_qr">Naskenovat QR kód</string>
<string name="title_activity_manage_slots">Spravovat sloty klíčů</string> <string name="title_activity_manage_slots">Spravovat sloty klíčů</string>
<string name="title_activity_select_entries">Vybrat položky</string> <string name="title_activity_import_entries">Vybrat položky</string>
<string name="dialog_wipe_entries_title">Vyčistit položky</string> <string name="dialog_wipe_entries_title">Vyčistit položky</string>
<string name="dialog_wipe_entries_message">Váš trezor už obsahuje záznamy. Chcete tyto záznamy smazat před importem tohoto souboru?\n\n<b>Přístup ke všem stávajícím položkám v trezoru trvale ztratíte.</b></string> <string name="dialog_wipe_entries_message">Váš trezor už obsahuje záznamy. Chcete tyto záznamy smazat před importem tohoto souboru?\n\n<b>Přístup ke všem stávajícím položkám v trezoru trvale ztratíte.</b></string>
<string name="dialog_wipe_entries_checkbox">Vymazat obsah trezoru</string> <string name="dialog_wipe_entries_checkbox">Vymazat obsah trezoru</string>

View file

@ -292,7 +292,7 @@
<string name="title_activity_edit_entry">Eintrag bearbeiten</string> <string name="title_activity_edit_entry">Eintrag bearbeiten</string>
<string name="title_activity_scan_qr">Scanne einen QR-Code</string> <string name="title_activity_scan_qr">Scanne einen QR-Code</string>
<string name="title_activity_manage_slots">Schlüsselplätze verwalten</string> <string name="title_activity_manage_slots">Schlüsselplätze verwalten</string>
<string name="title_activity_select_entries">Einträge auswählen</string> <string name="title_activity_import_entries">Einträge auswählen</string>
<string name="dialog_wipe_entries_title">Einträge löschen</string> <string name="dialog_wipe_entries_title">Einträge löschen</string>
<string name="dialog_wipe_entries_message">Deine Datenbank enthält bereits Einträge. Möchtest du diese Einträge entfernen, bevor diese Datei importiert wird?\n\n<b>Wenn du dies tust, verlierst du dauerhaft den Zugriff auf die vorhandenen Einträge in der Datenbank.</b></string> <string name="dialog_wipe_entries_message">Deine Datenbank enthält bereits Einträge. Möchtest du diese Einträge entfernen, bevor diese Datei importiert wird?\n\n<b>Wenn du dies tust, verlierst du dauerhaft den Zugriff auf die vorhandenen Einträge in der Datenbank.</b></string>
<string name="dialog_wipe_entries_checkbox">Datenbankinhalt löschen</string> <string name="dialog_wipe_entries_checkbox">Datenbankinhalt löschen</string>

View file

@ -292,7 +292,7 @@
<string name="title_activity_edit_entry">Επεξεργασία καταχώρησης</string> <string name="title_activity_edit_entry">Επεξεργασία καταχώρησης</string>
<string name="title_activity_scan_qr">Σάρωση κωδικού QR</string> <string name="title_activity_scan_qr">Σάρωση κωδικού QR</string>
<string name="title_activity_manage_slots">Διαχείριση κλειδιών υποδοχής</string> <string name="title_activity_manage_slots">Διαχείριση κλειδιών υποδοχής</string>
<string name="title_activity_select_entries">Επιλέξτε καταχωρήσεις</string> <string name="title_activity_import_entries">Επιλέξτε καταχωρήσεις</string>
<string name="dialog_wipe_entries_title">Διαγραφή καταχωρήσεων</string> <string name="dialog_wipe_entries_title">Διαγραφή καταχωρήσεων</string>
<string name="dialog_wipe_entries_message">Η κρύπτη σας περιέχει ήδη καταχωρήσεις. Θέλετε να καταργήσετε αυτές τις καταχωρήσεις πριν από την εισαγωγή αυτού του αρχείου;\n\n <b> Με αυτόν τον τρόπο, θα χάσετε μόνιμα την πρόσβαση στις υπάρχουσες καταχωρήσεις στην κρύπτη. </b></string> <string name="dialog_wipe_entries_message">Η κρύπτη σας περιέχει ήδη καταχωρήσεις. Θέλετε να καταργήσετε αυτές τις καταχωρήσεις πριν από την εισαγωγή αυτού του αρχείου;\n\n <b> Με αυτόν τον τρόπο, θα χάσετε μόνιμα την πρόσβαση στις υπάρχουσες καταχωρήσεις στην κρύπτη. </b></string>
<string name="dialog_wipe_entries_checkbox">Διαγραφή περιεχομένων κρύπτης</string> <string name="dialog_wipe_entries_checkbox">Διαγραφή περιεχομένων κρύπτης</string>

View file

@ -263,7 +263,7 @@
<string name="title_activity_about">Acerca de</string> <string name="title_activity_about">Acerca de</string>
<string name="title_activity_edit_entry">Editar entrada</string> <string name="title_activity_edit_entry">Editar entrada</string>
<string name="title_activity_scan_qr">Escanear un código QR</string> <string name="title_activity_scan_qr">Escanear un código QR</string>
<string name="title_activity_select_entries">Seleccionar entradas</string> <string name="title_activity_import_entries">Seleccionar entradas</string>
<string name="dialog_wipe_entries_title">Limpiar entradas</string> <string name="dialog_wipe_entries_title">Limpiar entradas</string>
<string name="dialog_wipe_entries_message">Su caja fuerte ya contiene entradas. ¿Desea eliminarlas antes de importar este archivo?\n\n<b>Al hacerlo, perderá permanentemente el acceso a las entradas existentes en la caja fuerte.</b></string> <string name="dialog_wipe_entries_message">Su caja fuerte ya contiene entradas. ¿Desea eliminarlas antes de importar este archivo?\n\n<b>Al hacerlo, perderá permanentemente el acceso a las entradas existentes en la caja fuerte.</b></string>
<string name="dialog_wipe_entries_checkbox">Limpiar el contenido de la caja fuerte</string> <string name="dialog_wipe_entries_checkbox">Limpiar el contenido de la caja fuerte</string>

View file

@ -286,7 +286,7 @@
<string name="title_activity_edit_entry">Editatu sarrera</string> <string name="title_activity_edit_entry">Editatu sarrera</string>
<string name="title_activity_scan_qr">Eskaneatu QR kodea</string> <string name="title_activity_scan_qr">Eskaneatu QR kodea</string>
<string name="title_activity_manage_slots">Kudeatu arteka giltzarriak</string> <string name="title_activity_manage_slots">Kudeatu arteka giltzarriak</string>
<string name="title_activity_select_entries">Aukeratu sarrerak</string> <string name="title_activity_import_entries">Aukeratu sarrerak</string>
<string name="dialog_wipe_entries_title">Garbitu sarrerak</string> <string name="dialog_wipe_entries_title">Garbitu sarrerak</string>
<string name="dialog_wipe_entries_message">Zure biltegiak sarrerak ditu dagoeneko. Sarrera horiek ezabatu nahi dituzu fitxategi hau inportatu aurretik?\n\n<b>Hori egitean, biltegiko sarreretarako sarbidea galduko da betirako.</b></string> <string name="dialog_wipe_entries_message">Zure biltegiak sarrerak ditu dagoeneko. Sarrera horiek ezabatu nahi dituzu fitxategi hau inportatu aurretik?\n\n<b>Hori egitean, biltegiko sarreretarako sarbidea galduko da betirako.</b></string>
<string name="dialog_wipe_entries_checkbox">Garbitu biltegiaren sarrerak</string> <string name="dialog_wipe_entries_checkbox">Garbitu biltegiaren sarrerak</string>

View file

@ -288,7 +288,7 @@
<string name="title_activity_edit_entry">ویرایش آیتم</string> <string name="title_activity_edit_entry">ویرایش آیتم</string>
<string name="title_activity_scan_qr">اسکن بارکد دوبعدی</string> <string name="title_activity_scan_qr">اسکن بارکد دوبعدی</string>
<string name="title_activity_manage_slots">مدیریت کلید‌ها</string> <string name="title_activity_manage_slots">مدیریت کلید‌ها</string>
<string name="title_activity_select_entries">انتخاب آیتم‌ها</string> <string name="title_activity_import_entries">انتخاب آیتم‌ها</string>
<string name="dialog_wipe_entries_title">پاک کردن موارد</string> <string name="dialog_wipe_entries_title">پاک کردن موارد</string>
<string name="dialog_wipe_entries_message">مخزن شما در حال حاضر شامل آیتم می‌باشد. آیا مطمئن به حذف این آیتم‌ها قبل از وارد کردن می‌باشید؟\n\n<b>بعد از این کار شما به هیچ وجه دسترسی به آیتم های قبلی نخواهید داشت.</b></string> <string name="dialog_wipe_entries_message">مخزن شما در حال حاضر شامل آیتم می‌باشد. آیا مطمئن به حذف این آیتم‌ها قبل از وارد کردن می‌باشید؟\n\n<b>بعد از این کار شما به هیچ وجه دسترسی به آیتم های قبلی نخواهید داشت.</b></string>
<string name="dialog_wipe_entries_checkbox">پاک کردن اطلاعات مخزن</string> <string name="dialog_wipe_entries_checkbox">پاک کردن اطلاعات مخزن</string>

View file

@ -288,7 +288,7 @@
<string name="title_activity_edit_entry">Muokkaa kohdetta</string> <string name="title_activity_edit_entry">Muokkaa kohdetta</string>
<string name="title_activity_scan_qr">Skannaa QR-koodi</string> <string name="title_activity_scan_qr">Skannaa QR-koodi</string>
<string name="title_activity_manage_slots">Hallitse avainpaikkoja</string> <string name="title_activity_manage_slots">Hallitse avainpaikkoja</string>
<string name="title_activity_select_entries">Valitse kohteet</string> <string name="title_activity_import_entries">Valitse kohteet</string>
<string name="dialog_wipe_entries_title">Tyhjennä kohteet</string> <string name="dialog_wipe_entries_title">Tyhjennä kohteet</string>
<string name="dialog_wipe_entries_message">Holvisi sisältää jo kohteita. Haluatko poistaa nämä kohteet ennen tämän tiedoston tuontia?\n\n<b>Tehdessäsi näin menetät pysyvästi pääsyn holvin olemassa oleviin kohteisiin.</b></string> <string name="dialog_wipe_entries_message">Holvisi sisältää jo kohteita. Haluatko poistaa nämä kohteet ennen tämän tiedoston tuontia?\n\n<b>Tehdessäsi näin menetät pysyvästi pääsyn holvin olemassa oleviin kohteisiin.</b></string>
<string name="dialog_wipe_entries_checkbox">Tyhjennä holvin sisältö</string> <string name="dialog_wipe_entries_checkbox">Tyhjennä holvin sisältö</string>

View file

@ -292,7 +292,7 @@
<string name="title_activity_edit_entry">Modifier l\'entrée</string> <string name="title_activity_edit_entry">Modifier l\'entrée</string>
<string name="title_activity_scan_qr">Scanner un code QR</string> <string name="title_activity_scan_qr">Scanner un code QR</string>
<string name="title_activity_manage_slots">Gérer les emplacements de clés</string> <string name="title_activity_manage_slots">Gérer les emplacements de clés</string>
<string name="title_activity_select_entries">Sélectionner les entrées</string> <string name="title_activity_import_entries">Sélectionner les entrées</string>
<string name="dialog_wipe_entries_title">Effacer les entrées</string> <string name="dialog_wipe_entries_title">Effacer les entrées</string>
<string name="dialog_wipe_entries_message">Votre coffre-fort contient déjà des entrées. Voulez-vous supprimer ces entrées avant d\'importer ce fichier ?\n\n<b>En faisant cela, vous perdrez définitivement l\'accès aux entrées existantes dans le coffre-fort.</b></string> <string name="dialog_wipe_entries_message">Votre coffre-fort contient déjà des entrées. Voulez-vous supprimer ces entrées avant d\'importer ce fichier ?\n\n<b>En faisant cela, vous perdrez définitivement l\'accès aux entrées existantes dans le coffre-fort.</b></string>
<string name="dialog_wipe_entries_checkbox">Effacer le contenu du coffre-fort</string> <string name="dialog_wipe_entries_checkbox">Effacer le contenu du coffre-fort</string>

View file

@ -283,7 +283,7 @@
<string name="title_activity_edit_entry">प्रविष्टि संपादित करें</string> <string name="title_activity_edit_entry">प्रविष्टि संपादित करें</string>
<string name="title_activity_scan_qr">क्यूआर कोड स्कैन करें</string> <string name="title_activity_scan_qr">क्यूआर कोड स्कैन करें</string>
<string name="title_activity_manage_slots">कुंजी स्लॉट प्रबंधन करें</string> <string name="title_activity_manage_slots">कुंजी स्लॉट प्रबंधन करें</string>
<string name="title_activity_select_entries">प्रविष्टियाँ चयन करें</string> <string name="title_activity_import_entries">प्रविष्टियाँ चयन करें</string>
<string name="dialog_wipe_entries_title">प्रविष्टियों को साफ करें</string> <string name="dialog_wipe_entries_title">प्रविष्टियों को साफ करें</string>
<string name="dialog_wipe_entries_message">आपके वॉल्ट में पहले से ही प्रविष्टियाँ हैं। क्या आप इस फ़ाइल को आयात करने से पहले इन प्रविष्टियों को निकालना चाहते हैं?\n\n<b> ऐसा करने पर, आप स्थायी रूप से वॉल्ट में मौजूदा प्रविष्टियों तक पहुंच खो देंगे। </b></string> <string name="dialog_wipe_entries_message">आपके वॉल्ट में पहले से ही प्रविष्टियाँ हैं। क्या आप इस फ़ाइल को आयात करने से पहले इन प्रविष्टियों को निकालना चाहते हैं?\n\n<b> ऐसा करने पर, आप स्थायी रूप से वॉल्ट में मौजूदा प्रविष्टियों तक पहुंच खो देंगे। </b></string>
<string name="dialog_wipe_entries_checkbox">वॉल्ट सामग्री साफ करें</string> <string name="dialog_wipe_entries_checkbox">वॉल्ट सामग्री साफ करें</string>

View file

@ -284,7 +284,7 @@
<string name="title_activity_edit_entry">Sunting catatan</string> <string name="title_activity_edit_entry">Sunting catatan</string>
<string name="title_activity_scan_qr">Pindai kode QR</string> <string name="title_activity_scan_qr">Pindai kode QR</string>
<string name="title_activity_manage_slots">Atur slot kunci</string> <string name="title_activity_manage_slots">Atur slot kunci</string>
<string name="title_activity_select_entries">Pilih catatan</string> <string name="title_activity_import_entries">Pilih catatan</string>
<string name="dialog_wipe_entries_title">Hapus catatan</string> <string name="dialog_wipe_entries_title">Hapus catatan</string>
<string name="dialog_wipe_entries_message">Brankas Anda sudah memiliki catatan. Apakah Anda ingin menghapus catatan ini tanpa mengimpor berkas ini?\n\n<b>Jika ini dilakukan, Anda akan kehilangan akses secara permanen ke catatan yang ada di brankas.</b></string> <string name="dialog_wipe_entries_message">Brankas Anda sudah memiliki catatan. Apakah Anda ingin menghapus catatan ini tanpa mengimpor berkas ini?\n\n<b>Jika ini dilakukan, Anda akan kehilangan akses secara permanen ke catatan yang ada di brankas.</b></string>
<string name="dialog_wipe_entries_checkbox">Hapus konten brankas</string> <string name="dialog_wipe_entries_checkbox">Hapus konten brankas</string>

View file

@ -292,7 +292,7 @@
<string name="title_activity_edit_entry">Modifica voce</string> <string name="title_activity_edit_entry">Modifica voce</string>
<string name="title_activity_scan_qr">Scansiona un codice QR</string> <string name="title_activity_scan_qr">Scansiona un codice QR</string>
<string name="title_activity_manage_slots">Gestisci slot di chiavi</string> <string name="title_activity_manage_slots">Gestisci slot di chiavi</string>
<string name="title_activity_select_entries">Seleziona voci</string> <string name="title_activity_import_entries">Seleziona voci</string>
<string name="dialog_wipe_entries_title">Cancella voci</string> <string name="dialog_wipe_entries_title">Cancella voci</string>
<string name="dialog_wipe_entries_message">La tua cassaforte contiene già delle voci. Rimuovere queste voci prima di importare questo file?\n\n<b>In questo modo, perderai definitivamente l\'accesso alle voci esistenti nella cassaforte.</b></string> <string name="dialog_wipe_entries_message">La tua cassaforte contiene già delle voci. Rimuovere queste voci prima di importare questo file?\n\n<b>In questo modo, perderai definitivamente l\'accesso alle voci esistenti nella cassaforte.</b></string>
<string name="dialog_wipe_entries_checkbox">Cancella contenuto della cassaforte</string> <string name="dialog_wipe_entries_checkbox">Cancella contenuto della cassaforte</string>

View file

@ -284,7 +284,7 @@
<string name="title_activity_edit_entry">エントリーを編集</string> <string name="title_activity_edit_entry">エントリーを編集</string>
<string name="title_activity_scan_qr">QRコードをスキャン</string> <string name="title_activity_scan_qr">QRコードをスキャン</string>
<string name="title_activity_manage_slots">キースロットの管理</string> <string name="title_activity_manage_slots">キースロットの管理</string>
<string name="title_activity_select_entries">項目を選択</string> <string name="title_activity_import_entries">項目を選択</string>
<string name="dialog_wipe_entries_title">項目を消去</string> <string name="dialog_wipe_entries_title">項目を消去</string>
<string name="dialog_wipe_entries_checkbox">保管庫の内容を消去</string> <string name="dialog_wipe_entries_checkbox">保管庫の内容を消去</string>
<string name="panic_trigger_ignore_toast">イージスはパニックトリガーを受信しましたが、設定は無効になっています。</string> <string name="panic_trigger_ignore_toast">イージスはパニックトリガーを受信しましたが、設定は無効になっています。</string>

View file

@ -292,7 +292,7 @@
<string name="title_activity_edit_entry">Item bewerken</string> <string name="title_activity_edit_entry">Item bewerken</string>
<string name="title_activity_scan_qr">Scan een QR-code</string> <string name="title_activity_scan_qr">Scan een QR-code</string>
<string name="title_activity_manage_slots">Beheer sleutelslots</string> <string name="title_activity_manage_slots">Beheer sleutelslots</string>
<string name="title_activity_select_entries">Geselecteerde items</string> <string name="title_activity_import_entries">Geselecteerde items</string>
<string name="dialog_wipe_entries_title">Items verwijderen</string> <string name="dialog_wipe_entries_title">Items verwijderen</string>
<string name="dialog_wipe_entries_message">Je kluis bevat al items. Wilt u deze invoergegevens verwijderen voordat u dit bestand importeert?\n\n<b>Hierdoor verliest u permanent toegang tot de bestaande items in de kluis.</b></string> <string name="dialog_wipe_entries_message">Je kluis bevat al items. Wilt u deze invoergegevens verwijderen voordat u dit bestand importeert?\n\n<b>Hierdoor verliest u permanent toegang tot de bestaande items in de kluis.</b></string>
<string name="dialog_wipe_entries_checkbox">Kluis inhoud wissen</string> <string name="dialog_wipe_entries_checkbox">Kluis inhoud wissen</string>

View file

@ -304,7 +304,7 @@
<string name="title_activity_edit_entry">Edytuj wpis</string> <string name="title_activity_edit_entry">Edytuj wpis</string>
<string name="title_activity_scan_qr">Skanuj kod QR</string> <string name="title_activity_scan_qr">Skanuj kod QR</string>
<string name="title_activity_manage_slots">Zarządzaj slotami klucza</string> <string name="title_activity_manage_slots">Zarządzaj slotami klucza</string>
<string name="title_activity_select_entries">Zaznacz wpisy</string> <string name="title_activity_import_entries">Zaznacz wpisy</string>
<string name="dialog_wipe_entries_title">Wyczyść wpisy</string> <string name="dialog_wipe_entries_title">Wyczyść wpisy</string>
<string name="dialog_wipe_entries_message">Twój sejf zawiera już wpisy. Czy chcesz usunąć te wpisy przed zaimportowaniem tego pliku?\n\n<b>W ten sposób trwale stracisz dostęp do obecnych wpisów w sejfie.</b></string> <string name="dialog_wipe_entries_message">Twój sejf zawiera już wpisy. Czy chcesz usunąć te wpisy przed zaimportowaniem tego pliku?\n\n<b>W ten sposób trwale stracisz dostęp do obecnych wpisów w sejfie.</b></string>
<string name="dialog_wipe_entries_checkbox">Wyczyść zawartość sejfu</string> <string name="dialog_wipe_entries_checkbox">Wyczyść zawartość sejfu</string>

View file

@ -292,7 +292,7 @@
<string name="title_activity_edit_entry">Editar entrada</string> <string name="title_activity_edit_entry">Editar entrada</string>
<string name="title_activity_scan_qr">Escanear QR code</string> <string name="title_activity_scan_qr">Escanear QR code</string>
<string name="title_activity_manage_slots">Gerenciar slots de chaves</string> <string name="title_activity_manage_slots">Gerenciar slots de chaves</string>
<string name="title_activity_select_entries">Selecionar entradas</string> <string name="title_activity_import_entries">Selecionar entradas</string>
<string name="dialog_wipe_entries_title">Limpar entradas</string> <string name="dialog_wipe_entries_title">Limpar entradas</string>
<string name="dialog_wipe_entries_message">O seu cofre já contém entradas. Você quer remover essas entradas antes de importar este arquivo?\n\n<b>Ao fazer isso, você perderá permanentemente o acesso às entradas existentes no cofre.</b></string> <string name="dialog_wipe_entries_message">O seu cofre já contém entradas. Você quer remover essas entradas antes de importar este arquivo?\n\n<b>Ao fazer isso, você perderá permanentemente o acesso às entradas existentes no cofre.</b></string>
<string name="dialog_wipe_entries_checkbox">Limpar conteúdo do cofre</string> <string name="dialog_wipe_entries_checkbox">Limpar conteúdo do cofre</string>

View file

@ -292,7 +292,7 @@
<string name="title_activity_edit_entry">Editar entrada</string> <string name="title_activity_edit_entry">Editar entrada</string>
<string name="title_activity_scan_qr">Escanear um código QR</string> <string name="title_activity_scan_qr">Escanear um código QR</string>
<string name="title_activity_manage_slots">Gerenciar slots de chaves</string> <string name="title_activity_manage_slots">Gerenciar slots de chaves</string>
<string name="title_activity_select_entries">Selecionar entradas</string> <string name="title_activity_import_entries">Selecionar entradas</string>
<string name="dialog_wipe_entries_title">Limpar entradas</string> <string name="dialog_wipe_entries_title">Limpar entradas</string>
<string name="dialog_wipe_entries_message">O seu cofre já contém entradas. Você quer remover essas entradas antes de importar este arquivo?\n\n<b>Ao fazer isso, você perderá permanentemente o acesso às entradas existentes no cofre.</b></string> <string name="dialog_wipe_entries_message">O seu cofre já contém entradas. Você quer remover essas entradas antes de importar este arquivo?\n\n<b>Ao fazer isso, você perderá permanentemente o acesso às entradas existentes no cofre.</b></string>
<string name="dialog_wipe_entries_checkbox">Limpar conteúdo do cofre</string> <string name="dialog_wipe_entries_checkbox">Limpar conteúdo do cofre</string>

View file

@ -296,7 +296,7 @@
<string name="title_activity_edit_entry">Editați intrarea</string> <string name="title_activity_edit_entry">Editați intrarea</string>
<string name="title_activity_scan_qr">Scanează cod QR</string> <string name="title_activity_scan_qr">Scanează cod QR</string>
<string name="title_activity_manage_slots">Gestionare sloturi cheie</string> <string name="title_activity_manage_slots">Gestionare sloturi cheie</string>
<string name="title_activity_select_entries">Selectați intrările</string> <string name="title_activity_import_entries">Selectați intrările</string>
<string name="dialog_wipe_entries_title">Ştergeţi intrările</string> <string name="dialog_wipe_entries_title">Ştergeţi intrările</string>
<string name="dialog_wipe_entries_message">Seiful dvs. conține deja intrări. Doriţi să ştergeţi aceste intrări înainte de a importa acest fişier?\n\n<b>Făcând acest lucru, vei pierde permanent accesul la intrările existente din seif.</b></string> <string name="dialog_wipe_entries_message">Seiful dvs. conține deja intrări. Doriţi să ştergeţi aceste intrări înainte de a importa acest fişier?\n\n<b>Făcând acest lucru, vei pierde permanent accesul la intrările existente din seif.</b></string>
<string name="dialog_wipe_entries_checkbox">Ştergeţi conţinutul seifului</string> <string name="dialog_wipe_entries_checkbox">Ştergeţi conţinutul seifului</string>

View file

@ -304,7 +304,7 @@
<string name="title_activity_edit_entry">Редактировать запись</string> <string name="title_activity_edit_entry">Редактировать запись</string>
<string name="title_activity_scan_qr">Сканировать QR-код</string> <string name="title_activity_scan_qr">Сканировать QR-код</string>
<string name="title_activity_manage_slots">Управление ключевыми слотами</string> <string name="title_activity_manage_slots">Управление ключевыми слотами</string>
<string name="title_activity_select_entries">Выбрать записи</string> <string name="title_activity_import_entries">Выбрать записи</string>
<string name="dialog_wipe_entries_title">Очистить записи</string> <string name="dialog_wipe_entries_title">Очистить записи</string>
<string name="dialog_wipe_entries_message">В вашем хранилище уже есть записи. Вы хотите удалить эти записи перед импортом файла?\n\n<b>При этом вы навсегда потеряете доступ к существующим записям в хранилище.</b></string> <string name="dialog_wipe_entries_message">В вашем хранилище уже есть записи. Вы хотите удалить эти записи перед импортом файла?\n\n<b>При этом вы навсегда потеряете доступ к существующим записям в хранилище.</b></string>
<string name="dialog_wipe_entries_checkbox">Очистить содержимое хранилища</string> <string name="dialog_wipe_entries_checkbox">Очистить содержимое хранилища</string>

View file

@ -286,7 +286,7 @@
<string name="title_activity_edit_entry">编辑项目</string> <string name="title_activity_edit_entry">编辑项目</string>
<string name="title_activity_scan_qr">扫描二维码</string> <string name="title_activity_scan_qr">扫描二维码</string>
<string name="title_activity_manage_slots">管理密钥槽位</string> <string name="title_activity_manage_slots">管理密钥槽位</string>
<string name="title_activity_select_entries">选择项目</string> <string name="title_activity_import_entries">选择项目</string>
<string name="dialog_wipe_entries_title">擦除条目</string> <string name="dialog_wipe_entries_title">擦除条目</string>
<string name="dialog_wipe_entries_message">您的密码库已经包含条目。 您想要在导入此文件之前删除这些条目吗?\n\n<b>这样做,您将永久失去对密码库中现有条目的访问权限。</b></string> <string name="dialog_wipe_entries_message">您的密码库已经包含条目。 您想要在导入此文件之前删除这些条目吗?\n\n<b>这样做,您将永久失去对密码库中现有条目的访问权限。</b></string>
<string name="dialog_wipe_entries_checkbox">擦除密码库内容</string> <string name="dialog_wipe_entries_checkbox">擦除密码库内容</string>

View file

@ -276,7 +276,7 @@
<string name="title_activity_edit_entry">編輯條目</string> <string name="title_activity_edit_entry">編輯條目</string>
<string name="title_activity_scan_qr">掃描QR碼</string> <string name="title_activity_scan_qr">掃描QR碼</string>
<string name="title_activity_manage_slots">管理密鎖槽位</string> <string name="title_activity_manage_slots">管理密鎖槽位</string>
<string name="title_activity_select_entries">選擇條目</string> <string name="title_activity_import_entries">選擇條目</string>
<string name="dialog_wipe_entries_title">清除條目</string> <string name="dialog_wipe_entries_title">清除條目</string>
<string name="dialog_wipe_entries_message">您的數據庫已包含某些條目,您是否想在匯入這個檔案前,先移除這些重複的條目?\n\n<b>如果繼續,您將會失去之前擁有的重複條目。</b></string> <string name="dialog_wipe_entries_message">您的數據庫已包含某些條目,您是否想在匯入這個檔案前,先移除這些重複的條目?\n\n<b>如果繼續,您將會失去之前擁有的重複條目。</b></string>
<string name="dialog_wipe_entries_checkbox">清除條目的內容</string> <string name="dialog_wipe_entries_checkbox">清除條目的內容</string>

View file

@ -344,7 +344,7 @@
<string name="title_activity_edit_entry">Edit entry</string> <string name="title_activity_edit_entry">Edit entry</string>
<string name="title_activity_scan_qr">Scan a QR code</string> <string name="title_activity_scan_qr">Scan a QR code</string>
<string name="title_activity_manage_slots">Manage key slots</string> <string name="title_activity_manage_slots">Manage key slots</string>
<string name="title_activity_select_entries">Select entries</string> <string name="title_activity_import_entries">Import entries</string>
<string name="dialog_wipe_entries_title">Wipe entries</string> <string name="dialog_wipe_entries_title">Wipe entries</string>
<string name="dialog_wipe_entries_message">Your vault already contains entries. Do you want to remove these entries before importing this file?\n\n<b>In doing so, you will permanently lose access to the existing entries in the vault.</b></string> <string name="dialog_wipe_entries_message">Your vault already contains entries. Do you want to remove these entries before importing this file?\n\n<b>In doing so, you will permanently lose access to the existing entries in the vault.</b></string>
<string name="dialog_wipe_entries_checkbox">Wipe vault contents</string> <string name="dialog_wipe_entries_checkbox">Wipe vault contents</string>