mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 05:52:52 +00:00
Move import logic to separate activity to fix a couple of issues
Fixes #456. Fixes #670.
This commit is contained in:
parent
7be1a74cfd
commit
ae71febf10
39 changed files with 415 additions and 462 deletions
|
@ -30,8 +30,8 @@
|
|||
android:name=".ui.AboutActivity"
|
||||
android:label="@string/title_activity_about" />
|
||||
<activity
|
||||
android:name=".ui.SelectEntriesActivity"
|
||||
android:label="@string/title_activity_select_entries" />
|
||||
android:name=".ui.ImportEntriesActivity"
|
||||
android:label="@string/title_activity_import_entries" />
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:label="${title}">
|
||||
|
|
|
@ -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<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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ public class AndOtpImporter extends DatabaseImporter {
|
|||
} catch (DatabaseImporterException e) {
|
||||
listener.onError(e);
|
||||
}
|
||||
});
|
||||
}, dialog1 -> listener.onCanceled());
|
||||
})
|
||||
.create());
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ public class AuthenticatorPlusImporter extends DatabaseImporter {
|
|||
} catch (DatabaseImporterException e) {
|
||||
listener.onError(e);
|
||||
}
|
||||
});
|
||||
}, dialog1 -> listener.onCanceled());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,7 +204,7 @@ public class AuthyImporter extends DatabaseImporter {
|
|||
} catch (DatabaseImporterException e) {
|
||||
listener.onError(e);
|
||||
}
|
||||
});
|
||||
}, dialog1 -> listener.onCanceled());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<? extends DatabaseImporter> _importerType;
|
||||
private AegisImporter.State _importerState;
|
||||
private UUIDMap<VaultEntry> _importerEntries;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
super.onCreatePreferences(savedInstanceState, rootKey);
|
||||
addPreferencesFromResource(R.xml.preferences_import_export);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
_importerType = (Class<? extends DatabaseImporter>) 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<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 startImportEntriesActivity(Class<? extends DatabaseImporter> 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<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) {
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ImportEntryHolder> {
|
||||
private List<ImportEntry> _entries;
|
||||
|
||||
|
@ -47,7 +47,7 @@ public class ImportEntriesAdapter extends RecyclerView.Adapter<ImportEntryHolder
|
|||
|
||||
@Override
|
||||
public void onViewRecycled(@NonNull ImportEntryHolder holder) {
|
||||
holder.getEntry().setOnCheckedChangedListener(null);
|
||||
holder.getData().setOnCheckedChangedListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,7 +15,7 @@ public class ImportEntryHolder extends RecyclerView.ViewHolder implements Import
|
|||
private TextView _accountName;
|
||||
private CheckBox _checkbox;
|
||||
|
||||
private ImportEntry _entry;
|
||||
private ImportEntry _data;
|
||||
|
||||
public ImportEntryHolder(final View view) {
|
||||
super(view);
|
||||
|
@ -23,20 +23,20 @@ public class ImportEntryHolder extends RecyclerView.ViewHolder implements Import
|
|||
_issuer = view.findViewById(R.id.profile_issuer);
|
||||
_accountName = view.findViewById(R.id.profile_account_name);
|
||||
_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) {
|
||||
_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
|
||||
|
|
|
@ -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">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
|
@ -2,75 +2,44 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
android:orientation="horizontal">
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/cardBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_weight="1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_alignParentBottom="false"
|
||||
android:layout_alignParentTop="false"
|
||||
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">
|
||||
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:layout_gravity="center_vertical">
|
||||
<TextView
|
||||
android:id="@+id/profile_issuer"
|
||||
android:layout_width="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:includeFontPadding="false"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?attr/primaryText"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="normal|bold"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="@string/issuer" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/profile_account_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/profile_issuer"
|
||||
android:layout_marginTop="2dp"
|
||||
android:ellipsize="end"
|
||||
android:includeFontPadding="false"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/extra_info_text"
|
||||
android:textSize="14sp"
|
||||
tools:text="AccountName" />
|
||||
|
||||
</LinearLayout>
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox_import_entry"
|
||||
android:clickable="false"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginEnd="6dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
|
||||
android:layout_gravity="center_vertical"/>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -296,7 +296,7 @@
|
|||
<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_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_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>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<string name="title_activity_edit_entry">Eintrag bearbeiten</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_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_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>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<string name="title_activity_edit_entry">Επεξεργασία καταχώρησης</string>
|
||||
<string name="title_activity_scan_qr">Σάρωση κωδικού QR</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_message">Η κρύπτη σας περιέχει ήδη καταχωρήσεις. Θέλετε να καταργήσετε αυτές τις καταχωρήσεις πριν από την εισαγωγή αυτού του αρχείου;\n\n <b> Με αυτόν τον τρόπο, θα χάσετε μόνιμα την πρόσβαση στις υπάρχουσες καταχωρήσεις στην κρύπτη. </b></string>
|
||||
<string name="dialog_wipe_entries_checkbox">Διαγραφή περιεχομένων κρύπτης</string>
|
||||
|
|
|
@ -263,7 +263,7 @@
|
|||
<string name="title_activity_about">Acerca de</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_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_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>
|
||||
|
|
|
@ -286,7 +286,7 @@
|
|||
<string name="title_activity_edit_entry">Editatu sarrera</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_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_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>
|
||||
|
|
|
@ -288,7 +288,7 @@
|
|||
<string name="title_activity_edit_entry">ویرایش آیتم</string>
|
||||
<string name="title_activity_scan_qr">اسکن بارکد دوبعدی</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_message">مخزن شما در حال حاضر شامل آیتم میباشد. آیا مطمئن به حذف این آیتمها قبل از وارد کردن میباشید؟\n\n<b>بعد از این کار شما به هیچ وجه دسترسی به آیتم های قبلی نخواهید داشت.</b></string>
|
||||
<string name="dialog_wipe_entries_checkbox">پاک کردن اطلاعات مخزن</string>
|
||||
|
|
|
@ -288,7 +288,7 @@
|
|||
<string name="title_activity_edit_entry">Muokkaa kohdetta</string>
|
||||
<string name="title_activity_scan_qr">Skannaa QR-koodi</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_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>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<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_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_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>
|
||||
|
|
|
@ -283,7 +283,7 @@
|
|||
<string name="title_activity_edit_entry">प्रविष्टि संपादित करें</string>
|
||||
<string name="title_activity_scan_qr">क्यूआर कोड स्कैन करें</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_message">आपके वॉल्ट में पहले से ही प्रविष्टियाँ हैं। क्या आप इस फ़ाइल को आयात करने से पहले इन प्रविष्टियों को निकालना चाहते हैं?\n\n<b> ऐसा करने पर, आप स्थायी रूप से वॉल्ट में मौजूदा प्रविष्टियों तक पहुंच खो देंगे। </b></string>
|
||||
<string name="dialog_wipe_entries_checkbox">वॉल्ट सामग्री साफ करें</string>
|
||||
|
|
|
@ -284,7 +284,7 @@
|
|||
<string name="title_activity_edit_entry">Sunting catatan</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_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_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>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<string name="title_activity_edit_entry">Modifica voce</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_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_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>
|
||||
|
|
|
@ -284,7 +284,7 @@
|
|||
<string name="title_activity_edit_entry">エントリーを編集</string>
|
||||
<string name="title_activity_scan_qr">QRコードをスキャン</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_checkbox">保管庫の内容を消去</string>
|
||||
<string name="panic_trigger_ignore_toast">イージスはパニックトリガーを受信しましたが、設定は無効になっています。</string>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<string name="title_activity_edit_entry">Item bewerken</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_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_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>
|
||||
|
|
|
@ -304,7 +304,7 @@
|
|||
<string name="title_activity_edit_entry">Edytuj wpis</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_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_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>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<string name="title_activity_edit_entry">Editar entrada</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_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_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>
|
||||
|
|
|
@ -292,7 +292,7 @@
|
|||
<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_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_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>
|
||||
|
|
|
@ -296,7 +296,7 @@
|
|||
<string name="title_activity_edit_entry">Editați intrarea</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_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_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>
|
||||
|
|
|
@ -304,7 +304,7 @@
|
|||
<string name="title_activity_edit_entry">Редактировать запись</string>
|
||||
<string name="title_activity_scan_qr">Сканировать QR-код</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_message">В вашем хранилище уже есть записи. Вы хотите удалить эти записи перед импортом файла?\n\n<b>При этом вы навсегда потеряете доступ к существующим записям в хранилище.</b></string>
|
||||
<string name="dialog_wipe_entries_checkbox">Очистить содержимое хранилища</string>
|
||||
|
|
|
@ -286,7 +286,7 @@
|
|||
<string name="title_activity_edit_entry">编辑项目</string>
|
||||
<string name="title_activity_scan_qr">扫描二维码</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_message">您的密码库已经包含条目。 您想要在导入此文件之前删除这些条目吗?\n\n<b>这样做,您将永久失去对密码库中现有条目的访问权限。</b></string>
|
||||
<string name="dialog_wipe_entries_checkbox">擦除密码库内容</string>
|
||||
|
|
|
@ -276,7 +276,7 @@
|
|||
<string name="title_activity_edit_entry">編輯條目</string>
|
||||
<string name="title_activity_scan_qr">掃描QR碼</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_message">您的數據庫已包含某些條目,您是否想在匯入這個檔案前,先移除這些重複的條目?\n\n<b>如果繼續,您將會失去之前擁有的重複條目。</b></string>
|
||||
<string name="dialog_wipe_entries_checkbox">清除條目的內容</string>
|
||||
|
|
|
@ -344,7 +344,7 @@
|
|||
<string name="title_activity_edit_entry">Edit entry</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_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_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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue