diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AegisFileImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AegisFileImporter.java deleted file mode 100644 index 7adf8486..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AegisFileImporter.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.beemdevelopment.aegis.importers; - -import android.content.Context; - -import com.beemdevelopment.aegis.db.DatabaseEntry; -import com.beemdevelopment.aegis.db.DatabaseFile; -import com.beemdevelopment.aegis.db.DatabaseFileCredentials; -import com.beemdevelopment.aegis.db.DatabaseFileException; -import com.beemdevelopment.aegis.encoding.Base64Exception; -import com.beemdevelopment.aegis.otp.OtpInfoException; -import com.beemdevelopment.aegis.util.ByteInputStream; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -public class AegisFileImporter extends DatabaseFileImporter { - private DatabaseFileCredentials _creds; - private DatabaseFile _file; - - public AegisFileImporter(Context context, ByteInputStream stream) { - super(context, stream); - } - - @Override - public void parse() throws DatabaseImporterException { - try { - byte[] bytes = _stream.getBytes(); - _file = DatabaseFile.fromBytes(bytes); - } catch (DatabaseFileException e) { - throw new DatabaseImporterException(e); - } - } - - @Override - public DatabaseImporterResult convert() throws DatabaseImporterException { - DatabaseImporterResult result = new DatabaseImporterResult(); - - try { - JSONObject obj; - if (_file.isEncrypted() && _creds != null) { - obj = _file.getContent(_creds); - } else { - obj = _file.getContent(); - } - - JSONArray array = obj.getJSONArray("entries"); - for (int i = 0; i < array.length(); i++) { - JSONObject entryObj = array.getJSONObject(i); - try { - DatabaseEntry entry = convertEntry(entryObj); - result.addEntry(entry); - } catch (DatabaseImporterEntryException e) { - result.addError(e); - } - } - } catch (JSONException | DatabaseFileException e) { - throw new DatabaseImporterException(e); - } - - return result; - } - - private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException { - try { - return DatabaseEntry.fromJson(obj); - } catch (JSONException | OtpInfoException | Base64Exception e) { - throw new DatabaseImporterEntryException(e, obj.toString()); - } - } - - @Override - public boolean isEncrypted() { - return _file.isEncrypted(); - } - - public void setCredentials(DatabaseFileCredentials creds) { - _creds = creds; - } - - public DatabaseFile getFile() { - return _file; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java new file mode 100644 index 00000000..31e467e6 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java @@ -0,0 +1,109 @@ +package com.beemdevelopment.aegis.importers; + +import android.content.Context; + +import com.beemdevelopment.aegis.db.DatabaseEntry; +import com.beemdevelopment.aegis.db.DatabaseFile; +import com.beemdevelopment.aegis.db.DatabaseFileCredentials; +import com.beemdevelopment.aegis.db.DatabaseFileException; +import com.beemdevelopment.aegis.db.slots.SlotList; +import com.beemdevelopment.aegis.encoding.Base64Exception; +import com.beemdevelopment.aegis.otp.OtpInfoException; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; + +public class AegisImporter extends DatabaseImporter { + + public AegisImporter(Context context) { + super(context); + } + + @Override + protected String getAppPkgName() { + throw new UnsupportedOperationException(); + } + + @Override + protected String getAppSubPath() { + throw new UnsupportedOperationException(); + } + + public State read(FileReader reader) throws DatabaseImporterException { + try { + byte[] bytes = reader.readAll(); + DatabaseFile file = DatabaseFile.fromBytes(bytes); + if (file.isEncrypted()) { + return new EncryptedState(file); + } + return new DecryptedState(file.getContent()); + } catch (DatabaseFileException | IOException e) { + throw new DatabaseImporterException(e); + } + } + + public static class EncryptedState extends State { + private DatabaseFile _file; + + private EncryptedState(DatabaseFile file) { + super(true); + _file = file; + } + + public SlotList getSlots() { + return _file.getHeader().getSlots(); + } + + public State decrypt(DatabaseFileCredentials creds) throws DatabaseFileException { + JSONObject obj = _file.getContent(creds); + return new DecryptedState(obj); + } + + @Override + public void decrypt(Context context, DecryptListener listener) { + + } + } + + public static class DecryptedState extends State { + private JSONObject _obj; + + private DecryptedState(JSONObject obj) { + super(false); + _obj = obj; + } + + @Override + public Result convert() throws DatabaseImporterException { + Result result = new Result(); + + try { + JSONArray array = _obj.getJSONArray("entries"); + for (int i = 0; i < array.length(); i++) { + JSONObject entryObj = array.getJSONObject(i); + try { + DatabaseEntry entry = convertEntry(entryObj); + result.addEntry(entry); + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } + } + } catch (JSONException e) { + throw new DatabaseImporterException(e); + } + + return result; + } + + private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException { + try { + return DatabaseEntry.fromJson(obj); + } catch (JSONException | OtpInfoException | Base64Exception e) { + throw new DatabaseImporterEntryException(e, obj.toString()); + } + } + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpFileImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpFileImporter.java deleted file mode 100644 index 5ebb2012..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpFileImporter.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.beemdevelopment.aegis.importers; - -import android.content.Context; - -import com.beemdevelopment.aegis.db.DatabaseEntry; -import com.beemdevelopment.aegis.encoding.Base32; -import com.beemdevelopment.aegis.encoding.Base32Exception; -import com.beemdevelopment.aegis.otp.HotpInfo; -import com.beemdevelopment.aegis.otp.OtpInfo; -import com.beemdevelopment.aegis.otp.OtpInfoException; -import com.beemdevelopment.aegis.otp.SteamInfo; -import com.beemdevelopment.aegis.otp.TotpInfo; -import com.beemdevelopment.aegis.util.ByteInputStream; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.nio.charset.StandardCharsets; - -public class AndOtpFileImporter extends DatabaseFileImporter { - private JSONArray _obj; - - public AndOtpFileImporter(Context context, ByteInputStream stream) { - super(context, stream); - } - - @Override - public void parse() throws DatabaseImporterException { - try { - _obj = new JSONArray(new String(_stream.getBytes(), StandardCharsets.UTF_8)); - } catch (JSONException e) { - throw new DatabaseImporterException(e); - } - } - - @Override - public DatabaseImporterResult convert() throws DatabaseImporterException { - DatabaseImporterResult result = new DatabaseImporterResult(); - - for (int i = 0; i < _obj.length(); i++) { - try { - JSONObject obj = _obj.getJSONObject(i); - DatabaseEntry entry = convertEntry(obj); - result.addEntry(entry); - } catch (JSONException e) { - throw new DatabaseImporterException(e); - } catch (DatabaseImporterEntryException e) { - result.addError(e); - } - } - - return result; - } - - private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException { - try { - String type = obj.getString("type").toLowerCase(); - String algo = obj.getString("algorithm"); - int digits = obj.getInt("digits"); - byte[] secret = Base32.decode(obj.getString("secret").toCharArray()); - - OtpInfo info; - switch (type) { - case "hotp": - info = new HotpInfo(secret, algo, digits, obj.getLong("counter")); - break; - case "totp": - info = new TotpInfo(secret, algo, digits, obj.getInt("period")); - break; - case "steam": - info = new SteamInfo(secret, algo, digits, obj.optInt("period", 30)); - break; - default: - throw new DatabaseImporterException("unsupported otp type: " + type); - } - - String name; - String issuer = ""; - - String[] parts = obj.getString("label").split(" - "); - if (parts.length > 1) { - issuer = parts[0]; - name = parts[1]; - } else { - name = parts[0]; - } - - return new DatabaseEntry(info, name, issuer); - } catch (DatabaseImporterException | Base32Exception | OtpInfoException | JSONException e) { - throw new DatabaseImporterEntryException(e, obj.toString()); - } - } - - @Override - public boolean isEncrypted() { - return false; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java new file mode 100644 index 00000000..0999d3c5 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AndOtpImporter.java @@ -0,0 +1,114 @@ +package com.beemdevelopment.aegis.importers; + +import android.content.Context; + +import com.beemdevelopment.aegis.db.DatabaseEntry; +import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.Base32Exception; +import com.beemdevelopment.aegis.otp.HotpInfo; +import com.beemdevelopment.aegis.otp.OtpInfo; +import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.SteamInfo; +import com.beemdevelopment.aegis.otp.TotpInfo; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class AndOtpImporter extends DatabaseImporter { + + public AndOtpImporter(Context context) { + super(context); + } + + @Override + protected String getAppPkgName() { + throw new UnsupportedOperationException(); + } + + @Override + protected String getAppSubPath() { + throw new UnsupportedOperationException(); + } + + public State read(FileReader reader) throws DatabaseImporterException { + byte[] bytes; + try { + bytes = reader.readAll(); + JSONArray array = new JSONArray(new String(bytes, StandardCharsets.UTF_8)); + return new State(array); + } catch (IOException | JSONException e) { + throw new DatabaseImporterException(e); + } + } + + public static class State extends DatabaseImporter.State { + private JSONArray _obj; + + private State(JSONArray obj) { + super(false); + _obj = obj; + } + + @Override + public Result convert() throws DatabaseImporterException { + Result result = new Result(); + + for (int i = 0; i < _obj.length(); i++) { + try { + JSONObject obj = _obj.getJSONObject(i); + DatabaseEntry entry = convertEntry(obj); + result.addEntry(entry); + } catch (JSONException e) { + throw new DatabaseImporterException(e); + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } + } + + return result; + } + + private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException { + try { + String type = obj.getString("type").toLowerCase(); + String algo = obj.getString("algorithm"); + int digits = obj.getInt("digits"); + byte[] secret = Base32.decode(obj.getString("secret").toCharArray()); + + OtpInfo info; + switch (type) { + case "hotp": + info = new HotpInfo(secret, algo, digits, obj.getLong("counter")); + break; + case "totp": + info = new TotpInfo(secret, algo, digits, obj.getInt("period")); + break; + case "steam": + info = new SteamInfo(secret, algo, digits, obj.optInt("period", 30)); + break; + default: + throw new DatabaseImporterException("unsupported otp type: " + type); + } + + String name; + String issuer = ""; + + String[] parts = obj.getString("label").split(" - "); + if (parts.length > 1) { + issuer = parts[0]; + name = parts[1]; + } else { + name = parts[0]; + } + + return new DatabaseEntry(info, name, issuer); + } catch (DatabaseImporterException | Base32Exception | OtpInfoException | JSONException e) { + throw new DatabaseImporterEntryException(e, obj.toString()); + } + } + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseAppImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseAppImporter.java deleted file mode 100644 index 514bdd23..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseAppImporter.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.beemdevelopment.aegis.importers; - -import android.content.Context; -import android.content.pm.PackageManager; - -import com.topjohnwu.superuser.io.SuFile; - -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -public abstract class DatabaseAppImporter implements DatabaseImporter { - private static Map> _importers; - static { - // note: keep this list sorted alphabetically - LinkedHashMap> importers = new LinkedHashMap<>(); - importers.put("Google Authenticator", GoogleAuthAppImporter.class); - importers.put("Steam", SteamAppImporter.class); - _importers = Collections.unmodifiableMap(importers); - } - - private SuFile _path; - private Context _context; - - protected DatabaseAppImporter(Context context, String pkgName, String subPath) throws DatabaseImporterException { - _context = context; - - try { - PackageManager man = context.getPackageManager(); - _path = new SuFile(man.getApplicationInfo(pkgName, 0).dataDir, subPath); - } catch (PackageManager.NameNotFoundException e) { - throw new DatabaseImporterException(e); - } - } - - protected SuFile getPath() { - return _path; - } - - public abstract void parse() throws DatabaseImporterException; - - public abstract DatabaseImporterResult convert() throws DatabaseImporterException; - - public abstract boolean isEncrypted(); - - public Context getContext() { - return _context; - } - - public static DatabaseAppImporter create(Context context, Class type) { - try { - return type.getConstructor(Context.class).newInstance(context); - } catch (IllegalAccessException | InstantiationException - | NoSuchMethodException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - public static Map> getImporters() { - return _importers; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseFileImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseFileImporter.java deleted file mode 100644 index cec937f9..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseFileImporter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.beemdevelopment.aegis.importers; - -import android.content.Context; - -import com.beemdevelopment.aegis.util.ByteInputStream; - -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -public abstract class DatabaseFileImporter implements DatabaseImporter { - private static Map> _importers; - static { - // note: keep this list sorted alphabetically - LinkedHashMap> importers = new LinkedHashMap<>(); - importers.put("Aegis", AegisFileImporter.class); - importers.put("andOTP", AndOtpFileImporter.class); - importers.put("FreeOTP", FreeOtpFileImporter.class); - _importers = Collections.unmodifiableMap(importers); - } - - private Context _context; - protected ByteInputStream _stream; - - protected DatabaseFileImporter(Context context, ByteInputStream stream) { - _context = context; - _stream = stream; - } - - public abstract void parse() throws DatabaseImporterException; - - public abstract DatabaseImporterResult convert() throws DatabaseImporterException; - - public abstract boolean isEncrypted(); - - public Context getContext() { - return _context; - } - - public static DatabaseFileImporter create(Context context, ByteInputStream stream, Class type) { - try { - return type.getConstructor(Context.class, ByteInputStream.class).newInstance(context, stream); - } catch (IllegalAccessException | InstantiationException - | NoSuchMethodException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - - public static Map> getImporters() { - return _importers; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java index 4b408a89..db54ede2 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -1,10 +1,178 @@ package com.beemdevelopment.aegis.importers; import android.content.Context; +import android.content.pm.PackageManager; +import android.net.Uri; -public interface DatabaseImporter { - void parse() throws DatabaseImporterException; - DatabaseImporterResult convert() throws DatabaseImporterException; - boolean isEncrypted(); - Context getContext(); +import com.beemdevelopment.aegis.db.DatabaseEntry; +import com.beemdevelopment.aegis.util.ByteInputStream; +import com.topjohnwu.superuser.io.SuFile; +import com.topjohnwu.superuser.io.SuFileInputStream; + +import java.io.Closeable; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +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; + +public abstract class DatabaseImporter { + private Context _context; + + private static Map> _importers; + private static Map> _appImporters; + + static { + // note: keep these lists sorted alphabetically + _importers = new LinkedHashMap<>(); + _importers.put("Aegis", AegisImporter.class); + _importers.put("andOTP", AndOtpImporter.class); + _importers.put("FreeOTP", FreeOtpImporter.class); + _importers.put("Google Authenticator", GoogleAuthImporter.class); + _importers.put("Steam", SteamImporter.class); + + _appImporters = new LinkedHashMap<>(); + _appImporters.put("FreeOTP", FreeOtpImporter.class); + _appImporters.put("Google Authenticator", GoogleAuthImporter.class); + _appImporters.put("Steam", SteamImporter.class); + } + + public DatabaseImporter(Context context) { + _context = context; + } + + protected Context getContext() { + return _context; + } + + public SuFile getAppPath() throws DatabaseImporterException, PackageManager.NameNotFoundException { + return getAppPath(getAppPkgName(), getAppSubPath()); + } + + protected SuFile getAppPath(String pkgName, String subPath) throws PackageManager.NameNotFoundException { + PackageManager man = getContext().getPackageManager(); + return new SuFile(man.getApplicationInfo(pkgName, 0).dataDir, subPath); + } + + protected abstract String getAppPkgName(); + + protected abstract String getAppSubPath() throws DatabaseImporterException, PackageManager.NameNotFoundException; + + public abstract State read(FileReader reader) throws DatabaseImporterException; + + public static DatabaseImporter create(Context context, Class type) { + try { + return type.getConstructor(Context.class).newInstance(context); + } catch (IllegalAccessException | InstantiationException + | NoSuchMethodException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + public static Map> getImporters() { + return Collections.unmodifiableMap(_importers); + } + + public static Map> getAppImporters() { + return Collections.unmodifiableMap(_appImporters); + } + + public static abstract class State { + private boolean _encrypted; + + public State(boolean encrypted) { + _encrypted = encrypted; + } + + public boolean isEncrypted() { + return _encrypted; + } + + public void decrypt(Context context, DecryptListener listener) throws DatabaseImporterException { + if (!_encrypted) { + throw new RuntimeException("Attempted to decrypt a plain text database"); + } + + throw new UnsupportedOperationException(); + } + + public Result convert() throws DatabaseImporterException { + if (_encrypted) { + throw new RuntimeException("Attempted to convert database before decrypting it"); + } + + throw new UnsupportedOperationException(); + } + } + + public static class Result { + private List _entries = new ArrayList<>(); + private List _errors = new ArrayList<>(); + + public void addEntry(DatabaseEntry entry) { + _entries.add(entry); + } + + public void addError(DatabaseImporterEntryException error) { + _errors.add(error); + } + + public List getEntries() { + return _entries; + } + + public List getErrors() { + return _errors; + } + } + + public static class FileReader implements Closeable { + private InputStream _stream; + + private FileReader(InputStream stream) { + _stream = stream; + } + + public static FileReader open(String filename) + throws FileNotFoundException { + FileInputStream stream = new FileInputStream(filename); + return new FileReader(stream); + } + + public static FileReader open(SuFile file) + throws FileNotFoundException { + SuFileInputStream stream = new SuFileInputStream(file); + return new FileReader(stream); + } + + public static FileReader open(Context context, Uri uri) + throws FileNotFoundException { + InputStream stream = context.getContentResolver().openInputStream(uri); + return new FileReader(stream); + } + + public byte[] readAll() throws IOException { + ByteInputStream stream = ByteInputStream.create(_stream); + return stream.getBytes(); + } + + public InputStream getStream() { + return _stream; + } + + @Override + public void close() throws IOException { + _stream.close(); + } + } + + public interface DecryptListener { + void onStateDecrypted(State state); + void onError(Exception e); + } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporterResult.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporterResult.java deleted file mode 100644 index 91fe0132..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporterResult.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.beemdevelopment.aegis.importers; - -import com.beemdevelopment.aegis.db.DatabaseEntry; - -import java.util.ArrayList; -import java.util.List; - -public class DatabaseImporterResult { - private List _entries = new ArrayList<>(); - private List _errors = new ArrayList<>(); - - public void addEntry(DatabaseEntry entry) { - _entries.add(entry); - } - - public void addError(DatabaseImporterEntryException error) { - _errors.add(error); - } - - public List getEntries() { - return _entries; - } - - public List getErrors() { - return _errors; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpFileImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpImporter.java similarity index 56% rename from app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpFileImporter.java rename to app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpImporter.java index def6af8b..a27aed5c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpFileImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/FreeOtpImporter.java @@ -8,7 +8,6 @@ import com.beemdevelopment.aegis.otp.HotpInfo; import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.TotpInfo; -import com.beemdevelopment.aegis.util.ByteInputStream; import org.json.JSONArray; import org.json.JSONException; @@ -20,81 +19,95 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -public class FreeOtpFileImporter extends DatabaseFileImporter { - private List _xmlEntries; +public class FreeOtpImporter extends DatabaseImporter { + private static final String _subPath = "shared_prefs/tokens.xml"; + private static final String _pkgName = "org.fedorahosted.freeotp"; - public FreeOtpFileImporter(Context context, ByteInputStream stream) { - super(context, stream); - } - - private static class XmlEntry { - String Name; - String Value; + public FreeOtpImporter(Context context) { + super(context); } @Override - public void parse() throws DatabaseImporterException { + protected String getAppPkgName() { + return _pkgName; + } + + @Override + protected String getAppSubPath() { + return _subPath; + } + + public State read(FileReader reader) throws DatabaseImporterException { try { XmlPullParser parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); - parser.setInput(_stream, null); + parser.setInput(reader.getStream(), null); parser.nextTag(); - _xmlEntries = parse(parser); + + List entries = parse(parser); + return new State(entries); } catch (XmlPullParserException | IOException e) { throw new DatabaseImporterException(e); } } - @Override - public DatabaseImporterResult convert() { - DatabaseImporterResult result = new DatabaseImporterResult(); + public static class State extends DatabaseImporter.State { + private List _entries; - for (XmlEntry xmlEntry : _xmlEntries) { - // TODO: order - if (!xmlEntry.Name.equals("tokenOrder")) { - try { - DatabaseEntry entry = convertEntry(xmlEntry); - result.addEntry(entry); - } catch (DatabaseImporterEntryException e) { - result.addError(e); + private State(List entries) { + super(false); + _entries = entries; + } + + @Override + public Result convert() { + Result result = new Result(); + + for (XmlEntry xmlEntry : _entries) { + // TODO: order + if (!xmlEntry.Name.equals("tokenOrder")) { + try { + DatabaseEntry entry = convertEntry(xmlEntry); + result.addEntry(entry); + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } } } + + return result; } - return result; - } + private static DatabaseEntry convertEntry(XmlEntry entry) throws DatabaseImporterEntryException { + try { + JSONObject obj = new JSONObject(entry.Value); - private static DatabaseEntry convertEntry(XmlEntry xmlEntry) throws DatabaseImporterEntryException { - try { - JSONObject obj = new JSONObject(xmlEntry.Value); + String type = obj.getString("type").toLowerCase(); + String algo = obj.getString("algo"); + int digits = obj.getInt("digits"); + byte[] secret = toBytes(obj.getJSONArray("secret")); - String type = obj.getString("type").toLowerCase(); - String algo = obj.getString("algo"); - int digits = obj.getInt("digits"); - byte[] secret = toBytes(obj.getJSONArray("secret")); + OtpInfo info; + switch (type) { + case "totp": + info = new TotpInfo(secret, algo, digits, obj.getInt("period")); + break; + case "hotp": + info = new HotpInfo(secret, algo, digits, obj.getLong("counter")); + break; + default: + throw new DatabaseImporterException("unsupported otp type: " + type); + } - OtpInfo info; - if (type.equals("totp")) { - info = new TotpInfo(secret, algo, digits, obj.getInt("period")); - } else if (type.equals("hotp")) { - info = new HotpInfo(secret, algo, digits, obj.getLong("counter")); - } else { - throw new DatabaseImporterException("unsupported otp type: " + type); + String issuer = obj.getString("issuerExt"); + String name = obj.optString("label"); + return new DatabaseEntry(info, name, issuer); + } catch (DatabaseImporterException | OtpInfoException | JSONException e) { + throw new DatabaseImporterEntryException(e, entry.Value); } - - String issuer = obj.getString("issuerExt"); - String name = obj.optString("label"); - return new DatabaseEntry(info, name, issuer); - } catch (DatabaseImporterException | OtpInfoException | JSONException e) { - throw new DatabaseImporterEntryException(e, xmlEntry.Value); } } - @Override - public boolean isEncrypted() { - return false; - } - private static List parse(XmlPullParser parser) throws IOException, XmlPullParserException { List entries = new ArrayList<>(); @@ -163,4 +176,9 @@ public class FreeOtpFileImporter extends DatabaseFileImporter { } } } + + private static class XmlEntry { + String Name; + String Value; + } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthAppImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java similarity index 58% rename from app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthAppImporter.java rename to app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java index 762e2b27..a0b320d6 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthAppImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/GoogleAuthImporter.java @@ -13,7 +13,6 @@ import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.TotpInfo; import com.topjohnwu.superuser.ShellUtils; -import com.topjohnwu.superuser.io.SuFileInputStream; import java.io.File; import java.io.FileOutputStream; @@ -23,30 +22,35 @@ import java.util.List; import static android.database.sqlite.SQLiteDatabase.OPEN_READONLY; -public class GoogleAuthAppImporter extends DatabaseAppImporter { +public class GoogleAuthImporter extends DatabaseImporter { private static final int TYPE_TOTP = 0; private static final int TYPE_HOTP = 1; private static final String _subPath = "databases/databases"; private static final String _pkgName = "com.google.android.apps.authenticator2"; - private List _entries = new ArrayList<>(); - - public GoogleAuthAppImporter(Context context) throws DatabaseImporterException { - super(context, _pkgName, _subPath); + public GoogleAuthImporter(Context context) { + super(context); } @Override - public void parse() throws DatabaseImporterException { + protected String getAppPkgName() { + return _pkgName; + } + + @Override + protected String getAppSubPath() { + return _subPath; + } + + public State read(FileReader reader) throws DatabaseImporterException { File file; try { // create a temporary copy of the database so that SQLiteDatabase can open it file = File.createTempFile("google-import-", "", getContext().getCacheDir()); - try (SuFileInputStream in = new SuFileInputStream(getPath())) { - try (FileOutputStream out = new FileOutputStream(file)) { - ShellUtils.pump(in, out); - } + try (FileOutputStream out = new FileOutputStream(file)) { + ShellUtils.pump(reader.getStream(), out); } } catch (IOException e) { throw new DatabaseImporterException(e); @@ -54,14 +58,16 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter { try (SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, OPEN_READONLY, null)) { try (Cursor cursor = db.rawQuery("SELECT * FROM accounts", null)) { - if (!cursor.moveToFirst()) { - return; + List entries = new ArrayList<>(); + + if (cursor.moveToFirst()) { + do { + Entry entry = new Entry(cursor); + entries.add(entry); + } while(cursor.moveToNext()); } - do { - Entry entry = new Entry(cursor); - _entries.add(entry); - } while(cursor.moveToNext()); + return new State(entries); } } catch (SQLiteException e) { throw new DatabaseImporterException(e); @@ -71,53 +77,57 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter { } } - @Override - public DatabaseImporterResult convert() { - DatabaseImporterResult result = new DatabaseImporterResult(); + public static class State extends DatabaseImporter.State { + private List _entries; - for (Entry sqlEntry : _entries) { + private State(List entries) { + super(false); + _entries = entries; + } + + @Override + public Result convert() { + Result result = new Result(); + + for (Entry sqlEntry : _entries) { + try { + DatabaseEntry entry = convertEntry(sqlEntry); + result.addEntry(entry); + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } + } + + return result; + } + + private static DatabaseEntry convertEntry(Entry entry) throws DatabaseImporterEntryException { try { - DatabaseEntry entry = convertEntry(sqlEntry); - result.addEntry(entry); - } catch (DatabaseImporterEntryException e) { - result.addError(e); + byte[] secret = Base32.decode(entry.getSecret().toCharArray()); + + OtpInfo info; + switch (entry.getType()) { + case TYPE_TOTP: + info = new TotpInfo(secret); + break; + case TYPE_HOTP: + info = new HotpInfo(secret, entry.getCounter()); + break; + default: + throw new DatabaseImporterException("unsupported otp type: " + entry.getType()); + } + + String name = entry.getEmail(); + String[] parts = name.split(":"); + if (parts.length == 2) { + name = parts[1]; + } + + return new DatabaseEntry(info, name, entry.getIssuer()); + } catch (Base32Exception | OtpInfoException | DatabaseImporterException e) { + throw new DatabaseImporterEntryException(e, entry.toString()); } } - - return result; - } - - private static DatabaseEntry convertEntry(Entry entry) throws DatabaseImporterEntryException { - try { - byte[] secret = Base32.decode(entry.getSecret().toCharArray()); - - OtpInfo info; - switch (entry.getType()) { - case TYPE_TOTP: - info = new TotpInfo(secret); - break; - case TYPE_HOTP: - info = new HotpInfo(secret, entry.getCounter()); - break; - default: - throw new DatabaseImporterException("unsupported otp type: " + entry.getType()); - } - - String name = entry.getEmail(); - String[] parts = name.split(":"); - if (parts.length == 2) { - name = parts[1]; - } - - return new DatabaseEntry(info, name, entry.getIssuer()); - } catch (Base32Exception | OtpInfoException | DatabaseImporterException e) { - throw new DatabaseImporterEntryException(e, entry.toString()); - } - } - - @Override - public boolean isEncrypted() { - return false; } private static String getString(Cursor cursor, String columnName) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/SteamAppImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/SteamAppImporter.java deleted file mode 100644 index 0c88e6a7..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/SteamAppImporter.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.beemdevelopment.aegis.importers; - -import android.content.Context; - -import com.beemdevelopment.aegis.db.DatabaseEntry; -import com.beemdevelopment.aegis.encoding.Base64; -import com.beemdevelopment.aegis.encoding.Base64Exception; -import com.beemdevelopment.aegis.otp.OtpInfoException; -import com.beemdevelopment.aegis.otp.SteamInfo; -import com.beemdevelopment.aegis.util.ByteInputStream; -import com.topjohnwu.superuser.io.SuFile; -import com.topjohnwu.superuser.io.SuFileInputStream; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -public class SteamAppImporter extends DatabaseAppImporter { - private static final String _subDir = "files"; - private static final String _pkgName = "com.valvesoftware.android.steam.community"; - - private List _objs = new ArrayList<>(); - - public SteamAppImporter(Context context) throws DatabaseImporterException { - super(context, _pkgName, _subDir); - } - - @Override - public void parse() throws DatabaseImporterException { - SuFile[] files = getPath().listFiles((d, name) -> name.startsWith("Steamguard-")); - if (files == null || files.length == 0) { - throw new DatabaseImporterException(String.format("Empty directory: %s", getPath().getAbsolutePath())); - } - - for (SuFile file : files) { - try (SuFileInputStream in = new SuFileInputStream(file)) { - try (ByteInputStream stream = ByteInputStream.create(in)) { - JSONObject obj = new JSONObject(new String(stream.getBytes(), StandardCharsets.UTF_8)); - _objs.add(obj); - } - } catch (IOException | JSONException e) { - throw new DatabaseImporterException(e); - } - } - } - - @Override - public DatabaseImporterResult convert() { - DatabaseImporterResult result = new DatabaseImporterResult(); - - for (JSONObject obj : _objs) { - try { - DatabaseEntry entry = convertEntry(obj); - result.addEntry(entry); - } catch (DatabaseImporterEntryException e) { - result.addError(e); - } - } - - return result; - } - - public DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException{ - try { - byte[] secret = Base64.decode(obj.getString("shared_secret")); - SteamInfo info = new SteamInfo(secret); - - String account = obj.getString("account_name"); - return new DatabaseEntry(info, account, "Steam"); - } catch (JSONException | Base64Exception | OtpInfoException e) { - throw new DatabaseImporterEntryException(e, obj.toString()); - } - } - - @Override - public boolean isEncrypted() { - return false; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/SteamImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/SteamImporter.java new file mode 100644 index 00000000..2e8f10cc --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/SteamImporter.java @@ -0,0 +1,89 @@ +package com.beemdevelopment.aegis.importers; + +import android.content.Context; +import android.content.pm.PackageManager; + +import com.beemdevelopment.aegis.db.DatabaseEntry; +import com.beemdevelopment.aegis.encoding.Base64; +import com.beemdevelopment.aegis.encoding.Base64Exception; +import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.SteamInfo; +import com.beemdevelopment.aegis.util.ByteInputStream; +import com.topjohnwu.superuser.io.SuFile; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class SteamImporter extends DatabaseImporter { + private static final String _subDir = "files"; + private static final String _pkgName = "com.valvesoftware.android.steam.community"; + + public SteamImporter(Context context) { + super(context); + } + + @Override + protected String getAppPkgName() { + return _pkgName; + } + + @Override + protected String getAppSubPath() throws DatabaseImporterException, PackageManager.NameNotFoundException { + // NOTE: this assumes that a global root shell has already been obtained by the caller + SuFile path = getAppPath(getAppPkgName(), _subDir); + SuFile[] files = path.listFiles((d, name) -> name.startsWith("Steamguard-")); + if (files == null || files.length == 0) { + throw new DatabaseImporterException(String.format("Empty directory: %s", path.getAbsolutePath())); + } + + // TODO: handle multiple files (can this even occur?) + return new SuFile(_subDir, files[0].getName()).getPath(); + } + + public State read(FileReader reader) throws DatabaseImporterException { + try (ByteInputStream stream = ByteInputStream.create(reader.getStream())) { + JSONObject obj = new JSONObject(new String(stream.getBytes(), StandardCharsets.UTF_8)); + return new State(obj); + } catch (IOException | JSONException e) { + throw new DatabaseImporterException(e); + } + } + + public static class State extends DatabaseImporter.State { + private JSONObject _obj; + + private State(JSONObject obj) { + super(false); + _obj = obj; + } + + @Override + public Result convert() { + Result result = new Result(); + + try { + DatabaseEntry entry = convertEntry(_obj); + result.addEntry(entry); + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } + + return result; + } + + private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException { + try { + byte[] secret = Base64.decode(obj.getString("shared_secret")); + SteamInfo info = new SteamInfo(secret); + + String account = obj.getString("account_name"); + return new DatabaseEntry(info, account, "Steam"); + } catch (JSONException | Base64Exception | OtpInfoException e) { + throw new DatabaseImporterEntryException(e, obj.toString()); + } + } + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesFragment.java index 8dba38bb..22d480ec 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesFragment.java @@ -4,6 +4,7 @@ import android.Manifest; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Bundle; @@ -20,6 +21,7 @@ import com.beemdevelopment.aegis.crypto.KeyStoreHandle; import com.beemdevelopment.aegis.crypto.KeyStoreHandleException; import com.beemdevelopment.aegis.db.DatabaseEntry; import com.beemdevelopment.aegis.db.DatabaseFileCredentials; +import com.beemdevelopment.aegis.db.DatabaseFileException; import com.beemdevelopment.aegis.db.DatabaseManager; import com.beemdevelopment.aegis.db.DatabaseManagerException; import com.beemdevelopment.aegis.db.slots.FingerprintSlot; @@ -29,25 +31,22 @@ import com.beemdevelopment.aegis.db.slots.SlotException; import com.beemdevelopment.aegis.db.slots.SlotList; import com.beemdevelopment.aegis.helpers.FingerprintHelper; import com.beemdevelopment.aegis.helpers.PermissionHelper; -import com.beemdevelopment.aegis.importers.AegisFileImporter; -import com.beemdevelopment.aegis.importers.DatabaseAppImporter; -import com.beemdevelopment.aegis.importers.DatabaseFileImporter; +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.importers.DatabaseImporterResult; import com.beemdevelopment.aegis.ui.preferences.SwitchPreference; -import com.beemdevelopment.aegis.util.ByteInputStream; import com.takisoft.preferencex.PreferenceFragmentCompat; import com.topjohnwu.superuser.Shell; +import com.topjohnwu.superuser.io.SuFile; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; 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; @@ -70,10 +69,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat { private Intent _result; private DatabaseManager _db; - // this is used to keep a reference to a database converter - // while the user provides credentials to decrypt it - private DatabaseFileImporter _importer; - private Class _importerType; + // keep a reference to the type of database converter the user selected + private Class _importerType; + private AegisImporter.State _importerState; private SwitchPreference _encryptionPreference; private SwitchPreference _fingerprintPreference; @@ -377,6 +375,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat { break; case CODE_GROUPS: onGroupManagerResult(resultCode, data); + break; case CODE_SELECT_ENTRIES: onSelectEntriesResult(resultCode, data); break; @@ -397,8 +396,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat { return; } - Map> importers = DatabaseFileImporter.getImporters(); - String[] names = importers.keySet().toArray(new String[importers.size()]); + Map> importers = DatabaseImporter.getImporters(); + String[] names = importers.keySet().toArray(new String[0]); Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) .setTitle(getString(R.string.choose_application)) @@ -417,61 +416,95 @@ public class PreferencesFragment extends PreferenceFragmentCompat { } private void onImportApp() { - Map> importers = DatabaseAppImporter.getImporters(); - String[] names = importers.keySet().toArray(new String[importers.size()]); + Map> importers = DatabaseImporter.getAppImporters(); + String[] names = importers.keySet().toArray(new String[0]); Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) .setTitle(getString(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(); - try { - DatabaseAppImporter 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(), getString(R.string.root_error), Toast.LENGTH_SHORT).show(); - return; - } - importer = DatabaseAppImporter.create(getContext(), importers.get(names[i])); - importer.parse(); - } catch (IOException e) { - Toast.makeText(getActivity(), getString(R.string.root_error), Toast.LENGTH_SHORT).show(); - return; - } - importDatabase(importer); - } catch (DatabaseImporterException e) { - e.printStackTrace(); - - String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage()); - Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show(); - } - } + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + Class 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() + try (Shell shell = Shell.getShell()) { + if (!shell.isRoot()) { + Toast.makeText(getActivity(), R.string.root_error, Toast.LENGTH_SHORT).show(); + return; + } + + SuFile file = importer.getAppPath(); + try (DatabaseImporter.FileReader reader = DatabaseImporter.FileReader.open(file)) { + importDatabase(importer, reader); + } + } 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(), getString(R.string.reading_file_error), Toast.LENGTH_SHORT).show(); + } + } + + private void importDatabase(DatabaseImporter importer, DatabaseImporter.FileReader reader) { + try { + DatabaseImporter.State state = importer.read(reader); + if (state.isEncrypted()) { + // temporary special case for encrypted Aegis databases + 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) { + Toast.makeText(getActivity(), R.string.decryption_error, Toast.LENGTH_SHORT).show(); + } + }); + } + } else { + importDatabase(state); + } + } catch (DatabaseImporterException e) { + e.printStackTrace(); + String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage()); + Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show(); + } + } + private void onImportDecryptResult(int resultCode, Intent data) { if (resultCode != Activity.RESULT_OK) { - _importer = null; + _importerState = null; return; } DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds"); - ((AegisFileImporter)_importer).setCredentials(creds); - + DatabaseImporter.State state; try { - importDatabase(_importer); - } catch (DatabaseImporterException e) { + state = ((AegisImporter.EncryptedState) _importerState).decrypt(creds); + } catch (DatabaseFileException e) { e.printStackTrace(); - - String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage()); - Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show(); + Toast.makeText(getActivity(), R.string.decryption_error, Toast.LENGTH_SHORT).show(); + return; } - _importer = null; + importDatabase(state); + _importerState = null; } private void onImportResult(int resultCode, Intent data) { @@ -480,44 +513,28 @@ public class PreferencesFragment extends PreferenceFragmentCompat { return; } - ByteInputStream stream; - - try (InputStream fileStream = getActivity().getContentResolver().openInputStream(uri)) { - stream = ByteInputStream.create(fileStream); + try (DatabaseImporter.FileReader reader = DatabaseImporter.FileReader.open(getContext(), uri)) { + DatabaseImporter importer = DatabaseImporter.create(getContext(), _importerType); + importDatabase(importer, reader); } catch (FileNotFoundException e) { Toast.makeText(getActivity(), getString(R.string.file_not_found), Toast.LENGTH_SHORT).show(); - return; } catch (IOException e) { e.printStackTrace(); Toast.makeText(getActivity(), getString(R.string.reading_file_error), Toast.LENGTH_SHORT).show(); - return; - } - - try { - DatabaseFileImporter importer = DatabaseFileImporter.create(getContext(), stream, _importerType); - importer.parse(); - - // special case to decrypt encrypted aegis databases - if (importer.isEncrypted() && importer instanceof AegisFileImporter) { - _importer = importer; - - Intent intent = new Intent(getActivity(), AuthActivity.class); - intent.putExtra("slots", ((AegisFileImporter)_importer).getFile().getHeader().getSlots()); - startActivityForResult(intent, CODE_IMPORT_DECRYPT); - return; - } - - importDatabase(importer); - } catch (DatabaseImporterException e) { - e.printStackTrace(); - - String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage()); - Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show(); } } - private void importDatabase(DatabaseImporter importer) throws DatabaseImporterException { - DatabaseImporterResult result = importer.convert(); + private void importDatabase(DatabaseImporter.State state) { + DatabaseImporter.Result result; + try { + result = state.convert(); + } catch (DatabaseImporterException e) { + e.printStackTrace(); + String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage()); + Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show(); + return; + } + List entries = result.getEntries(); List errors = result.getErrors(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5c4b946e..afb84721 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,6 +113,7 @@ An error occurred while trying to parse the file Error: File not found An error occurred while trying to read the file + Error: App is not installed Error: unable to obtain root access Imported %d entries Read %d entries. %d errors.