mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-25 08:16:07 +00:00
Display some help text in the importer selection dialog
This commit is contained in:
parent
b21b6dc750
commit
69196ddbcc
6 changed files with 148 additions and 70 deletions
|
@ -3,6 +3,9 @@ package com.beemdevelopment.aegis.importers;
|
|||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
|
@ -13,40 +16,29 @@ import java.io.InputStream;
|
|||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class DatabaseImporter {
|
||||
private Context _context;
|
||||
|
||||
private static Map<String, Class<? extends DatabaseImporter>> _importers;
|
||||
private static Map<String, Class<? extends DatabaseImporter>> _appImporters;
|
||||
private static List<Definition> _importers;
|
||||
|
||||
static {
|
||||
// note: keep these lists sorted alphabetically
|
||||
_importers = new LinkedHashMap<>();
|
||||
_importers.put("Aegis", AegisImporter.class);
|
||||
_importers.put("Authenticator Plus", AuthenticatorPlusImporter.class);
|
||||
_importers.put("Authy", AuthyImporter.class);
|
||||
_importers.put("andOTP", AndOtpImporter.class);
|
||||
_importers.put("FreeOTP", FreeOtpImporter.class);
|
||||
_importers.put("FreeOTP+", FreeOtpPlusImporter.class);
|
||||
_importers.put("Google Authenticator", GoogleAuthImporter.class);
|
||||
_importers.put("Microsoft Authenticator", MicrosoftAuthImporter.class);
|
||||
_importers.put("Plain text", GoogleAuthUriImporter.class);
|
||||
_importers.put("Steam", SteamImporter.class);
|
||||
_importers.put("TOTP Authenticator", TotpAuthenticatorImporter.class);
|
||||
_importers.put("WinAuth", WinAuthImporter.class);
|
||||
|
||||
_appImporters = new LinkedHashMap<>();
|
||||
_appImporters.put("Authy", AuthyImporter.class);
|
||||
_appImporters.put("FreeOTP", FreeOtpImporter.class);
|
||||
_appImporters.put("FreeOTP+", FreeOtpPlusImporter.class);
|
||||
_appImporters.put("Google Authenticator", GoogleAuthImporter.class);
|
||||
_appImporters.put("Microsoft Authenticator", MicrosoftAuthImporter.class);
|
||||
_appImporters.put("Steam", SteamImporter.class);
|
||||
_appImporters.put("TOTP Authenticator", TotpAuthenticatorImporter.class);
|
||||
_importers = new ArrayList<>();
|
||||
_importers.add(new Definition("Aegis", AegisImporter.class, R.string.importer_help_aegis, false));
|
||||
_importers.add(new Definition("Authenticator Plus", AuthenticatorPlusImporter.class, R.string.importer_help_authenticator_plus, false));
|
||||
_importers.add(new Definition("Authy", AuthyImporter.class, R.string.importer_help_authy, true));
|
||||
_importers.add(new Definition("andOTP", AndOtpImporter.class, R.string.importer_help_andotp, false));
|
||||
_importers.add(new Definition("FreeOTP", FreeOtpImporter.class, R.string.importer_help_freeotp, true));
|
||||
_importers.add(new Definition("FreeOTP+", FreeOtpPlusImporter.class, R.string.importer_help_freeotp_plus, true));
|
||||
_importers.add(new Definition("Google Authenticator", GoogleAuthImporter.class, R.string.importer_help_google_authenticator, true));
|
||||
_importers.add(new Definition("Microsoft Authenticator", MicrosoftAuthImporter.class, R.string.importer_help_microsoft_authenticator, true));
|
||||
_importers.add(new Definition("Plain text", GoogleAuthUriImporter.class, R.string.importer_help_plain_text, false));
|
||||
_importers.add(new Definition("Steam", SteamImporter.class, R.string.importer_help_steam, true));
|
||||
_importers.add(new Definition("TOTP Authenticator", TotpAuthenticatorImporter.class, R.string.importer_help_totp_authenticator, true));
|
||||
_importers.add(new Definition("WinAuth", WinAuthImporter.class, R.string.importer_help_winauth, false));
|
||||
}
|
||||
|
||||
public DatabaseImporter(Context context) {
|
||||
|
@ -88,12 +80,42 @@ public abstract class DatabaseImporter {
|
|||
}
|
||||
}
|
||||
|
||||
public static Map<String, Class<? extends DatabaseImporter>> getImporters() {
|
||||
return Collections.unmodifiableMap(_importers);
|
||||
public static List<Definition> getImporters(boolean isDirect) {
|
||||
if (isDirect) {
|
||||
return Collections.unmodifiableList(_importers.stream().filter(Definition::supportsDirect).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(_importers);
|
||||
}
|
||||
|
||||
public static Map<String, Class<? extends DatabaseImporter>> getAppImporters() {
|
||||
return Collections.unmodifiableMap(_appImporters);
|
||||
public static class Definition {
|
||||
private final String _name;
|
||||
private final Class<? extends DatabaseImporter> _type;
|
||||
private final @StringRes int _help;
|
||||
private final boolean _supportsDirect;
|
||||
|
||||
public Definition(String name, Class<? extends DatabaseImporter> type, @StringRes int help, boolean supportsDirect) {
|
||||
_name = name;
|
||||
_type = type;
|
||||
_help = help;
|
||||
_supportsDirect = supportsDirect;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return _name;
|
||||
}
|
||||
|
||||
public Class<? extends DatabaseImporter> getType() {
|
||||
return _type;
|
||||
}
|
||||
|
||||
public @StringRes int getHelp() {
|
||||
return _help;
|
||||
}
|
||||
|
||||
public boolean supportsDirect() {
|
||||
return _supportsDirect;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class State {
|
||||
|
|
|
@ -15,10 +15,12 @@ import android.text.method.PasswordTransformationMethod;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
@ -31,6 +33,7 @@ import com.beemdevelopment.aegis.Preferences;
|
|||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.PasswordStrengthHelper;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||
import com.beemdevelopment.aegis.ui.tasks.KeyDerivationTask;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
|
@ -39,6 +42,7 @@ import com.google.android.material.textfield.TextInputLayout;
|
|||
import com.nulabinc.zxcvbn.Strength;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
@ -383,6 +387,37 @@ public class Dialogs {
|
|||
.create());
|
||||
}
|
||||
|
||||
public static void showImportersDialog(Context context, boolean isDirect, ImporterListener listener) {
|
||||
List<DatabaseImporter.Definition> importers = DatabaseImporter.getImporters(isDirect);
|
||||
String[] names = importers.stream().map(DatabaseImporter.Definition::getName).toArray(String[]::new);
|
||||
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.dialog_importers, null);
|
||||
TextView helpText = view.findViewById(R.id.text_importer_help);
|
||||
setImporterHelpText(helpText, importers.get(0), isDirect);
|
||||
ListView listView = view.findViewById(R.id.list_importers);
|
||||
listView.setAdapter(new ArrayAdapter<>(context, R.layout.card_importer, names));
|
||||
listView.setItemChecked(0, true);
|
||||
listView.setOnItemClickListener((parent, view1, position, id) -> {
|
||||
setImporterHelpText(helpText, importers.get(position), isDirect);
|
||||
});
|
||||
|
||||
Dialogs.showSecureDialog(new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.choose_application)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok, (dialog1, which) -> {
|
||||
listener.onImporterSelectionResult(importers.get(listView.getCheckedItemPosition()));
|
||||
})
|
||||
.create());
|
||||
}
|
||||
|
||||
private static void setImporterHelpText(TextView view, DatabaseImporter.Definition definition, boolean isDirect) {
|
||||
if (isDirect) {
|
||||
view.setText(view.getResources().getString(R.string.importer_help_direct, definition.getName()));
|
||||
} else {
|
||||
view.setText(definition.getHelp());
|
||||
}
|
||||
}
|
||||
|
||||
public interface CheckboxInputListener {
|
||||
void onCheckboxInputResult(boolean checkbox);
|
||||
}
|
||||
|
@ -399,4 +434,8 @@ public class Dialogs {
|
|||
void onSlotResult(Slot slot, Cipher cipher);
|
||||
void onException(Exception e);
|
||||
}
|
||||
|
||||
public interface ImporterListener {
|
||||
void onImporterSelectionResult(DatabaseImporter.Definition definition);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,8 +57,6 @@ import java.io.OutputStream;
|
|||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
@ -175,7 +173,13 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
importPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
startImport();
|
||||
Dialogs.showImportersDialog(getContext(), false, definition -> {
|
||||
_importerType = definition.getType();
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, CODE_IMPORT);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
@ -184,7 +188,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
importAppPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
onImportApp();
|
||||
Dialogs.showImportersDialog(getContext(), true, definition -> {
|
||||
DatabaseImporter importer = DatabaseImporter.create(getContext(), definition.getType());
|
||||
importApp(importer);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
@ -540,42 +547,6 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
getActivity().setResult(Activity.RESULT_OK, _result);
|
||||
}
|
||||
|
||||
private void startImport() {
|
||||
Map<String, Class<? extends DatabaseImporter>> importers = DatabaseImporter.getImporters();
|
||||
String[] names = importers.keySet().toArray(new String[0]);
|
||||
|
||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.choose_application)
|
||||
.setSingleChoiceItems(names, 0, null)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||
_importerType = importers.get(names[i]);
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
startActivityForResult(intent, CODE_IMPORT);
|
||||
}
|
||||
})
|
||||
.create());
|
||||
}
|
||||
|
||||
private void onImportApp() {
|
||||
Map<String, Class<? extends DatabaseImporter>> importers = DatabaseImporter.getAppImporters();
|
||||
String[] names = importers.keySet().toArray(new String[0]);
|
||||
|
||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||
.setTitle(R.string.choose_application)
|
||||
.setSingleChoiceItems(names, 0, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||
Class<? extends DatabaseImporter> importerType = Objects.requireNonNull(importers.get(names[i]));
|
||||
DatabaseImporter importer = DatabaseImporter.create(getContext(), importerType);
|
||||
importApp(importer);
|
||||
})
|
||||
.create());
|
||||
}
|
||||
|
||||
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()
|
||||
|
|
7
app/src/main/res/layout/card_importer.xml
Normal file
7
app/src/main/res/layout/card_importer.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:checkMark="?android:attr/listChoiceIndicatorSingle">
|
||||
</CheckedTextView>
|
24
app/src/main/res/layout/dialog_importers.xml
Normal file
24
app/src/main/res/layout/dialog_importers.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="25dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingEnd="25dp"
|
||||
android:paddingTop="10dp">
|
||||
<ListView
|
||||
android:id="@+id/list_importers"
|
||||
android:layout_weight="1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@null" />
|
||||
<TextView
|
||||
android:id="@+id/text_importer_help"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp" />
|
||||
</LinearLayout>
|
|
@ -329,4 +329,19 @@
|
|||
<string name="panic_trigger_ignore_toast">Aegis received panic trigger but setting is disabled, ignoring</string>
|
||||
<string name="pref_panic_trigger_title">Delete vault on panic trigger</string>
|
||||
<string name="pref_panic_trigger_summary">Delete vault when a panic trigger is received from Ripple</string>
|
||||
|
||||
<string name="importer_help_aegis">Supply an Aegis export/backup file.</string>
|
||||
<string name="importer_help_authenticator_plus">Supply an Authenticator Plus export file obtained through <b>Settings -> Backup & Restore -> Export as Text and HTML</b>.</string>
|
||||
<string name="importer_help_authy">Supply a copy of <b>/data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml</b>, located in the internal storage directory of Authy.</string>
|
||||
<string name="importer_help_andotp">Supply an andOTP export/backup file.</string>
|
||||
<string name="importer_help_freeotp">Supply a copy of <b>/data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml</b>, located in the internal storage directory of FreeOTP.</string>
|
||||
<string name="importer_help_freeotp_plus">Supply a FreeOTP+ export file.</string>
|
||||
<string name="importer_help_google_authenticator">Supply a copy of <b>/data/data/com.google.android.apps.authenticator2/databases/databases</b>, located in the internal storage directory of Google Authenticator.</string>
|
||||
<string name="importer_help_microsoft_authenticator">Supply a copy of <b>/data/data/com.azure.authenticator/databases/PhoneFactor</b>, located in the internal storage directory of Microsoft Authenticator.</string>
|
||||
<string name="importer_help_plain_text">Supply a plain text file with a Google Authenticator URI on each line.</string>
|
||||
<string name="importer_help_steam">Supply a copy of <b>/data/data/com.valvesoftware.android.steam.community/files/Steamguard-*.json</b>, located in the internal storage directory of Steam.</string>
|
||||
<string name="importer_help_totp_authenticator">Supply a TOTP Authenticator export file.</string>
|
||||
<string name="importer_help_winauth">Supply a WinAuth export file.</string>
|
||||
|
||||
<string name="importer_help_direct">Import entries directly from %s. This requires the app to be installed on this device and for root access to be granted to Aegis.</string>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Reference in a new issue