Compare commits

...
46 Commits
Author SHA1 Message Date
Alexander Bakker f27dc54ec4 Release v0.3 2019-04-01 23:50:07 +02:00
Alexander Bakker 4c1dec613c Fallback to a 30 second period if an andOTP doesn't provide one 2019-04-01 23:48:44 +02:00
Alexander Bakker a251685002 Add a dialog that displays a list of errors that occurred during import 2019-04-01 23:36:31 +02:00
Alexander Bakker 118221e3df Add support for Steam tokens to the andOTP importer
Close #44
2019-04-01 22:37:56 +02:00
Alexander Bakker 1dd5f893da Add support for Steam OTP 2019-04-01 22:37:52 +02:00
Michael Schättgen a46640f43d Fix recreate when setting view mode 2019-04-01 21:47:41 +02:00
Michael Schättgen a293078603 Add way to set view mode 2019-04-01 01:44:38 +02:00
Michael Schättgen f3ed79dc71 Add ability to change view mode 2019-04-01 01:21:12 +02:00
Michael Schättgen ee8fd2e9f2 Hide divider in compact mode when progressbar is visible 2019-04-01 01:04:26 +02:00
Michael Schättgen 8b9ec5f976 Start working on compact mode 2019-04-01 00:58:45 +02:00
Michael Schättgen d283eae7a0 Add russian strings for authentication methods
#43
2019-04-01 00:18:44 +02:00
Michael SchättgenandGitHub a854db760c Merge pull request #41 from ferhadnecef/stringlate-ru-1048
Updated ru (Russian) translation
2019-03-31 23:57:36 +02:00
Michael Schättgen caf2ec882a Merge branch 'feature-sorting' 2019-03-31 23:30:52 +02:00
Michael Schättgen bbf967e2b7 Add a way to save current sorting method 2019-03-31 22:34:25 +02:00
Michael Schättgen b9d011b48e Disable drag and drop when list is sorted 2019-03-31 21:28:37 +02:00
Michael Schättgen ecdadf5335 Add SortCategory fixes and support for custom sorting 2019-03-31 21:23:14 +02:00
Michael Schättgen 10b80442d0 Comparators will now ignore casing 2019-03-31 21:22:20 +02:00
Alexander Bakker a2879a4558 Fix the screenshot links in the README 2019-03-31 20:39:41 +02:00
Michael Schättgen 62025d1053 Add way to check for reversed sort categories 2019-03-31 20:14:11 +02:00
Michael Schättgen 09fe550a28 Add AccountNameComparator 2019-03-31 20:12:34 +02:00
Michael Schättgen 07aac1e6a3 Add support for reversed sorting methods 2019-03-31 20:12:25 +02:00
Michael Schättgen b8939b504e Start working on entry sorting 2019-03-31 19:57:45 +02:00
Alexander Bakker 4f29d0c2e4 Fix metadata files for F-Droid (for real this time) 2019-03-31 18:21:10 +02:00
Alexander Bakker 592c6683c3 Continue importing entries even if one can't be parsed 2019-03-30 18:26:16 +01:00
ferhad.necef f8edac72ea Updated ru (Russian) translation 2019-03-29 16:00:49 +03:00
Alexander Bakker fc0e1150f6 Replace hardcoded encoding names with constants 2019-03-28 20:01:00 +01:00
Michael SchättgenandGitHub e1fd913817 Update README.md 2019-03-28 13:15:18 +00:00
Michael Schättgen ede9f93a59 Release v0.2.2 2019-03-28 13:46:39 +01:00
Michael Schättgen c6c0eb88e9 Fix true black theme in all activities
Closes #32. Also tested on API 19.
2019-03-28 13:46:33 +01:00
Michael Schättgen f929790be6 Fix metadata path by adding android folder
Closes #36
2019-03-28 13:46:07 +01:00
Michael Schättgen a4ff9d6d31 Fix crash in API 19 2019-03-28 13:45:43 +01:00
Michael SchättgenandGitHub a21d28a91f Update README.md 2019-03-28 10:47:24 +00:00
Michael Schättgen f7c32bc28b Improve theme based texts 2019-03-28 01:27:07 +01:00
Michael Schättgen 59c0ca947d Add workaround for amoled themed preferences 2019-03-28 01:08:44 +01:00
Michael Schättgen a7ac1adb80 Convert dark mode preference to select theme preference 2019-03-28 00:56:29 +01:00
Michael Schättgen 17204cc026 Add status bar color for true dark theme 2019-03-28 00:54:50 +01:00
Michael Schättgen cf4aecbd3e Refactor dark mode to allow multiple themes 2019-03-28 00:54:30 +01:00
Michael Schättgen 9baaf824c6 Start working on amoled / true black theme
#32
2019-03-28 00:15:02 +01:00
Michael Schättgen eb172fc54e Merge branch 'master' of https://github.com/alexbakker/Aegis 2019-03-27 21:18:26 +01:00
Michael Schättgen e99762b814 Change global progressbar color to improve visibility
As part of #34
2019-03-27 21:18:16 +01:00
Alexander Bakker 6b1d4a60e3 Credit Freepik from Flaticon for the icon 2019-03-27 12:32:40 +01:00
Alexander Bakker a540b3cfee Add icon to fastlane metadata 2019-03-27 12:14:08 +01:00
Michael SchättgenandGitHub b0481d6b30 Update README.md 2019-03-27 11:07:03 +00:00
Alexander Bakker 0c315c53e5 Add .idea/ and release/ to .gitignore 2019-03-27 01:03:55 +01:00
Alexander Bakker 75c37a454f Release v0.2.1 2019-03-27 00:51:44 +01:00
Michael Schättgen 554d7e17be Fix NullPointerException when deleting tokens 2019-03-27 00:47:39 +01:00
72 changed files with 1342 additions and 336 deletions
+2 -2
View File
@@ -16,6 +16,7 @@ out/
# Gradle files
.gradle/
build/
release/
# Local configuration file (sdk path, etc)
local.properties
@@ -34,8 +35,7 @@ captures/
# Intellij
*.iml
.idea/workspace.xml
.idea/libraries
.idea/
# Keystore files
*.jks
Generated
-1
View File
@@ -1 +0,0 @@
Aegis
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>
-3
View File
@@ -1,3 +0,0 @@
<component name="CopyrightManager">
<settings default="" />
</component>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
-22
View File
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT" />
</compositeConfiguration>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="F:\Android\Android Studio\gradle\gradle-2.14.1" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
-33
View File
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
-9
View File
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Aegis.iml" filepath="$PROJECT_DIR$/Aegis.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>
-12
View File
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+13 -4
View File
@@ -14,9 +14,12 @@ Aegis is a free, secure and open source 2FA app for Android.
- Multiple ways to add new entries
- Scan QR code
- Enter details manually
- Import
- Import from files
- andOTP
- FreeOTP
- Aegis
- Import from apps (requires root):
- Google Authenticator
- Supported algorithms:
- HOTP ([RFC 4226](https://tools.ietf.org/html/rfc4226))
- TOTP ([RFC 6238](https://tools.ietf.org/html/rfc6238))
@@ -26,15 +29,17 @@ Aegis is a free, secure and open source 2FA app for Android.
- Drag and drop
- Custom groups
- Advanced entry editing
- Material design
- Light and dark themes
- Material design with multiple themes:
- Light theme
- Dark theme
- Amoled / true dark theme
- Export (plaintext or encrypted)
## Screenshots
[<img width=200 alt="Main Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true)
[<img width=200 alt="Settings Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true)
[<img width=200 alt="Edit Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true).png
[<img width=200 alt="Edit Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true)
[<img width=200 alt="Main Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true)
[<img width=200 alt="Main Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true)
@@ -62,3 +67,7 @@ There are plans to make it available on F-Droid as well.
## License
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details.
## Attribution
The icon is largely based on a design by Freepik from Flaticon.
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "com.beemdevelopment.aegis"
minSdkVersion 19
targetSdkVersion 28
versionCode 3
versionName "0.2"
versionCode 6
versionName "0.3"
}
buildTypes {
debug {
@@ -39,10 +39,38 @@ public class Preferences {
_prefs.edit().putInt("pref_tap_to_reveal_time", number).apply();
}
public void setCurrentSortCategory(SortCategory category) {
_prefs.edit().putInt("pref_current_sort_category", category.ordinal()).apply();
}
public int getCurrentSortCategory() {
return _prefs.getInt("pref_current_sort_category", 0);
}
public int getTapToRevealTime() {
return _prefs.getInt("pref_tap_to_reveal_time", 30);
}
public int getCurrentTheme() {
return _prefs.getInt("pref_current_theme", 0);
}
public void setCurrentTheme(Theme theme) {
_prefs.edit().putInt("pref_current_theme", theme.ordinal()).apply();
}
public int getCurrentViewMode() {
return _prefs.getInt("pref_current_view_mode", 0);
}
public void setCurrentViewMode(ViewMode viewMode) {
_prefs.edit().putInt("pref_current_view_mode", viewMode.ordinal()).apply();
}
public int getTimeout() {
return _prefs.getInt("pref_timeout", -1);
@@ -0,0 +1,73 @@
package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.helpers.comparators.AccountNameComparator;
import com.beemdevelopment.aegis.helpers.comparators.IssuerNameComparator;
import java.util.Comparator;
public enum SortCategory {
CUSTOM,
ACCOUNT,
ACCOUNTREVERSED,
ISSUER,
ISSUERREVERSED;
public static SortCategory fromInteger(int x) {
switch(x) {
case 0:
return CUSTOM;
case 1:
return ACCOUNT;
case 2:
return ACCOUNTREVERSED;
case 3:
return ISSUER;
case 4:
return ISSUERREVERSED;
}
return null;
}
public static Comparator getComparator(SortCategory sortCategory) {
switch(sortCategory) {
case ACCOUNT:
case ACCOUNTREVERSED:
return new AccountNameComparator();
case ISSUER:
case ISSUERREVERSED:
return new IssuerNameComparator();
case CUSTOM:
return new IssuerNameComparator();
}
return null;
}
public static boolean isReversed(SortCategory sortCategory) {
switch(sortCategory) {
case ACCOUNTREVERSED:
case ISSUERREVERSED:
return true;
default:
return false;
}
}
public static int getMenuItem(SortCategory sortCategory) {
switch (sortCategory) {
case CUSTOM:
return R.id.menu_sort_custom;
case ACCOUNT:
return R.id.menu_sort_alphabetically_name;
case ACCOUNTREVERSED:
return R.id.menu_sort_alphabetically_name_reverse;
case ISSUER:
return R.id.menu_sort_alphabetically;
case ISSUERREVERSED:
return R.id.menu_sort_alphabetically_reverse;
default:
return R.id.menu_sort_custom;
}
}
}
@@ -0,0 +1,39 @@
package com.beemdevelopment.aegis;
public enum Theme {
LIGHT,
DARK,
AMOLED;
public static Theme fromInteger(int x) {
switch(x) {
case 0:
return LIGHT;
case 1:
return DARK;
case 2:
return AMOLED;
}
return null;
}
public static String getThemeName(int x) {
switch(x) {
case 0:
return "Light theme";
case 1:
return "Dark theme";
case 2:
return "Amoled theme";
}
return null;
}
public static String[] getThemeNames() {
return new String[] {
"Light theme",
"Dark theme",
"Amoled theme"
};
}
}
@@ -0,0 +1,33 @@
package com.beemdevelopment.aegis;
public enum ViewMode {
NORMAL,
COMPACT;
public static ViewMode fromInteger(int x) {
switch(x) {
case 0:
return NORMAL;
case 1:
return COMPACT;
}
return null;
}
public static String getViewModeName(int x) {
switch(x) {
case 0:
return "Normal";
case 1:
return "Compact";
}
return null;
}
public static String[] getViewModeNames() {
return new String[] {
"Normal",
"Compact"
};
}
}
@@ -6,7 +6,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -122,7 +122,7 @@ public class CryptoUtils {
private static byte[] toBytes(char[] chars) {
CharBuffer charBuf = CharBuffer.wrap(chars);
ByteBuffer byteBuf = Charset.forName("UTF-8").encode(charBuf);
ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(charBuf);
return byteBuf.array();
}
}
@@ -12,7 +12,7 @@ public class HOTP {
private HOTP() {
}
public static String generateOTP(byte[] secret, String algo, int digits, long counter)
public static OTP generateOTP(byte[] secret, String algo, int digits, long counter)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec key = new SecretKeySpec(secret, "RAW");
@@ -30,18 +30,11 @@ public class HOTP {
// truncate hash to get the HTOP value
// http://tools.ietf.org/html/rfc4226#section-5.4
int offset = hash[hash.length - 1] & 0xf;
long bin = ((hash[offset] & 0x7f) << 24)
int otp = ((hash[offset] & 0x7f) << 24)
| ((hash[offset + 1] & 0xff) << 16)
| ((hash[offset + 2] & 0xff) << 8)
| (hash[offset + 3] & 0xff);
long otp = bin % (long) Math.pow(10, digits);
// prepend zeroes if needed
StringBuilder res = new StringBuilder(Long.toString(otp));
while (res.length() < digits) {
res.insert(0, "0");
}
return res.toString();
return new OTP(otp, digits);
}
}
@@ -0,0 +1,50 @@
package com.beemdevelopment.aegis.crypto.otp;
import androidx.annotation.NonNull;
public class OTP {
private static final String STEAM_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY";
private int _code;
private int _digits;
public OTP(int code, int digits) {
_code = code;
_digits = digits;
}
public int getCode() {
return _code;
}
public int getDigits() {
return _digits;
}
@NonNull
@Override
public String toString() {
int code = _code % (int) Math.pow(10, _digits);
// prepend zeroes if needed
StringBuilder res = new StringBuilder(Long.toString(code));
while (res.length() < _digits) {
res.insert(0, "0");
}
return res.toString();
}
public String toSteamString() {
int code = _code;
StringBuilder res = new StringBuilder();
for (int i = 0; i < _digits; i++) {
char c = STEAM_ALPHABET.charAt(code % STEAM_ALPHABET.length());
res.append(c);
code /= STEAM_ALPHABET.length();
}
return res.toString();
}
}
@@ -8,13 +8,13 @@ public class TOTP {
private TOTP() {
}
public static String generateOTP(byte[] secret, String algo, int digits, long period, long seconds)
public static OTP generateOTP(byte[] secret, String algo, int digits, long period, long seconds)
throws InvalidKeyException, NoSuchAlgorithmException {
long counter = (long) Math.floor((double) seconds / period);
return HOTP.generateOTP(secret, algo, digits, counter);
}
public static String generateOTP(byte[] secret, String algo, int digits, long period)
public static OTP generateOTP(byte[] secret, String algo, int digits, long period)
throws InvalidKeyException, NoSuchAlgorithmException {
return generateOTP(secret, algo, digits, period, System.currentTimeMillis() / 1000);
}
@@ -12,7 +12,7 @@ import com.beemdevelopment.aegis.encoding.HexException;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
public class DatabaseFile {
public static final byte VERSION = 1;
@@ -54,8 +54,8 @@ public class DatabaseFile {
try {
String string = obj.toString(4);
return string.getBytes("UTF-8");
} catch (JSONException | UnsupportedEncodingException e) {
return string.getBytes(StandardCharsets.UTF_8);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@@ -79,9 +79,9 @@ public class DatabaseFile {
public static DatabaseFile fromBytes(byte[] data) throws DatabaseFileException {
try {
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
JSONObject obj = new JSONObject(new String(data, StandardCharsets.UTF_8));
return DatabaseFile.fromJson(obj);
} catch (UnsupportedEncodingException | JSONException e) {
} catch (JSONException e) {
throw new DatabaseFileException(e);
}
}
@@ -94,8 +94,8 @@ public class DatabaseFile {
try {
byte[] bytes = Base64.decode((String) _content);
CryptResult result = creds.decrypt(bytes, _header.getParams());
return new JSONObject(new String(result.getData(), "UTF-8"));
} catch (MasterKeyException | JSONException | UnsupportedEncodingException | Base64Exception e) {
return new JSONObject(new String(result.getData(), StandardCharsets.UTF_8));
} catch (MasterKeyException | JSONException | Base64Exception e) {
throw new DatabaseFileException(e);
}
}
@@ -108,12 +108,12 @@ public class DatabaseFile {
public void setContent(JSONObject obj, DatabaseFileCredentials creds) throws DatabaseFileException {
try {
String string = obj.toString(4);
byte[] dbBytes = string.getBytes("UTF-8");
byte[] dbBytes = string.getBytes(StandardCharsets.UTF_8);
CryptResult result = creds.encrypt(dbBytes);
_content = Base64.encode(result.getData());
_header = new Header(creds.getSlots(), result.getParams());
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
} catch (MasterKeyException | JSONException e) {
throw new DatabaseFileException(e);
}
}
@@ -1,6 +1,6 @@
package com.beemdevelopment.aegis.encoding;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
public class Base64 {
private static final int _flags = android.util.Base64.NO_WRAP;
@@ -19,11 +19,6 @@ public class Base64 {
public static String encode(byte[] data) {
byte[] encoded = android.util.Base64.encode(data, _flags);
try {
return new String(encoded, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return new String(encoded, StandardCharsets.UTF_8);
}
}
@@ -0,0 +1,12 @@
package com.beemdevelopment.aegis.helpers.comparators;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import java.util.Comparator;
public class AccountNameComparator implements Comparator<DatabaseEntry> {
@Override
public int compare(DatabaseEntry a, DatabaseEntry b) {
return a.getName().compareToIgnoreCase(b.getName());
}
}
@@ -0,0 +1,13 @@
package com.beemdevelopment.aegis.helpers.comparators;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.ui.views.EntryHolder;
import java.util.Comparator;
public class IssuerNameComparator implements Comparator<DatabaseEntry> {
@Override
public int compare(DatabaseEntry a, DatabaseEntry b) {
return a.getIssuer().compareToIgnoreCase(b.getIssuer());
}
}
@@ -2,16 +2,16 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
import com.beemdevelopment.aegis.db.Database;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.db.DatabaseException;
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;
public class AegisFileImporter extends DatabaseFileImporter {
@@ -33,7 +33,9 @@ public class AegisFileImporter extends DatabaseFileImporter {
}
@Override
public List<DatabaseEntry> convert() throws DatabaseImporterException {
public DatabaseImporterResult convert() throws DatabaseImporterException {
DatabaseImporterResult result = new DatabaseImporterResult();
try {
JSONObject obj;
if (_file.isEncrypted() && _creds != null) {
@@ -42,11 +44,29 @@ public class AegisFileImporter extends DatabaseFileImporter {
obj = _file.getContent();
}
Database db = Database.fromJson(obj);
return db.getEntries();
} catch (DatabaseException | DatabaseFileException e) {
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
@@ -6,9 +6,7 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import java.nio.charset.StandardCharsets;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.encoding.Base32;
@@ -16,6 +14,7 @@ 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;
@@ -29,55 +28,68 @@ public class AndOtpFileImporter extends DatabaseFileImporter {
@Override
public void parse() throws DatabaseImporterException {
try {
_obj = new JSONArray(new String(_stream.getBytes(), "UTF-8"));
_obj = new JSONArray(new String(_stream.getBytes(), StandardCharsets.UTF_8));
} catch (JSONException e) {
throw new DatabaseImporterException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@Override
public List<DatabaseEntry> convert() throws DatabaseImporterException {
List<DatabaseEntry> entries = new ArrayList<>();
public DatabaseImporterResult convert() throws DatabaseImporterException {
DatabaseImporterResult result = new DatabaseImporterResult();
try {
for (int i = 0; i < _obj.length(); i++) {
for (int i = 0; i < _obj.length(); i++) {
try {
JSONObject obj = _obj.getJSONObject(i);
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;
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 = "";
String name = "";
String[] parts = obj.getString("label").split(" - ");
if (parts.length > 1) {
issuer = parts[0];
name = parts[1];
} else {
name = parts[0];
}
DatabaseEntry entry = new DatabaseEntry(info, name, issuer);
entries.add(entry);
DatabaseEntry entry = convertEntry(obj);
result.addEntry(entry);
} catch (JSONException e) {
throw new DatabaseImporterException(e);
} catch (DatabaseImporterEntryException e) {
result.addError(e);
}
} catch (Base32Exception | OtpInfoException | JSONException e) {
throw new DatabaseImporterException(e);
}
return entries;
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
@@ -16,6 +16,7 @@ public abstract class DatabaseAppImporter implements DatabaseImporter {
// note: keep this list sorted alphabetically
LinkedHashMap<String, Class<? extends DatabaseAppImporter>> importers = new LinkedHashMap<>();
importers.put("Google Authenticator", GoogleAuthAppImporter.class);
importers.put("Steam", SteamAppImporter.class);
_importers = Collections.unmodifiableMap(importers);
}
@@ -27,7 +28,7 @@ public abstract class DatabaseAppImporter implements DatabaseImporter {
public abstract void parse() throws DatabaseImporterException;
public abstract List<DatabaseEntry> convert() throws DatabaseImporterException;
public abstract DatabaseImporterResult convert() throws DatabaseImporterException;
public abstract boolean isEncrypted();
@@ -32,7 +32,7 @@ public abstract class DatabaseFileImporter implements DatabaseImporter {
public abstract void parse() throws DatabaseImporterException;
public abstract List<DatabaseEntry> convert() throws DatabaseImporterException;
public abstract DatabaseImporterResult convert() throws DatabaseImporterException;
public abstract boolean isEncrypted();
@@ -8,7 +8,7 @@ import java.util.List;
public interface DatabaseImporter {
void parse() throws DatabaseImporterException;
List<DatabaseEntry> convert() throws DatabaseImporterException;
DatabaseImporterResult convert() throws DatabaseImporterException;
boolean isEncrypted();
Context getContext();
}
@@ -0,0 +1,14 @@
package com.beemdevelopment.aegis.importers;
public class DatabaseImporterEntryException extends Exception {
private String _text;
public DatabaseImporterEntryException(Throwable cause, String text) {
super(cause);
_text = text;
}
public String getText() {
return _text;
}
}
@@ -0,0 +1,27 @@
package com.beemdevelopment.aegis.importers;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import java.util.ArrayList;
import java.util.List;
public class DatabaseImporterResult {
private List<DatabaseEntry> _entries = new ArrayList<>();
private List<DatabaseImporterEntryException> _errors = new ArrayList<>();
public void addEntry(DatabaseEntry entry) {
_entries.add(entry);
}
public void addError(DatabaseImporterEntryException error) {
_errors.add(error);
}
public List<DatabaseEntry> getEntries() {
return _entries;
}
public List<DatabaseImporterEntryException> getErrors() {
return _errors;
}
}
@@ -46,43 +46,48 @@ public class FreeOtpFileImporter extends DatabaseFileImporter {
}
@Override
public List<DatabaseEntry> convert() throws DatabaseImporterException {
List<DatabaseEntry> entries = new ArrayList<>();
public DatabaseImporterResult convert() {
DatabaseImporterResult result = new DatabaseImporterResult();
try {
for (XmlEntry xmlEntry : _xmlEntries) {
if (xmlEntry.Name.equals("tokenOrder")) {
// TODO: order
JSONArray array = new JSONArray(xmlEntry.Value);
} else {
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"));
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");
DatabaseEntry entry = new DatabaseEntry(info, name, issuer);
entries.add(entry);
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);
}
}
} catch (OtpInfoException | JSONException e) {
throw new DatabaseImporterException(e);
}
return entries;
return result;
}
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"));
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, xmlEntry.Value);
}
}
@Override
@@ -32,7 +32,7 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
@SuppressLint("SdCardPath")
private static final String _filename = "/data/data/com.google.android.apps.authenticator2/databases/databases";
private List<DatabaseEntry> _entries = new ArrayList<>();
private List<Entry> _entries = new ArrayList<>();
public GoogleAuthAppImporter(Context context) {
super(context);
@@ -61,34 +61,11 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
}
do {
int type = getInt(cursor, "type");
byte[] secret = Base32.decode(getString(cursor, "secret").toCharArray());
OtpInfo info;
switch (type) {
case TYPE_TOTP:
info = new TotpInfo(secret);
break;
case TYPE_HOTP:
info = new HotpInfo(secret, getInt(cursor, "counter"));
break;
default:
throw new DatabaseImporterException("unsupported otp type: " + type);
}
String name = getString(cursor, "email", "");
String issuer = getString(cursor, "issuer", "");
String[] parts = name.split(":");
if (parts.length == 2) {
name = parts[1];
}
DatabaseEntry entry = new DatabaseEntry(info, name, issuer);
Entry entry = new Entry(cursor);
_entries.add(entry);
} while(cursor.moveToNext());
}
} catch (SQLiteException | OtpInfoException | Base32Exception e) {
} catch (SQLiteException e) {
throw new DatabaseImporterException(e);
} finally {
// always delete the temporary file
@@ -97,8 +74,47 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
}
@Override
public List<DatabaseEntry> convert() {
return _entries;
public DatabaseImporterResult convert() {
DatabaseImporterResult result = new DatabaseImporterResult();
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 {
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
@@ -121,4 +137,44 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
private static int getInt(Cursor cursor, String columnName) {
return cursor.getInt(cursor.getColumnIndex(columnName));
}
private static long getLong(Cursor cursor, String columnName) {
return cursor.getLong(cursor.getColumnIndex(columnName));
}
private static class Entry {
private int _type;
private String _secret;
private String _email;
private String _issuer;
private long _counter;
public Entry(Cursor cursor) {
_type = getInt(cursor, "type");
_secret = getString(cursor, "secret");
_email = getString(cursor, "email", "");
_issuer = getString(cursor, "issuer", "");
_counter = getLong(cursor, "counter");
}
public int getType() {
return _type;
}
public String getSecret() {
return _secret;
}
public String getEmail() {
return _email;
}
public String getIssuer() {
return _issuer;
}
public long getCounter() {
return _counter;
}
}
}
@@ -0,0 +1,80 @@
package com.beemdevelopment.aegis.importers;
import android.annotation.SuppressLint;
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 {
@SuppressLint("SdCardPath")
private static final String _path = "/data/data/com.valvesoftware.android.steam.community/files";
private List<JSONObject> _objs = new ArrayList<>();
public SteamAppImporter(Context context) {
super(context);
}
@Override
public void parse() throws DatabaseImporterException {
SuFile dir = new SuFile(_path);
for (SuFile file : dir.listFiles((d, name) -> name.startsWith("Steamguard-"))) {
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;
}
}
@@ -1,6 +1,7 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.crypto.otp.HOTP;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import org.json.JSONException;
import org.json.JSONObject;
@@ -9,6 +10,8 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class HotpInfo extends OtpInfo {
public static final String ID = "hotp";
private long _counter;
public HotpInfo(byte[] secret, long counter) throws OtpInfoException {
@@ -28,7 +31,8 @@ public class HotpInfo extends OtpInfo {
@Override
public String getOtp() {
try {
return HOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getCounter());
OTP otp = HOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getCounter());
return otp.toString();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException();
}
@@ -36,7 +40,7 @@ public class HotpInfo extends OtpInfo {
@Override
public String getType() {
return "hotp";
return ID;
}
@Override
@@ -98,13 +98,14 @@ public abstract class OtpInfo implements Serializable {
int digits = obj.getInt("digits");
switch (type) {
case "totp":
int period = obj.getInt("period");
info = new TotpInfo(secret, algo, digits, period);
case TotpInfo.ID:
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
break;
case "hotp":
long counter = obj.getLong("counter");
info = new HotpInfo(secret, algo, digits, counter);
case SteamInfo.ID:
info = new SteamInfo(secret, algo, digits, obj.getInt("period"));
break;
case HotpInfo.ID:
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
break;
default:
throw new OtpInfoException("unsupported otp type: " + type);
@@ -126,7 +127,8 @@ public abstract class OtpInfo implements Serializable {
}
OtpInfo info = (OtpInfo) o;
return Arrays.equals(getSecret(), info.getSecret())
return getType().equals(info.getType())
&& Arrays.equals(getSecret(), info.getSecret())
&& getAlgorithm(false).equals(info.getAlgorithm(false))
&& getDigits() == info.getDigits();
}
@@ -0,0 +1,34 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class SteamInfo extends TotpInfo {
public static final String ID = "steam";
public SteamInfo(byte[] secret) throws OtpInfoException {
super(secret, "SHA1", 5, 30);
}
public SteamInfo(byte[] secret, String algorithm, int digits, int period) throws OtpInfoException {
super(secret, algorithm, digits, period);
}
@Override
public String getOtp() {
try {
OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod());
return otp.toSteamString();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
public String getType() {
return ID;
}
}
@@ -1,5 +1,6 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import org.json.JSONException;
@@ -9,6 +10,8 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class TotpInfo extends OtpInfo {
public static final String ID = "totp";
private int _period;
public TotpInfo(byte[] secret) throws OtpInfoException {
@@ -24,7 +27,8 @@ public class TotpInfo extends OtpInfo {
@Override
public String getOtp() {
try {
return TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod());
OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod());
return otp.toString();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
@@ -32,7 +36,7 @@ public class TotpInfo extends OtpInfo {
@Override
public String getType() {
return "totp";
return ID;
}
@Override
@@ -7,6 +7,7 @@ import android.view.WindowManager;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
public abstract class AegisActivity extends AppCompatActivity {
private AegisApplication _app;
@@ -22,7 +23,7 @@ public abstract class AegisActivity extends AppCompatActivity {
}
// set the theme
setPreferredTheme(getPreferences().isDarkModeEnabled());
setPreferredTheme(Theme.fromInteger(getPreferences().getCurrentTheme()));
}
protected AegisApplication getApp() {
@@ -33,11 +34,19 @@ public abstract class AegisActivity extends AppCompatActivity {
return _app.getPreferences();
}
protected void setPreferredTheme(boolean darkMode) {
if (darkMode) {
setTheme(R.style.AppTheme_Dark);
} else {
setTheme(R.style.AppTheme);
protected void setPreferredTheme(Theme theme) {
switch (theme) {
case LIGHT:
setTheme(R.style.AppTheme);
break;
case DARK:
setTheme(R.style.AppTheme_Dark);
break;
case AMOLED:
setTheme(R.style.AppTheme_TrueBlack);
break;
}
}
}
@@ -38,6 +38,7 @@ import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
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 java.io.ByteArrayInputStream;
@@ -166,7 +167,7 @@ public class EditEntryActivity extends AegisActivity {
}
String type = _origEntry.getInfo().getType();
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type.toUpperCase()), false);
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type), false);
String algo = _origEntry.getInfo().getAlgorithm(false);
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
@@ -189,11 +190,12 @@ public class EditEntryActivity extends AegisActivity {
String type = _spinnerType.getSelectedItem().toString();
switch (type.toLowerCase()) {
case "totp":
case TotpInfo.ID:
case SteamInfo.ID:
_rowCounter.setVisibility(View.GONE);
_rowPeriod.setVisibility(View.VISIBLE);
break;
case "hotp":
case HotpInfo.ID:
_rowPeriod.setVisibility(View.GONE);
_rowCounter.setVisibility(View.VISIBLE);
break;
@@ -253,15 +255,6 @@ public class EditEntryActivity extends AegisActivity {
}
}
@Override
protected void setPreferredTheme(boolean darkMode) {
if (darkMode) {
setTheme(R.style.AppTheme_Dark);
} else {
setTheme(R.style.AppTheme);
}
}
private void openAdvancedSettings() {
Animation fadeOut = new AlphaAnimation(1, 0);
fadeOut.setInterpolator(new AccelerateInterpolator());
@@ -426,6 +419,14 @@ public class EditEntryActivity extends AegisActivity {
super.onActivityResult(requestCode, resultCode, data);
}
private int parsePeriod() throws ParseException {
try {
return Integer.parseInt(_textPeriod.getText().toString());
} catch (NumberFormatException e) {
throw new ParseException("Period is not an integer.");
}
}
private DatabaseEntry parseEntry() throws ParseException {
if (_textSecret.length() == 0) {
throw new ParseException("Secret is a required field.");
@@ -452,16 +453,13 @@ public class EditEntryActivity extends AegisActivity {
OtpInfo info;
try {
switch (type.toLowerCase()) {
case "totp":
int period;
try {
period = Integer.parseInt(_textPeriod.getText().toString());
} catch (NumberFormatException e) {
throw new ParseException("Period is not an integer.");
}
info = new TotpInfo(secret, algo, digits, period);
case TotpInfo.ID:
info = new TotpInfo(secret, algo, digits, parsePeriod());
break;
case "hotp":
case SteamInfo.ID:
info = new SteamInfo(secret, algo, digits, parsePeriod());
break;
case HotpInfo.ID:
long counter;
try {
counter = Long.parseLong(_textCounter.getText().toString());
@@ -555,7 +553,7 @@ public class EditEntryActivity extends AegisActivity {
private int getStringResourceIndex(@ArrayRes int id, String string) {
String[] res = getResources().getStringArray(id);
for (int i = 0; i < res.length; i++) {
if (res[i].equals(string)) {
if (res[i].equalsIgnoreCase(string)) {
return i;
}
}
@@ -8,6 +8,10 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.helpers.comparators.IssuerNameComparator;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import android.os.Bundle;
import android.view.Menu;
@@ -27,6 +31,8 @@ import com.beemdevelopment.aegis.ui.views.EntryListView;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import com.beemdevelopment.aegis.AegisApplication;
@@ -37,7 +43,6 @@ import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.db.DatabaseManager;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
public class MainActivity extends AegisActivity implements EntryListView.Listener {
// activity request codes
@@ -56,6 +61,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private DatabaseManager _db;
private boolean _loaded;
private String _checkedGroup;
private SortCategory _sortCategory;
private Menu _menu;
private FloatingActionsMenu _fabMenu;
@@ -77,6 +83,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setShowAccountName(getPreferences().isAccountNameVisible());
_entryListView.setTapToReveal(getPreferences().isTapToRevealEnabled());
_entryListView.setTapToRevealTime(getPreferences().getTapToRevealTime());
_entryListView.setViewMode(ViewMode.fromInteger(getPreferences().getCurrentViewMode()));
// set up the floating action button
_fabMenu = findViewById(R.id.fab);
@@ -170,10 +177,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
boolean showAccountName = getPreferences().isAccountNameVisible();
boolean tapToReveal = getPreferences().isTapToRevealEnabled();
int tapToRevealTime = getPreferences().getTapToRevealTime();
int viewMode = getPreferences().getCurrentViewMode();
_entryListView.setShowAccountName(showAccountName);
_entryListView.setTapToReveal(tapToReveal);
_entryListView.setTapToRevealTime(tapToRevealTime);
_entryListView.refresh(true);
}
}
@@ -258,6 +268,22 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setGroupFilter(group);
}
private void setSortCategory(SortCategory sortCategory) {
_sortCategory = sortCategory;
if(sortCategory == SortCategory.CUSTOM)
{
_entryListView.clearEntries();
loadEntries();
}
_entryListView.setSortCategory(sortCategory);
}
private void updateSortCategoryMenu(SortCategory sortCategory) {
_menu.findItem(SortCategory.getMenuItem(sortCategory)).setChecked(true);
}
private void addEntry(DatabaseEntry entry) {
_db.addEntry(entry);
_entryListView.addEntry(entry);
@@ -369,8 +395,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
Dialogs.showDeleteEntryDialog(this, (d, which) -> {
deleteEntry(entry);
// update the filter list if the group no longer exists
if (!_db.getGroups().contains(entry.getGroup())) {
updateGroupFilterMenu();
if (entry.getGroup() != null) {
if (!_db.getGroups().contains(entry.getGroup())) {
updateGroupFilterMenu();
}
}
});
});
@@ -397,6 +425,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
updateLockIcon();
if (_loaded) {
updateGroupFilterMenu();
updateSortCategoryMenu(SortCategory.fromInteger(getPreferences().getCurrentSortCategory()));
}
return true;
}
@@ -421,6 +450,37 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
setGroupFilter(group);
}
if (item.getGroupId() == R.id.action_sort_category) {
item.setChecked(true);
SortCategory sortCategory;
switch (item.getItemId()) {
case R.id.menu_sort_alphabetically:
sortCategory = SortCategory.ISSUER;
break;
case R.id.menu_sort_alphabetically_reverse:
sortCategory = SortCategory.ISSUERREVERSED;
break;
case R.id.menu_sort_alphabetically_name:
sortCategory = SortCategory.ACCOUNT;
break;
case R.id.menu_sort_alphabetically_name_reverse:
sortCategory = SortCategory.ACCOUNTREVERSED;
break;
case R.id.menu_sort_custom:
default:
sortCategory = SortCategory.CUSTOM;
break;
}
setSortCategory(sortCategory);
getPreferences().setCurrentSortCategory(sortCategory);
}
return super.onOptionsItemSelected(item);
}
}
@@ -458,11 +518,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
loadEntries();
SortCategory currentSortCategory = SortCategory.fromInteger(getPreferences().getCurrentSortCategory());
setSortCategory(currentSortCategory);
}
private void loadEntries() {
// load all entries
_entryListView.addEntries(_db.getEntries());
List<DatabaseEntry> entries = new ArrayList<DatabaseEntry>(_db.getEntries());
_entryListView.addEntries(entries);
_loaded = true;
}
@@ -527,8 +590,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
super.onAnimationEnd(animation);
}
}).start();
}
else if (dy < 0 && _fabMenu.getVisibility() != View.VISIBLE && !isAnimating) {
} else if (dy < 0 && _fabMenu.getVisibility() != View.VISIBLE && !isAnimating) {
_fabMenu.setVisibility(View.VISIBLE);
_fabMenu.animate()
.translationY(0)
@@ -2,6 +2,9 @@ package com.beemdevelopment.aegis.ui;
import android.os.Bundle;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
public class PreferencesActivity extends AegisActivity {
private PreferencesFragment _fragment;
@@ -36,4 +39,21 @@ public class PreferencesActivity extends AegisActivity {
outState.putParcelable("result", _fragment.getResult());
super.onSaveInstanceState(outState);
}
@Override
protected void setPreferredTheme(Theme theme) {
switch (theme) {
case LIGHT:
setTheme(R.style.AppTheme);
break;
case DARK:
setTheme(R.style.AppTheme_Dark);
break;
case AMOLED:
setTheme(R.style.AppTheme_TrueBlack_Preferences);
break;
}
}
}
@@ -2,6 +2,9 @@ package com.beemdevelopment.aegis.ui;
import android.Manifest;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.MediaScannerConnection;
@@ -9,11 +12,18 @@ import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.Toast;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.Theme;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
@@ -21,9 +31,12 @@ import com.beemdevelopment.aegis.importers.AegisFileImporter;
import com.beemdevelopment.aegis.importers.DatabaseAppImporter;
import com.beemdevelopment.aegis.importers.DatabaseFileImporter;
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.google.android.material.snackbar.Snackbar;
import com.takisoft.preferencex.PreferenceFragmentCompat;
import java.io.FileNotFoundException;
@@ -86,7 +99,33 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
// set the result intent in advance
setResult(new Intent());
int currentTheme = app.getPreferences().getCurrentTheme();
Preference darkModePreference = findPreference("pref_dark_mode");
darkModePreference.setSummary("Selected: " + Theme.getThemeName(currentTheme));
darkModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
String[] themeNames = Theme.getThemeNames();
int checkedTheme = app.getPreferences().getCurrentTheme();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_theme))
.setSingleChoiceItems(themeNames, checkedTheme, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
app.getPreferences().setCurrentTheme(Theme.fromInteger(i));
dialog.dismiss();
_result.putExtra("needsRecreate", true);
getActivity().recreate();
})
.setPositiveButton(android.R.string.ok, null)
.create());
return true;
}
});
darkModePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
@@ -96,6 +135,33 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
});
int currentViewMode = app.getPreferences().getCurrentViewMode();
Preference viewModePreference = findPreference("pref_view_mode");
viewModePreference.setSummary("Selected: " + ViewMode.getViewModeName(currentViewMode));
viewModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
String[] viewModes = ViewMode.getViewModeNames();
int checkedMode = app.getPreferences().getCurrentViewMode();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_view_mode))
.setSingleChoiceItems(viewModes, checkedMode, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
app.getPreferences().setCurrentViewMode(ViewMode.fromInteger(i));
dialog.dismiss();
viewModePreference.setSummary("Selected: " + ViewMode.getViewModeName(i));
_result.putExtra("needsRecreate", true);
})
.setPositiveButton(android.R.string.ok, null)
.create());
return true;
}
});
Preference importPreference = findPreference("pref_import");
importPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
@@ -434,7 +500,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
private void importDatabase(DatabaseImporter importer) throws DatabaseImporterException {
List<DatabaseEntry> entries = importer.convert();
DatabaseImporterResult result = importer.convert();
List<DatabaseEntry> entries = result.getEntries();
List<DatabaseImporterEntryException> errors = result.getErrors();
for (DatabaseEntry entry : entries) {
// temporary: randomize the UUID of duplicate entries and add them anyway
if (_db.getEntryByUUID(entry.getUUID()) != null) {
@@ -449,7 +518,30 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
_result.putExtra("needsRecreate", true);
Toast.makeText(getActivity(), String.format(Locale.getDefault(), getString(R.string.imported_entries_count), entries.size()), Toast.LENGTH_LONG).show();
Snackbar bar = Snackbar.make(getView(), String.format(Locale.getDefault(), getString(R.string.imported_entries_count), entries.size(), errors.size()), Snackbar.LENGTH_LONG);
if (errors.size() > 0) {
bar.setAction(R.string.details, v -> {
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(getActivity())
.setTitle(R.string.import_error_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(android.R.string.copy, (dialog, which) -> {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", message);
clipboard.setPrimaryClip(clip);
Toast.makeText(getActivity(), getString(R.string.errors_copied), Toast.LENGTH_SHORT).show();
})
.create());
});
}
bar.show();
}
private void onExport() {
@@ -8,6 +8,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.beemdevelopment.aegis.Theme;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.beemdevelopment.aegis.helpers.SquareFinderView;
@@ -57,7 +58,7 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
}
@Override
protected void setPreferredTheme(boolean darkMode) {
protected void setPreferredTheme(Theme theme) {
setTheme(R.style.AppTheme_Fullscreen);
}
@@ -5,7 +5,10 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.helpers.ItemTouchHelperAdapter;
import com.beemdevelopment.aegis.helpers.comparators.IssuerNameComparator;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
@@ -27,6 +30,9 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
private boolean _tapToReveal;
private int _tapToRevealTime;
private String _groupFilter;
private SortCategory _sortCategory;
private ViewMode _viewMode;
// keeps track of the viewholders that are currently bound
private List<EntryHolder> _holders;
@@ -151,6 +157,25 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
notifyDataSetChanged();
}
public void setSortCategory(SortCategory sortCategory) {
if (_sortCategory != sortCategory && sortCategory != SortCategory.CUSTOM) {
Collections.sort(_shownEntries, SortCategory.getComparator(sortCategory));
if(SortCategory.isReversed(sortCategory))
{
Collections.reverse(_shownEntries);
}
notifyDataSetChanged();
}
_sortCategory = sortCategory;
}
public void setViewMode(ViewMode viewMode) {
_viewMode = viewMode;
}
@Override
public void onItemDismiss(int position) {
@@ -184,7 +209,13 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
@Override
public EntryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry, parent, false);
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry_compact, parent, false);
if (_viewMode == ViewMode.NORMAL) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry, parent, false);
} else if (_viewMode == ViewMode.COMPACT) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry_compact, parent, false);
}
return new EntryHolder(view);
}
@@ -13,6 +13,8 @@ import com.amulyakhare.textdrawable.TextDrawable;
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
import com.beemdevelopment.aegis.helpers.UiRefresher;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.R;
@@ -27,6 +29,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private DatabaseEntry _entry;
private ImageView _buttonRefresh;
private View _entryDivider;
private View _currentView;
private boolean _hidden;
@@ -47,6 +51,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_profileDrawable = view.findViewById(R.id.ivTextDrawable);
_buttonRefresh = view.findViewById(R.id.buttonRefresh);
_entryDivider = view.findViewById(R.id.entryDivider);
_progressBar = view.findViewById(R.id.progressBar);
int primaryColorId = view.getContext().getResources().getColor(R.color.colorPrimary);
_progressBar.getProgressDrawable().setColorFilter(primaryColorId, PorterDuff.Mode.SRC_IN);
@@ -78,6 +84,10 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_progressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE);
if (showProgress) {
_progressBar.setPeriod(((TotpInfo)entry.getInfo()).getPeriod());
if (_entryDivider != null) {
_entryDivider.setVisibility(View.GONE);
}
}
// only show the button if this entry is of type HotpInfo
@@ -131,10 +141,18 @@ public class EntryHolder extends RecyclerView.ViewHolder {
}
private void updateCode() {
String otp = _entry.getInfo().getOtp();
String text = otp.substring(0, (otp.length() / 2)
+ (otp.length() % 2)) + " "
+ otp.substring(otp.length() / 2);
OtpInfo info = _entry.getInfo();
String text;
if (info instanceof SteamInfo) {
text = info.getOtp();
} else {
String otp = info.getOtp();
text = otp.substring(0, (otp.length() / 2)
+ (otp.length() % 2)) + " "
+ otp.substring(otp.length() / 2);
}
_profileCode.setText(text);
}
@@ -1,10 +1,8 @@
package com.beemdevelopment.aegis.ui.views;
import android.content.Context;
import android.graphics.PorterDuff;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -15,6 +13,8 @@ import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.helpers.SimpleItemTouchHelperCallback;
import com.beemdevelopment.aegis.helpers.UiRefresher;
import com.beemdevelopment.aegis.otp.TotpInfo;
@@ -35,8 +35,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private UiRefresher _refresher;
private RecyclerView.OnScrollChangeListener _onScrollListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -49,8 +47,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
View view = inflater.inflate(R.layout.fragment_entry_list_view, container, false);
_progressBar = view.findViewById(R.id.progressBar);
int primaryColorId = getResources().getColor(R.color.colorPrimary);
_progressBar.getProgressDrawable().setColorFilter(primaryColorId, PorterDuff.Mode.SRC_IN);
// set up the recycler view
_rvKeyProfiles = view.findViewById(R.id.rvKeyProfiles);
@@ -96,6 +92,17 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
runLayoutAnimation(_rvKeyProfiles);
}
public void setSortCategory(SortCategory sortCategory) {
_touchCallback.setIsLongPressDragEnabled(sortCategory == SortCategory.CUSTOM);
_adapter.setSortCategory(sortCategory);
runLayoutAnimation(_rvKeyProfiles);
}
public void setViewMode(ViewMode viewMode) {
_adapter.setViewMode(viewMode);
}
public void refresh(boolean hard) {
if (_showProgress) {
_progressBar.refresh();
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="#FF000000"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

@@ -0,0 +1,10 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@color/colorAccent"/>
</shape>
</clip>
</item>
</layer-list>
@@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/background"
tools:context="com.beemdevelopment.aegis.com.impy.aegis.ui.AuthActivity">
<LinearLayout
android:orientation="vertical"
@@ -2,6 +2,8 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:background="?attr/background"
android:layout_height="match_parent">
<LinearLayout
@@ -5,6 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/background"
tools:context="com.beemdevelopment.aegis.com.impy.aegis.ui.GroupManagerActivity">
<androidx.core.widget.NestedScrollView
@@ -5,6 +5,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="?attr/background"
tools:context="com.beemdevelopment.aegis.com.impy.aegis.ui.SlotManagerActivity">
@@ -2,6 +2,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="?attr/background"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
@@ -11,7 +12,7 @@
android:id="@+id/copy_button"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:background="?attr/background"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
@@ -31,7 +32,7 @@
android:padding="16dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:background="?attr/background"
android:id="@+id/edit_button"
android:orientation="horizontal">
<ImageView
@@ -53,7 +54,7 @@
android:id="@+id/delete_button"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<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:clickable="true"
android:focusable="true"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:background="?attr/cardBackground"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="16dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivTextDrawable"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
/>
</RelativeLayout>
<RelativeLayout
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="6dp"
android:paddingBottom="8dp"
android:paddingEnd="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingStart="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/profile_issuer"
android:text="@string/issuer"
android:textColor="?attr/secondaryText"
android:includeFontPadding="false"
android:textSize="13sp"
android:layout_below="@+id/profile_code"
android:layout_alignLeft="@+id/profile_code"
android:layout_alignStart="@+id/profile_code"
android:ellipsize="end"
android:maxLines="1"/>
<TextView
android:id="@+id/profile_account_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/profile_code"
android:layout_toRightOf="@id/profile_issuer"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/extra_info_text"
android:textSize="13sp"
tools:text=" - AccountName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="sans-serif-light"
tools:text="012 345"
android:id="@+id/profile_code"
android:includeFontPadding="false"
android:textSize="26sp"
android:textColor="?attr/codePrimaryText"
android:layout_marginLeft="6dp"
android:layout_marginStart="6dp"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="0dp"
android:textStyle="normal|bold"/>
</RelativeLayout>
<ImageView
android:id="@+id/buttonRefresh"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_refresh_black_24dp"
android:tint="?attr/iconColorPrimary"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.beemdevelopment.aegis.ui.views.PeriodProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="3dp"
android:id="@+id/progressBar"
android:max="1000"
android:layout_weight="1"/>
</LinearLayout>
<View
android:id="@+id/entryDivider"
android:layout_width="match_parent"
android:layout_height="0.1dp"
android:background="@color/divider" />
</LinearLayout>
@@ -10,6 +10,7 @@
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="4dp"
android:progressDrawable="@drawable/progress_horizontal"
android:id="@+id/progressBar"
android:visibility="gone"
android:max="1000"/>
+29
View File
@@ -21,6 +21,35 @@
android:title="@string/all"
android:checked="true" />
</group>
</menu>
</item>
<item
android:id="@+id/action_sort"
android:icon="@drawable/ic_action_sort"
app:showAsAction="always"
android:title="@string/filter">
<menu>
<group
android:id="@+id/action_sort_category"
android:checkableBehavior="single">
<item
android:id="@+id/menu_sort_custom"
android:title="@string/sort_custom"
android:checked="true" />
<item
android:id="@+id/menu_sort_alphabetically_name"
android:title="@string/sort_alphabetically_name"/>
<item
android:id="@+id/menu_sort_alphabetically_name_reverse"
android:title="@string/sort_alphabetically_name_reverse"/>
<item
android:id="@+id/menu_sort_alphabetically"
android:title="@string/sort_alphabetically"/>
<item
android:id="@+id/menu_sort_alphabetically_reverse"
android:title="@string/sort_alphabetically_reverse"/>
</group>
</menu>
</item>
<item
+144
View File
@@ -0,0 +1,144 @@
<resources>
<string name="app_name">Aegis</string>
<string name="app_name_dev">AegisDev</string>
<string name="action_settings">Настройки</string>
<string name="action_import">Импорт</string>
<string name="action_delete">Удалить</string>
<string name="action_default_icon">Восстановить значок по умолчанию</string>
<string name="discard">Отмена</string>
<string name="save">Сохранить</string>
<string name="issuer">Эмитент</string>
<string name="title_activity_intro">IntroActivity</string>
<string name="settings">Предпочтения</string>
<string name="pref_select_theme_title">Тема</string>
<string name="pref_view_mode_title">Select view mode</string>
<string name="pref_account_name_title">Показать имя аккаунта</string>
<string name="pref_account_name_summary">Включите это, чтобы показать имя учетной записи рядом с эмитентом</string>
<string name="pref_timeout_title">Тайм-аут</string>
<string name="pref_timeout_summary">Автоматическая блокировка базы данных после %1$s секунд бездействия</string>
<string name="pref_slots_title">Ключевые слоты</string>
<string name="pref_slots_summary">Управление списком ключей которые могут расшифровать базу данных</string>
<string name="pref_import_file_title">Импорт из файла</string>
<string name="pref_import_file_summary">Импортировать базу данных из файла</string>
<string name="pref_import_app_title">Импорт из приложения</string>
<string name="pref_import_app_summary">Импорт базы данных из приложения (требуется root-доступ)</string>
<string name="pref_export_title">Экспорт</string>
<string name="pref_export_summary">Экспорт базы данных</string>
<string name="pref_secure_screen_title">Безопасность экрана</string>
<string name="pref_secure_screen_summary">Блокировка скриншотов и других попыток захвата экрана в приложении</string>
<string name="pref_tap_to_reveal_title">Нажмите, чтобы показать</string>
<string name="pref_tap_to_reveal_summary">Токены будут скрыты по умолчанию. Нажмите на токены, чтобы показать код.</string>
<string name="pref_tap_to_reveal_time_title">Тайм-аут для показа экрана</string>
<string name="pref_encryption_title">Шифрование</string>
<string name="pref_encryption_summary">Зашифруйте базу данных и разблокируйте ее с помощью пароля или отпечатка пальца</string>
<string name="pref_fingerprint_title">Отпечаток пальца</string>
<string name="pref_fingerprint_summary">Разрешить отпечатки пальцев зарегистрированных на этом устройстве, чтобы разблокировать хранилище</string>
<string name="pref_set_password_title">Изменить пароль</string>
<string name="pref_set_password_summary">Установите новый пароль который вам понадобится, чтобы разблокировать ваше хранилище</string>
<string name="fingerprint_hint">Сенсорный датчик</string>
<string name="fingerprint_not_recognized">Отпечаток пальца не распознан. Попробуйте снова.</string>
<string name="fingerprint_success">Отпечаток пальца распознан</string>
<string name="choose_authentication_method">Безопасность</string>
<string name="authentication_method_none">Нет</string>
<string name="authentication_method_none_description">Вам не нужен пароль для разблокировки хранилища, и он не будет зашифрован. Этот вариант не рекомендуется.</string>
<string name="authentication_method_password">Пароль</string>
<string name="authentication_method_password_description">Вам нужен пароль чтобы разблокировать хранилище.</string>
<string name="authentication_method_fingerprint">Отпечаток</string>
<string name="authentication_method_fingerprint_description">В дополнение к паролю для разблокировки хранилища можно использовать отпечатки пальцев, зарегистрированные на этом устройстве.</string>
<string name="authentication_method_set_password">Пароль</string>
<string name="authentication_enter_password">Введите свой пароль</string>
<string name="authentication">Разблокировать хранилище</string>
<string name="set_password">Пожалуйста, введите пароль</string>
<string name="set_group">Пожалуйста, введите название группы</string>
<string name="set_number">Пожалуйста, введите номер</string>
<string name="set_password_confirm">Пожалуйста, подтвердите пароль</string>
<string name="invalidated_fingerprint">Обнаружено изменение настроек безопасности вашего устройства. Перейдите в «Настройки - Отпечаток пальца» и повторно добавьте свой отпечаток пальца.</string>
<string-array name="authentication_methods">
<item>Нет</item>
<item>Пароль</item>
<item>Отпечаток пальца</item>
</string-array>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Привет пустой фрагмент</string>
<string name="unlock">Разблокировать</string>
<string name="advanced">Расширенные</string>
<string name="seconds">секунд</string>
<string name="counter">Счетчик</string>
<string name="digits">Цифры</string>
<string name="secret">Секрет</string>
<string name="scan">Сканировать QR-код</string>
<string name="enter_manually">Введите вручную</string>
<string name="add_fingerprint">Добавить отпечаток</string>
<string name="add_password">Добавить паспорт</string>
<string name="slots_warning">Хранилище надежно защищено. При добавлении нового отпечатка пальца на ваше устройство вы активируете аутентификацию по отпечатку пальца в Aegis.</string>
<string name="copy">Копировать</string>
<string name="edit">Изменить</string>
<string name="delete">Удалить</string>
<string name="password">Пароль</string>
<string name="confirm_password">Подтвердите пароль</string>
<string name="new_profile">Новый профиль</string>
<string name="add_new_profile">Добавить новый профиль</string>
<string name="unlock_vault_error">Не удалось разблокировать хранилище</string>
<string name="unlock_vault_error_description">Неверный пароль. Убедитесь, что вы не опечатали свой пароль.</string>
<string name="password_equality_error">Пароли должны быть одинаковыми и непустыми</string>
<string name="register_fingerprint">Зарегистрируйте свой отпечаток пальца</string>
<string name="snackbar_authentication_method">Пожалуйста, выберите метод аутентификации</string>
<string name="encrypting_vault">Шифрование хранилища</string>
<string name="delete_entry">Удалить запись</string>
<string name="delete_entry_description">Вы уверены что хотите удалить эту запись?</string>
<string name="discard_changes">Отменить изменения?</string>
<string name="discard_changes_description">Ваши изменения не были сохранены</string>
<string name="folder">Папка</string>
<string name="tap_to_select">Нажмите, чтобы выбрать</string>
<string name="saving_profile_error">Ошибка сохранения профиля</string>
<string name="welcome">Добро пожаловать</string>
<string name="app_description">Aegis - бесплатное, безопасное и открытое приложение 2FA.</string>
<string name="setup_completed">Настройка завершена</string>
<string name="setup_completed_description">Aegis настроен и готов к работе.</string>
<string name="vault_not_found">Хранилище не найдено, запуск установки…</string>
<string name="code_copied">Код скопирован в буфер обмена</string>
<string name="decryption_error">Произошла ошибка при попытке разблокировать хранилище</string>
<string name="saving_error">Произошла ошибка при попытке сохранить хранилище</string>
<string name="disable_encryption">Отключить шифрование</string>
<string name="disable_encryption_description">Вы уверены что хотите отключить шифрование? Это приведет к хранению хранилища в виде простого текста.</string>
<string name="encrypting_error">Произошла ошибка при включении шифрования</string>
<string name="permission_denied">Доступ запрещен</string>
<string name="choose_application">Выберите приложение из которого вы хотите импортировать базу данных.</string>
<string name="choose_theme">Выберите тему</string>
<string name="parsing_file_error">Произошла ошибка при попытке разобрать файл</string>
<string name="file_not_found">Ошибка: файл не найден</string>
<string name="reading_file_error">Произошла ошибка при попытке прочитать файл</string>
<string name="root_error">Ошибка: невозможно получить root-доступ</string>
<string name="imported_entries_count">Импортироаано %d записей. %d ошибки.</string>
<string name="exporting_database_error">Произошла ошибка при попытке экспорта базы данных</string>
<string name="export_database_location">База данных была экспортирована в:</string>
<string name="export_warning">Это действие экспортирует базу данных из личного хранилища Aegis.</string>
<string name="encryption_set_password_error">Произошла ошибка при попытке установить пароль:</string>
<string name="encryption_enable_fingerprint_error">Произошла ошибка при попытке разблокировать отпечаток пальца:</string>
<string name="no_cameras_available">Нет доступных камер</string>
<string name="read_qr_error">Произошла ошибка при попытке прочитать QR-код</string>
<string name="authentication_method_raw">Необработанный</string>
<string name="unlocking_vault">Разблокировка хранилища</string>
<string name="password_slot_error">У вас должен быть хотя бы один слот для пароля</string>
<string name="remove_slot">Удалить слот</string>
<string name="remove_slot_description">Вы уверены что хотите удалить этот слот?</string>
<string name="remove_group">Удалить группу</string>
<string name="remove_group_description">Вы уверены что хотите удалить эту группу? Записи в этой группе автоматически переключатся на «Нет группы».</string>
<string name="adding_new_slot_error">Произошла ошибка при попытке добавить новый слот:</string>
<string name="filter">Фильтр</string>
<string name="lock">Заблокировать</string>
<string name="all">Все</string>
<string name="name">Имя</string>
<string name="no_group">Нет группы</string>
<string name="new_group">Новая группа...</string>
<string name="enter_group_name">Введите название группы</string>
<string name="group_name_hint">Название группы</string>
<string name="preference_manage_groups">Редактировать группы</string>
<string name="preference_manage_groups_summary">Управляйте и удаляйте свои группы здесь</string>
<string name="tap_to_reveal">Скрытый</string>
</resources>
+1
View File
@@ -35,6 +35,7 @@
<color name="hint_text_dark">#a7a7a7</color>
<color name="secondary_text_dark">#FF5252</color>
<color name="background_dark">#212121</color>
<color name="background_true_dark">#000000</color>
<color name="warning_color">#f4511e</color>
<color name="hint_color">#42000000</color>
+14 -3
View File
@@ -11,8 +11,8 @@
<string name="title_activity_intro">IntroActivity</string>
<string name="settings">Preferences</string>
<string name="pref_dark_mode_title">Dark mode</string>
<string name="pref_dark_mode_summary">Enable this to use darker colors</string>
<string name="pref_select_theme_title">Theme</string>
<string name="pref_view_mode_title">View mode</string>
<string name="pref_account_name_title">Show the account name</string>
<string name="pref_account_name_summary">Enable this to show the account name next to the issuer</string>
<string name="pref_timeout_title">Timeout</string>
@@ -66,6 +66,7 @@
<string-array name="otp_types_array">
<item>TOTP</item>
<item>HOTP</item>
<item>Steam</item>
</string-array>
<string-array name="otp_algo_array">
@@ -113,6 +114,7 @@
<string name="setup_completed_description">Aegis has been setup and is ready to go.</string>
<string name="vault_not_found">Vault not found, starting setup…</string>
<string name="code_copied">Code copied to the clipboard</string>
<string name="errors_copied">Errors copied to the clipboard</string>
<string name="decryption_error">An error occurred while trying to unlock the vault</string>
<string name="saving_error">An error occurred while trying to save the vault</string>
<string name="disable_encryption">Disable encryption</string>
@@ -120,11 +122,14 @@
<string name="encrypting_error">An error occurred while enabling encryption</string>
<string name="permission_denied">Permission denied</string>
<string name="choose_application">Select the application you\'d like to import a database from</string>
<string name="choose_theme">Select your desired theme</string>
<string name="choose_view_mode">Select your desired view mode</string>
<string name="parsing_file_error">An error occurred while trying to parse the file</string>
<string name="file_not_found">Error: File not found</string>
<string name="reading_file_error">An error occurred while trying to read the file</string>
<string name="root_error">Error: unable to obtain root access</string>
<string name="imported_entries_count">Imported %d entries</string>
<string name="imported_entries_count">Imported %d entries. %d errors.</string>
<string name="import_error_title">One or more errors occurred during the import</string>
<string name="exporting_database_error">An error occurred while trying to export the database</string>
<string name="export_database_location">The database has been exported to:</string>
<string name="export_warning">This action will export the database out of Aegis\' private storage.</string>
@@ -140,11 +145,17 @@
<string name="remove_group">Remove group</string>
<string name="remove_group_description">Are you sure you want to remove this group? Entries in this group will automatically switch to \'No group\'.</string>
<string name="adding_new_slot_error">An error occurred while trying to add a new slot:</string>
<string name="details">Details</string>
<string name="filter">Filter</string>
<string name="lock">Lock</string>
<string name="all">All</string>
<string name="name">Name</string>
<string name="no_group">No group</string>
<string name="sort_alphabetically">Issuer (A to Z)</string>
<string name="sort_alphabetically_reverse">Issuer (Z to A)</string>
<string name="sort_alphabetically_name">Account (A to Z)</string>
<string name="sort_alphabetically_name_reverse">Account (Z to A)</string>
<string name="sort_custom">Custom</string>
<string name="new_group">New group...</string>
<string name="enter_group_name">Enter a group name</string>
<string name="group_name_hint">Group name</string>
+14
View File
@@ -66,6 +66,20 @@
<item name="swirl_ridgeColor">@color/colorSwirlPrimaryDark</item>
<item name="swirl_errorColor">@color/colorSwirlErrorDark</item>
</style>
<style name="AppTheme.TrueBlack" parent="AppTheme.Dark">
<item name="colorPrimary">@color/background_true_dark</item>
<item name="cardBackground">@color/background_true_dark</item>
<item name="background">@color/background_true_dark</item>
<item name="bottomSheetDialogTheme">@style/Theme.Design.BottomSheetDialog</item>
<item name="colorPrimaryDark">@color/background_true_dark</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="AppTheme.TrueBlack.Preferences" parent="AppTheme.TrueBlack">
<item name="android:windowBackground">@color/background_true_dark</item>
</style>
<style name="AppTheme.Dark.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
+8 -3
View File
@@ -7,11 +7,16 @@
<PreferenceCategory
android:title="Appearance"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
<Preference
android:defaultValue="false"
android:key="pref_dark_mode"
android:title="@string/pref_dark_mode_title"
android:summary="@string/pref_dark_mode_summary"
android:title="@string/pref_select_theme_title"
app:iconSpaceReserved="false"/>
<Preference
android:defaultValue="false"
android:key="pref_view_mode"
android:title="@string/pref_view_mode_title"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
@@ -6,6 +6,7 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import com.beemdevelopment.aegis.crypto.otp.HOTP;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import static org.junit.Assert.*;
@@ -27,8 +28,8 @@ public class HOTPTest {
@Test
public void vectorsMatch() throws InvalidKeyException, NoSuchAlgorithmException {
for (int i = 0; i < _vectors.length; i++) {
String otp = HOTP.generateOTP(_secret, "HmacSHA1", 6, i);
assertEquals(_vectors[i], otp);
OTP otp = HOTP.generateOTP(_secret, "HmacSHA1", 6, i);
assertEquals(_vectors[i], otp.toString());
}
}
}
@@ -2,6 +2,7 @@ package com.beemdevelopment.aegis;
import org.junit.Test;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import com.beemdevelopment.aegis.encoding.Hex;
import com.beemdevelopment.aegis.encoding.HexException;
@@ -87,8 +88,8 @@ public class TOTPTest {
return;
}
String otp = TOTP.generateOTP(seed, vector.Algo, 8, 30, vector.Time);
assertEquals(vector.OTP, otp);
OTP otp = TOTP.generateOTP(seed, vector.Algo, 8, 30, vector.Time);
assertEquals(vector.OTP, otp.toString());
}
}
}
@@ -30,3 +30,10 @@ To make sure you will never lose access to your online accounts Aegis
Authenticator supports exporting your vault which you can import onto a new
device. Aegis Authenticator also allows you to import AndOTP and FreeOTP
databases so switching to Aegis is made easier for you.
'''Open source and license'''
Aegis Authenticator is open source (licensed under GPL v3) and the source code
can be found here: http://github.com/beemdevelopment/Aegis
'''Attribution'''
The icon is largely based on a design by Freepik from Flaticon.
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB