From 4c977a6371b5a6dc26fcb99274ce279a86a439d4 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 28 Nov 2021 19:27:40 +0100 Subject: [PATCH 001/389] Disable the PIN keyboard when enabling encryption Fixes #854 --- .../aegis/ui/fragments/SecurityPreferencesFragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java index ae6f4dbb70..3595539a45 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java @@ -121,6 +121,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { .setNegativeButton(android.R.string.no, null) .create()); } + return false; }); @@ -383,6 +384,7 @@ public void onSlotResult(Slot slot, Cipher cipher) { } getActivity().startService(new Intent(getActivity(), NotificationService.class)); + _pinKeyboardPreference.setChecked(false); updateEncryptionPreferences(); } From 56ddefa2462fdba2dde32b044848528f12351714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sieva=20Kimaje=C5=AD?= Date: Wed, 27 Oct 2021 14:11:47 -0700 Subject: [PATCH 002/389] Add DUO importer. #68 --- app/src/main/AndroidManifest.xml | 1 + .../aegis/importers/DatabaseImporter.java | 1 + .../aegis/importers/DuoImporter.java | 103 ++++++++++++++++++ app/src/main/res/values-ru-rRU/strings.xml | 1 + app/src/main/res/values-uk-rUA/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 6 files changed, 108 insertions(+) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0f16a22bb..384c50f37e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -114,6 +114,7 @@ + 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 2e5fa4b42f..cd72dca4c0 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -32,6 +32,7 @@ public abstract class DatabaseImporter { _importers.add(new Definition("andOTP", AndOtpImporter.class, R.string.importer_help_andotp, false)); _importers.add(new Definition("Authenticator Plus", AuthenticatorPlusImporter.class, R.string.importer_help_authenticator_plus, false)); _importers.add(new Definition("Authy", AuthyImporter.class, R.string.importer_help_authy, true)); + _importers.add(new Definition("DUO", DuoImporter.class, R.string.importer_help_duo, true)); _importers.add(new Definition("FreeOTP", FreeOtpImporter.class, R.string.importer_help_freeotp, true)); _importers.add(new Definition("FreeOTP+", FreeOtpPlusImporter.class, R.string.importer_help_freeotp_plus, true)); _importers.add(new Definition("Google Authenticator", GoogleAuthImporter.class, R.string.importer_help_google_authenticator, true)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java new file mode 100644 index 0000000000..b449e940bb --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java @@ -0,0 +1,103 @@ +package com.beemdevelopment.aegis.importers; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.EncodingException; +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.IOUtils; +import com.beemdevelopment.aegis.vault.VaultEntry; +import com.topjohnwu.superuser.io.SuFile; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.io.InputStream; + +public class DuoImporter extends DatabaseImporter { + private static final String _pkgName = "com.duosecurity.duomobile"; + private static final String _subPath = "files/duokit/accounts.json"; + + public DuoImporter(Context context) { + super(context); + } + + @Override + protected @NonNull SuFile getAppPath() throws DatabaseImporterException, NameNotFoundException { + return getAppPath(_pkgName, _subPath); + } + + @Override + protected @NonNull State read( + @NonNull InputStream stream, boolean isInternal + ) throws DatabaseImporterException { + try { + String contents = new String(IOUtils.readAll(stream), UTF_8); + return new DecryptedState(new JSONArray(contents)); + } catch (JSONException | IOException e) { + throw new DatabaseImporterException(e); + } + } + + public static class DecryptedState extends DatabaseImporter.State { + private final JSONArray _array; + + public DecryptedState(@NonNull JSONArray array) { + super(false); + _array = array; + } + + @Override + public @NonNull Result convert() throws DatabaseImporterException { + Result result = new Result(); + + try { + for (int i = 0; i < _array.length(); i++) { + JSONObject entry = _array.getJSONObject(i); + try { + result.addEntry(convertEntry(entry)); + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } + } + } catch (JSONException e) { + throw new DatabaseImporterException(e); + } + + return result; + } + + private static @NonNull VaultEntry convertEntry( + @NonNull JSONObject entry + ) throws DatabaseImporterEntryException { + try { + String label = entry.optString("name"); + + JSONObject otpData = entry.getJSONObject("otpGenerator"); + + byte[] secret = Base32.decode(otpData.getString("otpSecret")); + + Long counter = otpData.has("counter") ? otpData.getLong("counter") : null; + + OtpInfo otp = counter == null + ? new TotpInfo(secret) + : new HotpInfo(secret, counter); + + return new VaultEntry(otp, label, ""); + } catch (JSONException | OtpInfoException | EncodingException e) { + throw new DatabaseImporterEntryException(e, entry.toString()); + } + } + } +} diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index cadf7b53c6..d5fe5aaf7e 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -370,6 +370,7 @@ Предоставьте файл экспорта Authenticator Plus, полученный через «Настройки» → «Рез. копия и восстановление» → «Экспорт текста и HTML». Предоставьте копию файла /data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml, расположенного в папке Authy во внутренней памяти. Предоставьте файл экспорта/резервной копии andOTP. + Предоставьте копию файла /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, расположенного в папке DUO во внутренней памяти. Предоставьте копию файла /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, расположенного в папке FreeOTP во внутренней памяти. Предоставьте файл экспорта FreeOTP+. Предоставьте копию файла /data/data/com.google.android.apps.authenticator2/databases/databases, расположенного в папке Google Authenticator во внутренней памяти. diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index cc4c4d86ef..2d7b675ef0 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -337,6 +337,7 @@ Надайте файл експорту Authenticator Plus, отриманий через Settings -> Backup & Restore -> Export as Text and HTML. Надайте копію /data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml, що знаходиться в каталозі Authy у внутрішній пам\'яті. Надайте файл експорту/резервної копії andOTP. + Надайте копію /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, що знаходиться в каталозі DUO у внутрішній пам\'яті. Надайте копію /data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, що знаходиться в каталозі FreeOTP у внутрішній пам\'яті. Надайте файл експортований з FreeOTP+. Надайте копію /data/com.google.android.apps.authenticator2/databases/databases, що знаходиться в каталозі Google Authenticator у внутрішній пам\'яті. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd888a74af..b737abfdfc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -391,6 +391,7 @@ Supply an Authenticator Plus export file obtained through Settings -> Backup & Restore -> Export as Text and HTML. Supply a copy of /data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml, located in the internal storage directory of Authy. Supply an andOTP export/backup file. + Supply a copy of /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, located in the internal storage directory of DUO. Supply a copy of /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, located in the internal storage directory of FreeOTP. Supply a FreeOTP+ export file. Supply a copy of /data/data/com.google.android.apps.authenticator2/databases/databases, located in the internal storage directory of Google Authenticator. From aec639cc53bf6042ad839f999bba291053770fff Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sat, 4 Dec 2021 11:40:29 +0100 Subject: [PATCH 003/389] Add a test for the Duo importer --- .../aegis/importers/DatabaseImporter.java | 2 +- .../aegis/importers/DuoImporter.java | 4 --- .../aegis/importers/DatabaseImporterTest.java | 13 ++++++++++ .../beemdevelopment/aegis/importers/duo.json | 25 +++++++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 app/src/test/resources/com/beemdevelopment/aegis/importers/duo.json 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 cd72dca4c0..63713b836a 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -32,7 +32,7 @@ public abstract class DatabaseImporter { _importers.add(new Definition("andOTP", AndOtpImporter.class, R.string.importer_help_andotp, false)); _importers.add(new Definition("Authenticator Plus", AuthenticatorPlusImporter.class, R.string.importer_help_authenticator_plus, false)); _importers.add(new Definition("Authy", AuthyImporter.class, R.string.importer_help_authy, true)); - _importers.add(new Definition("DUO", DuoImporter.class, R.string.importer_help_duo, true)); + _importers.add(new Definition("Duo", DuoImporter.class, R.string.importer_help_duo, true)); _importers.add(new Definition("FreeOTP", FreeOtpImporter.class, R.string.importer_help_freeotp, true)); _importers.add(new Definition("FreeOTP+", FreeOtpPlusImporter.class, R.string.importer_help_freeotp_plus, true)); _importers.add(new Definition("Google Authenticator", GoogleAuthImporter.class, R.string.importer_help_google_authenticator, true)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java index b449e940bb..687ab4333c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java @@ -6,7 +6,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.EncodingException; @@ -83,11 +82,8 @@ public DecryptedState(@NonNull JSONArray array) { ) throws DatabaseImporterEntryException { try { String label = entry.optString("name"); - JSONObject otpData = entry.getJSONObject("otpGenerator"); - byte[] secret = Base32.decode(otpData.getString("otpSecret")); - Long counter = otpData.has("counter") ? otpData.getLong("counter") : null; OtpInfo otp = counter == null diff --git a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java index 581b29f711..b213805984 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java @@ -81,6 +81,19 @@ public void testImportAegisEncrypted() throws IOException, DatabaseImporterExcep checkImportedEntries(entries); } + @Test + public void testImportDuo() throws IOException, DatabaseImporterException, OtpInfoException { + List entries = importPlain(DuoImporter.class, "duo.json"); + for (VaultEntry entry : entries) { + VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret()); + entryVector.setIssuer(""); + if (entryVector.getInfo() instanceof HotpInfo) { + ((HotpInfo) entry.getInfo()).setCounter(1); + } + checkImportedEntry(entryVector, entry); + } + } + @Test public void testImportWinAuth() throws IOException, DatabaseImporterException { List entries = importPlain(WinAuthImporter.class, "plain.txt"); diff --git a/app/src/test/resources/com/beemdevelopment/aegis/importers/duo.json b/app/src/test/resources/com/beemdevelopment/aegis/importers/duo.json new file mode 100644 index 0000000000..cbdd21dfe6 --- /dev/null +++ b/app/src/test/resources/com/beemdevelopment/aegis/importers/duo.json @@ -0,0 +1,25 @@ +[ + { + "version": 1.0, + "accountType": "OtpAccount", + "name": "Mason", + "otpGenerator": { + "otpSecret": "4SJHB4GSD43FZBAI7C2HLRJGPQ" + }, + "logoUri": "file:///data/user/0/com.duosecurity.duomobile/files/duokit/logos/9a011a29-0496-4c0b-b53a-b9125eea14a3.png", + "pkey": "9a011a29-0496-4c0b-b53a-b9125eea14a3", + "serviceTypeLabelIsCustom": true + }, + { + "version": 1.0, + "accountType": "OtpAccount", + "name": "James", + "otpGenerator": { + "otpSecret": "YOOMIXWS5GN6RTBPUFFWKTW5M4", + "counter": 3 + }, + "logoUri": "file:///data/user/0/com.duosecurity.duomobile/files/duokit/logos/907d30f0-d322-4fa5-baff-4b7c603f5141.png", + "pkey": "907d30f0-d322-4fa5-baff-4b7c603f5141", + "serviceTypeLabelIsCustom": true + } +] \ No newline at end of file From cf63f5bef2018afdc41c648e121e2d6b320d68b5 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sat, 4 Dec 2021 11:42:41 +0100 Subject: [PATCH 004/389] Fix the build --- .github/workflows/build-app-workflow.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-app-workflow.yaml b/.github/workflows/build-app-workflow.yaml index a68464493e..6786b083bf 100644 --- a/.github/workflows/build-app-workflow.yaml +++ b/.github/workflows/build-app-workflow.yaml @@ -7,6 +7,6 @@ jobs: - name: Checkout the code uses: actions/checkout@v2 - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@e2c57acffb2c9aa5a8dc6eda2bbae0b6e495bd4c + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b - name: Build the app run: ./gradlew build From 153f5e09d81604a616655c5b2f9fc23fcea4b0ba Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sat, 4 Dec 2021 11:54:02 +0100 Subject: [PATCH 005/389] Update dependencies --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b1b33ecdbd..edba9d0013 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -128,18 +128,18 @@ dependencies { def junitVersion = '4.13.2' def libsuVersion = '3.1.2' - annotationProcessor 'androidx.annotation:annotation:1.2.0' + annotationProcessor 'androidx.annotation:annotation:1.3.0' annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}" implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.4.0' implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.camera:camera-camera2:$cameraxVersion" implementation "androidx.camera:camera-lifecycle:$cameraxVersion" - implementation "androidx.camera:camera-view:1.0.0-alpha30" + implementation "androidx.camera:camera-view:1.0.0-alpha31" implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.core:core:1.7.0" - implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' implementation 'androidx.documentfile:documentfile:1.0.1' implementation "androidx.lifecycle:lifecycle-process:2.4.0" implementation 'androidx.preference:preference:1.1.1' From 6955eb10da6d95673450cb0c5915bb476c2e2357 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Mon, 6 Dec 2021 22:30:56 +0100 Subject: [PATCH 006/389] Add a GitHub Action for uploading sources to Crowdin --- .github/workflows/crowdin.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/crowdin.yml diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml new file mode 100644 index 0000000000..59f455fb5e --- /dev/null +++ b/.github/workflows/crowdin.yml @@ -0,0 +1,21 @@ +name: crowdin +on: + push: + branches: + - master +jobs: + upload-sources: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install crowdin-cli + run: | + wget https://github.com/crowdin/crowdin-cli/releases/download/3.2.1/crowdin-cli.zip + echo "953e8714ca114b21eabd8f588d4b1c9e8ac1d1df3621176fc08ce611200f3108 crowdin-cli.zip" | sha256sum -c + unzip crowdin-cli.zip -d crowdin-cli + - name: Upload to Crowdin + env: + CROWDIN_SETTINGS: "${{ secrets.CROWDIN_SETTINGS }}" + run: | + echo "$CROWDIN_SETTINGS" > ~/.crowdin.yml + java -jar ./crowdin-cli/3.2.1/crowdin-cli.jar push -c ~/.crowdin.yml -b master From 86580e38afe2e350b66b45d26715700b594e3205 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Mon, 6 Dec 2021 23:03:05 +0100 Subject: [PATCH 007/389] Update crowdin-cli to 3.7.2 --- .github/workflows/crowdin.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 59f455fb5e..9a637664e3 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -3,6 +3,8 @@ on: push: branches: - master +# run sequentially (per branch) +concurrency: "crowdin-upload-${{ github.ref }}" jobs: upload-sources: runs-on: ubuntu-latest @@ -10,12 +12,19 @@ jobs: - uses: actions/checkout@v2 - name: Install crowdin-cli run: | - wget https://github.com/crowdin/crowdin-cli/releases/download/3.2.1/crowdin-cli.zip - echo "953e8714ca114b21eabd8f588d4b1c9e8ac1d1df3621176fc08ce611200f3108 crowdin-cli.zip" | sha256sum -c + wget https://github.com/crowdin/crowdin-cli/releases/download/3.7.2/crowdin-cli.zip + echo "ee9f838b819ccedc33c9b2537055e5ba7d7934561b24df1e1a6274cbd6e27f2d crowdin-cli.zip" | sha256sum -c unzip crowdin-cli.zip -d crowdin-cli - name: Upload to Crowdin env: - CROWDIN_SETTINGS: "${{ secrets.CROWDIN_SETTINGS }}" + CROWDIN_TOKEN: "${{ secrets.CROWDIN_TOKEN }}" run: | - echo "$CROWDIN_SETTINGS" > ~/.crowdin.yml - java -jar ./crowdin-cli/3.2.1/crowdin-cli.jar push -c ~/.crowdin.yml -b master + java -jar ./crowdin-cli/3.7.2/crowdin-cli.jar upload sources \ + --no-progress \ + --token "$CROWDIN_TOKEN" \ + --project-id 372633 \ + --base-path app/src/main \ + --source res/values/strings.xml \ + --translation "res/values-%android_code%/%original_file_name%" \ + --dest strings.xml \ + --branch master From f9b4e8fa3e25c4984189f76196fcfc1041adccde Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Mon, 3 Jan 2022 19:52:36 +0100 Subject: [PATCH 008/389] Use the first grapheme for the TextDrawable icon Fixes #862 --- .../aegis/helpers/TextDrawableHelper.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/helpers/TextDrawableHelper.java b/app/src/main/java/com/beemdevelopment/aegis/helpers/TextDrawableHelper.java index 6d2cca95dc..fc66b0cbd6 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/helpers/TextDrawableHelper.java +++ b/app/src/main/java/com/beemdevelopment/aegis/helpers/TextDrawableHelper.java @@ -5,6 +5,7 @@ import com.amulyakhare.textdrawable.TextDrawable; import com.amulyakhare.textdrawable.util.ColorGenerator; +import java.text.BreakIterator; import java.util.Arrays; public class TextDrawableHelper { @@ -48,6 +49,18 @@ public static TextDrawable generate(String text, String fallback, View view) { .width(view.getLayoutParams().width) .height(view.getLayoutParams().height) .endConfig() - .buildRound(text.substring(0, 1).toUpperCase(), color); + .buildRound(getFirstGrapheme(text).toUpperCase(), color); + } + + private static String getFirstGrapheme(String text) { + BreakIterator iter = BreakIterator.getCharacterInstance(); + iter.setText(text); + + int start = iter.first(), end = iter.next(); + if (end == BreakIterator.DONE) { + return ""; + } + + return text.substring(start, end); } } From e54ac9aba495a261c424703ba5364c9feb28329d Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Mon, 3 Jan 2022 21:10:10 +0100 Subject: [PATCH 009/389] Update FUNDING.yml --- .github/FUNDING.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 63ea84c143..bb4c019b6a 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,4 @@ -custom: ['https://www.buymeacoffee.com/beemdevelopment'] +custom: + - "https://www.buymeacoffee.com/beemdevelopment" + - "https://www.blockchain.com/btc/address/bc1q26kyxqjkc6tu477pzy0whagwhs4ypv93qls22n" + - "https://nanocrawler.cc/explorer/account/nano_1aegisc559b1x4p3839egnu579jkd4htpidy14eo9e31gzqmwuafypnj4q94" From f14bd99f63ead16b27f24037aab4588a8950471e Mon Sep 17 00:00:00 2001 From: moesheyab Date: Sun, 16 Jan 2022 12:21:18 -0800 Subject: [PATCH 010/389] Fix Aegis import dialog message title --- .../com/beemdevelopment/aegis/importers/AegisImporter.java | 3 ++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java index 1b18ae6bdf..75d864738b 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/AegisImporter.java @@ -5,6 +5,7 @@ import androidx.lifecycle.Lifecycle; +import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.helpers.ContextHelper; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; @@ -83,7 +84,7 @@ public State decrypt(char[] password) throws DatabaseImporterException { @Override public void decrypt(Context context, DecryptListener listener) { - Dialogs.showPasswordInputDialog(context, (Dialogs.TextInputListener) password -> { + Dialogs.showPasswordInputDialog(context, R.string.enter_password_aegis_title, 0, (Dialogs.TextInputListener) password -> { List slots = getSlots().findAll(PasswordSlot.class); PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password); PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(context, result -> { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e50b47c9bd..447b6945da 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,6 +119,7 @@ A change in your device\'s security settings has been detected. Please go to \"Aegis -> Settings -> Security -> Biometric unlock\" to disable and re-enable biometric unlock. Please enter your password. We occasionally ask you to do this so that don\'t forget it. It looks like your Authy tokens are encrypted. Please close Aegis, open Authy and unlock the tokens with your password. Instead, Aegis can also attempt to decrypt your Authy tokens for you, if you enter your password below. + Please enter the import password Period (seconds) Hash function From af2bf6f683df813df091557fa89481857ef40d16 Mon Sep 17 00:00:00 2001 From: Mikhail Prokofev Date: Fri, 7 Jan 2022 12:49:53 +0300 Subject: [PATCH 011/389] Add Yandex OTP support --- .../beemdevelopment/aegis/OverallTest.java | 6 +- .../aegis/crypto/otp/HOTP.java | 27 +++-- .../aegis/crypto/otp/YAOTP.java | 83 ++++++++++++++ .../aegis/otp/GoogleAuthInfo.java | 108 +++++++++++------- .../beemdevelopment/aegis/otp/OtpInfo.java | 4 + .../beemdevelopment/aegis/otp/YandexInfo.java | 89 +++++++++++++++ .../aegis/ui/EditEntryActivity.java | 54 ++++++++- .../aegis/ui/views/EntryHolder.java | 3 +- .../aegis/util/YandexUtils.java | 89 +++++++++++++++ .../res/drawable/ic_baseline_fiber_pin_24.xml | 9 ++ .../main/res/layout/activity_edit_entry.xml | 38 +++++- app/src/main/res/values/arrays.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../aegis/crypto/otp/YAOTPTest.java | 53 +++++++++ .../aegis/util/YandexUtilsTest.java | 35 ++++++ 15 files changed, 540 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java create mode 100644 app/src/main/res/drawable/ic_baseline_fiber_pin_24.xml create mode 100644 app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java create mode 100644 app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java index 4db3a94cc8..60c752133c 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java @@ -12,6 +12,7 @@ import com.beemdevelopment.aegis.otp.HotpInfo; import com.beemdevelopment.aegis.otp.SteamInfo; import com.beemdevelopment.aegis.otp.TotpInfo; +import com.beemdevelopment.aegis.otp.YandexInfo; import com.beemdevelopment.aegis.ui.MainActivity; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultManager; @@ -68,7 +69,8 @@ public void doOverallTest() { generateEntry(TotpInfo.class, "Frank", "Google"), generateEntry(HotpInfo.class, "John", "GitHub"), generateEntry(TotpInfo.class, "Alice", "Office 365"), - generateEntry(SteamInfo.class, "Gaben", "Steam") + generateEntry(SteamInfo.class, "Gaben", "Steam"), + generateEntry(YandexInfo.class, "Ivan", "Yandex") ); for (VaultEntry entry : entries) { addEntry(entry); @@ -171,6 +173,8 @@ private void addEntry(VaultEntry entry) { otpType = "Steam"; } else if (entry.getInfo() instanceof TotpInfo) { otpType = "TOTP"; + } else if (entry.getInfo() instanceof YandexInfo) { + otpType = "Yandex"; } else { throw new RuntimeException(String.format("Unexpected entry type: %s", entry.getInfo().getClass().getSimpleName())); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/HOTP.java b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/HOTP.java index 5c5afec082..22dea18697 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/HOTP.java +++ b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/HOTP.java @@ -14,6 +14,21 @@ private HOTP() { public static OTP generateOTP(byte[] secret, String algo, int digits, long counter) throws NoSuchAlgorithmException, InvalidKeyException { + byte[] hash = getHash(secret, algo, counter); + + // truncate hash to get the HTOP value + // http://tools.ietf.org/html/rfc4226#section-5.4 + int offset = hash[hash.length - 1] & 0xf; + int otp = ((hash[offset] & 0x7f) << 24) + | ((hash[offset + 1] & 0xff) << 16) + | ((hash[offset + 2] & 0xff) << 8) + | (hash[offset + 3] & 0xff); + + return new OTP(otp, digits); + } + + public static byte[] getHash(byte[] secret, String algo, long counter) + throws NoSuchAlgorithmException, InvalidKeyException { SecretKeySpec key = new SecretKeySpec(secret, "RAW"); // encode counter in big endian @@ -25,16 +40,6 @@ public static OTP generateOTP(byte[] secret, String algo, int digits, long count // calculate the hash of the counter Mac mac = Mac.getInstance(algo); mac.init(key); - byte[] hash = mac.doFinal(counterBytes); - - // truncate hash to get the HTOP value - // http://tools.ietf.org/html/rfc4226#section-5.4 - int offset = hash[hash.length - 1] & 0xf; - int otp = ((hash[offset] & 0x7f) << 24) - | ((hash[offset + 1] & 0xff) << 16) - | ((hash[offset + 2] & 0xff) << 8) - | (hash[offset + 3] & 0xff); - - return new OTP(otp, digits); + return mac.doFinal(counterBytes); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java new file mode 100644 index 0000000000..6689ede0c5 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java @@ -0,0 +1,83 @@ +package com.beemdevelopment.aegis.crypto.otp; + +import androidx.annotation.NonNull; + +import com.beemdevelopment.aegis.util.YandexUtils; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class YAOTP { + private static final int EN_ALPHABET_LENGTH = 26; + private final long _code; + private final int _digits; + + private YAOTP(long code, int digits) { + _code = code; + _digits = digits; + } + + public static YAOTP generateOTP(byte[] secret, byte[] pin, int digits, String otpAlgo, long period) + throws NoSuchAlgorithmException, InvalidKeyException, IOException { + long seconds = System.currentTimeMillis() / 1000; + return generateOTP(secret, pin, digits, otpAlgo, seconds, period); + } + + public static YAOTP generateOTP(byte[] secret, byte[] pin, int digits, String otpAlgo, long seconds, long period) + throws NoSuchAlgorithmException, InvalidKeyException, IOException { + + long counter = (long) Math.floor((double) seconds / period); + + try (ByteArrayOutputStream pinWithHashStream = + new ByteArrayOutputStream(pin.length + secret.length)) { + + pinWithHashStream.write(pin); + pinWithHashStream.write(secret, 0, YandexUtils.APPROVED_SECRET_LENGTH); + + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] keyHash = md.digest(pinWithHashStream.toByteArray()); + + if (keyHash[0] == 0) { + keyHash = Arrays.copyOfRange(keyHash, 1, keyHash.length); + } + + byte[] periodHash = HOTP.getHash(keyHash, otpAlgo, counter); + int offset = periodHash[periodHash.length - 1] & 0xf; + + periodHash[offset] &= 0x7f; + long otp = ByteBuffer.wrap(periodHash) + .order(ByteOrder.BIG_ENDIAN) + .getLong(offset); + + return new YAOTP(otp, digits); + } + } + + public long getCode() { + return _code; + } + + public int getDigits() { + return _digits; + } + + @NonNull + @Override + public String toString() { + long code = _code % (long) Math.pow(EN_ALPHABET_LENGTH, _digits); + char[] chars = new char[_digits]; + + for (int i = _digits - 1; i >= 0; i--) { + chars[i] = (char) ('a' + (code % EN_ALPHABET_LENGTH)); + code /= EN_ALPHABET_LENGTH; + } + + return new String(chars); + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java index fbfd044d48..b220762856 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java @@ -26,42 +26,6 @@ public GoogleAuthInfo(OtpInfo info, String accountName, String issuer) { _issuer = issuer; } - public OtpInfo getOtpInfo() { - return _info; - } - - public Uri getUri() { - Uri.Builder builder = new Uri.Builder(); - builder.scheme(SCHEME); - - if (_info instanceof TotpInfo) { - if (_info instanceof SteamInfo) { - builder.authority("steam"); - } else { - builder.authority("totp"); - } - builder.appendQueryParameter("period", Integer.toString(((TotpInfo)_info).getPeriod())); - } else if (_info instanceof HotpInfo) { - builder.authority("hotp"); - builder.appendQueryParameter("counter", Long.toString(((HotpInfo)_info).getCounter())); - } else { - throw new RuntimeException(String.format("Unsupported OtpInfo type: %s", _info.getClass())); - } - - builder.appendQueryParameter("digits", Integer.toString(_info.getDigits())); - builder.appendQueryParameter("algorithm", _info.getAlgorithm(false)); - builder.appendQueryParameter("secret", Base32.encode(_info.getSecret())); - - if (_issuer != null && !_issuer.equals("")) { - builder.path(String.format("%s:%s", _issuer, _accountName)); - builder.appendQueryParameter("issuer", _issuer); - } else { - builder.path(_accountName); - } - - return builder.build(); - } - public static GoogleAuthInfo parseUri(String s) throws GoogleAuthInfoException { Uri uri = Uri.parse(s); if (uri == null) { @@ -84,12 +48,13 @@ public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException { byte[] secret; try { - secret = parseSecret(encodedSecret); + secret = uri.getHost().equals(YandexInfo.OTP_SCHEMA_ID) ? parseYandexSecret(encodedSecret) : parseSecret(encodedSecret); } catch (EncodingException e) { throw new GoogleAuthInfoException(uri, "Bad secret", e); } OtpInfo info; + String issuer = ""; try { String type = uri.getHost(); if (type == null) { @@ -122,10 +87,15 @@ public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException { hotpInfo.setCounter(Long.parseLong(counter)); info = hotpInfo; break; + case YandexInfo.OTP_SCHEMA_ID: + String pinValue = uri.getQueryParameter("pin"); + info = pinValue != null ? new YandexInfo(secret, parseSecret(pinValue)) : new YandexInfo(secret); + issuer = info.getType(); + break; default: throw new GoogleAuthInfoException(uri, String.format("Unsupported OTP type: %s", type)); } - } catch (OtpInfoException | NumberFormatException e) { + } catch (OtpInfoException | NumberFormatException | EncodingException e) { throw new GoogleAuthInfoException(uri, e); } @@ -134,7 +104,6 @@ public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException { String label = path != null && path.length() > 0 ? path.substring(1) : ""; String accountName = ""; - String issuer = ""; if (label.contains(":")) { // a label can only contain one colon @@ -151,7 +120,9 @@ public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException { // label only contains the account name // grab the issuer's info from the 'issuer' parameter if it's present String issuerParam = uri.getQueryParameter("issuer"); - issuer = issuerParam != null ? issuerParam : ""; + if (issuer.isEmpty()) { + issuer = issuerParam != null ? issuerParam : ""; + } accountName = label; } @@ -180,6 +151,19 @@ public static byte[] parseSecret(String s) throws EncodingException { return Base32.decode(s); } + /** + * When arrives from Yandex site QR code - there will always be 26 symbols (secret only) + * If it arrives from Aegis Export - it can be 42 (if was manually created) + * Just to be sure, let's check secret length (until final RFC comes up) + */ + public static byte[] parseYandexSecret(String s) throws EncodingException { + if (s.length() == YandexInfo.SECRET_LENGTH || s.length() == YandexInfo.SECRET_FULL_LENGTH) { + return parseSecret(s); + } else { + throw new EncodingException(new Throwable("Length differs from expected")); + } + } + public static Export parseExportUri(String s) throws GoogleAuthInfoException { Uri uri = Uri.parse(s); if (uri == null) { @@ -260,7 +244,7 @@ public static Export parseExportUri(Uri uri) throws GoogleAuthInfoException { default: throw new GoogleAuthInfoException(uri, String.format("Unsupported algorithm: %d", params.getType().ordinal())); } - } catch (OtpInfoException e){ + } catch (OtpInfoException e) { throw new GoogleAuthInfoException(uri, e); } @@ -279,6 +263,48 @@ public static Export parseExportUri(Uri uri) throws GoogleAuthInfoException { return new Export(infos, payload.getBatchId(), payload.getBatchIndex(), payload.getBatchSize()); } + public OtpInfo getOtpInfo() { + return _info; + } + + public Uri getUri() { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(SCHEME); + + if (_info instanceof TotpInfo) { + if (_info instanceof SteamInfo) { + builder.authority("steam"); + } else if (_info instanceof YandexInfo) { + builder.authority(YandexInfo.OTP_SCHEMA_ID); + } else { + builder.authority("totp"); + } + builder.appendQueryParameter("period", Integer.toString(((TotpInfo) _info).getPeriod())); + } else if (_info instanceof HotpInfo) { + builder.authority("hotp"); + builder.appendQueryParameter("counter", Long.toString(((HotpInfo) _info).getCounter())); + } else { + throw new RuntimeException(String.format("Unsupported OtpInfo type: %s", _info.getClass())); + } + + builder.appendQueryParameter("digits", Integer.toString(_info.getDigits())); + builder.appendQueryParameter("algorithm", _info.getAlgorithm(false)); + builder.appendQueryParameter("secret", Base32.encode(_info.getSecret())); + + if (_info instanceof YandexInfo) { + builder.appendQueryParameter("pin", Base32.encode(((YandexInfo) _info).getPinBytes())); + } + + if (_issuer != null && !_issuer.equals("")) { + builder.path(String.format("%s:%s", _issuer, _accountName)); + builder.appendQueryParameter("issuer", _issuer); + } else { + builder.path(_accountName); + } + + return builder.build(); + } + public String getIssuer() { return _issuer; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java index a340861084..52f035fab7 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java @@ -115,6 +115,10 @@ public static OtpInfo fromJson(String type, JSONObject obj) throws OtpInfoExcept case HotpInfo.ID: info = new HotpInfo(secret, algo, digits, obj.getLong("counter")); break; + case YandexInfo.ID: + byte[] pin = Base32.decode(obj.getString("pin")); + info = new YandexInfo(secret, pin); + break; default: throw new OtpInfoException("unsupported otp type: " + type); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java new file mode 100644 index 0000000000..04ebf107b2 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java @@ -0,0 +1,89 @@ +package com.beemdevelopment.aegis.otp; + +import com.beemdevelopment.aegis.crypto.otp.YAOTP; +import com.beemdevelopment.aegis.encoding.Base32; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Locale; + +public class YandexInfo extends TotpInfo { + public static final String DEFAULT_ALGORITHM = "SHA256"; + public static final int DIGITS = 8; + + public static final int SECRET_LENGTH = 26; + public static final int SECRET_FULL_LENGTH = 42; + public static final String ID = "yandex"; + public static final String OTP_SCHEMA_ID = "yaotp"; + + private byte[] _pin; + + public YandexInfo(byte[] secret) throws OtpInfoException { + super(secret, DEFAULT_ALGORITHM, DIGITS, TotpInfo.DEFAULT_PERIOD); + } + + public YandexInfo(byte[] secret, byte[] pin) throws OtpInfoException { + super(secret, DEFAULT_ALGORITHM, DIGITS, TotpInfo.DEFAULT_PERIOD); + this._pin = pin; + } + + @Override + public String getOtp() { + try { + YAOTP otp = YAOTP.generateOTP(getSecret(), _pin, getDigits(), getAlgorithm(true), getPeriod()); + return otp.toString(); + } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) { + throw new RuntimeException(e); + } + } + + public String getPin() { + return _pin != null ? new String(_pin, StandardCharsets.UTF_8) : ""; + } + + public byte[] getPinBytes() { + return _pin; + } + + @Override + public String getTypeId() { + return ID; + } + + @Override + public String getType() { + String id = getTypeId(); + return id.substring(0, 1).toUpperCase(Locale.ROOT) + id.substring(1); + } + + @Override + public JSONObject toJson() { + JSONObject result = super.toJson(); + try { + result.put("pin", Base32.encode(getPinBytes())); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return result; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof YandexInfo)) return false; + + YandexInfo that = (YandexInfo) o; + return super.equals(o) && Arrays.equals(_pin, that._pin); + } + + @Override + public int hashCode() { + return super.hashCode() + Arrays.hashCode(_pin); + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index 090ed81ef1..a3b86b92c6 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -32,6 +32,7 @@ import com.amulyakhare.textdrawable.TextDrawable; import com.avito.android.krop.KropView; import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.crypto.CryptoUtils; import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.EncodingException; import com.beemdevelopment.aegis.helpers.DropdownHelper; @@ -46,6 +47,7 @@ import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.SteamInfo; import com.beemdevelopment.aegis.otp.TotpInfo; +import com.beemdevelopment.aegis.otp.YandexInfo; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.dialogs.IconPickerDialog; import com.beemdevelopment.aegis.ui.glide.IconLoader; @@ -53,6 +55,7 @@ import com.beemdevelopment.aegis.ui.views.IconAdapter; import com.beemdevelopment.aegis.util.Cloner; import com.beemdevelopment.aegis.util.IOUtils; +import com.beemdevelopment.aegis.util.YandexUtils; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultManager; import com.bumptech.glide.Glide; @@ -101,6 +104,8 @@ public class EditEntryActivity extends AegisActivity { private TextInputEditText _textDigits; private TextInputLayout _textDigitsLayout; private TextInputEditText _textSecret; + private TextInputEditText _textYandexPin; + private LinearLayout _textYandexPinLayout; private TextInputEditText _textUsageCount; private TextInputEditText _textNote; @@ -153,6 +158,8 @@ protected void onCreate(Bundle savedInstanceState) { _textDigits = findViewById(R.id.text_digits); _textDigitsLayout = findViewById(R.id.text_digits_layout); _textSecret = findViewById(R.id.text_secret); + _textYandexPin = findViewById(R.id.text_yandex_pin); + _textYandexPinLayout = findViewById(R.id.layout_yandex_pin); _textUsageCount = findViewById(R.id.text_usage_count); _textNote = findViewById(R.id.text_note); _dropdownType = findViewById(R.id.dropdown_type); @@ -166,12 +173,20 @@ protected void onCreate(Bundle savedInstanceState) { // if this is NOT a manually entered entry, move the "Secret" field from basic to advanced settings if (!_isNew || (_isNew && !_isManual)) { + int secretIndex = 0; LinearLayout layoutSecret = findViewById(R.id.layout_secret); LinearLayout layoutBasic = findViewById(R.id.layout_basic); LinearLayout layoutAdvanced = findViewById(R.id.layout_advanced); layoutBasic.removeView(layoutSecret); - layoutAdvanced.addView(layoutSecret, 0); - ((LinearLayout.LayoutParams) layoutSecret.getLayoutParams()).topMargin = 0; + if (!_isNew) { + secretIndex = 1; + layoutBasic.removeView(_textYandexPinLayout); + layoutAdvanced.addView(_textYandexPinLayout, 0); + ((LinearLayout.LayoutParams) _textYandexPinLayout.getLayoutParams()).topMargin = 0; + } else { + ((LinearLayout.LayoutParams) layoutSecret.getLayoutParams()).topMargin = 0; + } + layoutAdvanced.addView(layoutSecret, secretIndex); if (_isNew && !_isManual) { setViewEnabled(layoutAdvanced, false); @@ -206,6 +221,7 @@ protected void onCreate(Bundle savedInstanceState) { _textNote.setText(_origEntry.getNote()); OtpInfo info = _origEntry.getInfo(); + if (info instanceof TotpInfo) { _textPeriodCounterLayout.setHint(R.string.period_hint); _textPeriodCounter.setText(Integer.toString(((TotpInfo) info).getPeriod())); @@ -225,7 +241,13 @@ protected void onCreate(Bundle savedInstanceState) { _dropdownType.setText(_origEntry.getInfo().getType(), false); _dropdownAlgo.setText(_origEntry.getInfo().getAlgorithm(false), false); + + if (info instanceof YandexInfo) { + _textYandexPin.setText(((YandexInfo) info).getPin()); + } + updateAdvancedFieldStatus(_origEntry.getInfo().getTypeId()); + updatePinFieldVisibility(_origEntry.getInfo().getTypeId()); String group = _origEntry.getGroup(); setGroup(group); @@ -254,11 +276,18 @@ protected void onCreate(Bundle savedInstanceState) { _textPeriodCounter.setText(String.valueOf(HotpInfo.DEFAULT_COUNTER)); _textDigits.setText(String.valueOf(OtpInfo.DEFAULT_DIGITS)); break; + case YandexInfo.ID: + _dropdownAlgo.setText(YandexInfo.DEFAULT_ALGORITHM, false); + _textPeriodCounterLayout.setHint(R.string.period_hint); + _textPeriodCounter.setText(String.valueOf(TotpInfo.DEFAULT_PERIOD)); + _textDigits.setText(String.valueOf(YandexInfo.DIGITS)); + break; default: throw new RuntimeException(String.format("Unsupported OTP type: %s", type)); } updateAdvancedFieldStatus(type); + updatePinFieldVisibility(type); }); _iconView.setOnClickListener(v -> { @@ -290,12 +319,17 @@ public void onItemClick(AdapterView parent, View view, int position, long id) } private void updateAdvancedFieldStatus(String otpType) { - boolean enabled = !otpType.equals(SteamInfo.ID) && (!_isNew || _isManual); + boolean enabled = !otpType.equals(SteamInfo.ID) && !otpType.equals(YandexInfo.ID) && (!_isNew || _isManual); _textDigitsLayout.setEnabled(enabled); _textPeriodCounterLayout.setEnabled(enabled); _dropdownAlgoLayout.setEnabled(enabled); } + private void updatePinFieldVisibility(String otpType) { + boolean visible = otpType.equals(YandexInfo.ID); + _textYandexPinLayout.setVisibility(visible ? View.VISIBLE : View.GONE); + } + private void setGroup(String groupName) { int pos = 0; if (groupName != null) { @@ -627,6 +661,14 @@ private VaultEntry parseEntry() throws ParseException { String type = _dropdownType.getText().toString(); String algo = _dropdownAlgo.getText().toString(); + String lowerCasedType = type.toLowerCase(Locale.ROOT); + + if (lowerCasedType.equals(YandexInfo.ID)) { + int pinLength = _textYandexPin.length(); + if (pinLength < 4) { + throw new ParseException("PIN is a required field. Min 4 digits."); + } + } int digits; try { @@ -664,6 +706,12 @@ private VaultEntry parseEntry() throws ParseException { } info = new HotpInfo(secret, algo, digits, counter); break; + case YandexInfo.OTP_SCHEMA_ID: + case YandexInfo.ID: + YandexUtils.validateSecret(secret); + byte[] pin = CryptoUtils.toBytes(_textYandexPin.getText().toString().toCharArray()); + info = new YandexInfo(secret, pin); + break; default: throw new RuntimeException(String.format("Unsupported OTP type: %s", type)); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java index 2007d29cc1..04013d64fa 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java @@ -22,6 +22,7 @@ import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.SteamInfo; import com.beemdevelopment.aegis.otp.TotpInfo; +import com.beemdevelopment.aegis.otp.YandexInfo; import com.beemdevelopment.aegis.ui.glide.IconLoader; import com.beemdevelopment.aegis.vault.VaultEntry; import com.bumptech.glide.Glide; @@ -238,7 +239,7 @@ private void updateCode() { OtpInfo info = _entry.getInfo(); String otp = info.getOtp(); - if (!(info instanceof SteamInfo)) { + if (!(info instanceof SteamInfo || info instanceof YandexInfo)) { otp = formatCode(otp); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java b/app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java new file mode 100644 index 0000000000..208fdf292e --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java @@ -0,0 +1,89 @@ +package com.beemdevelopment.aegis.util; + +import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.YandexInfo; + +public class YandexUtils { + private static final char CHECKSUM_POLY = 0b1_1000_1111_0011; + public static final int APPROVED_SECRET_LENGTH = 16; + + private YandexUtils() { + } + + private static int getNumberOfLeadingZeros(char value) { + if (value == 0) return 16; + + int n = 0; + if ((value & 0xFF00) == 0) { + n += 8; + value <<= 8; + } + if ((value & 0xF000) == 0) { + n += 4; + value <<= 4; + } + if ((value & 0xC000) == 0) { + n += 2; + value <<= 2; + } + if ((value & 0x8000) == 0) { + n++; + } + + return n; + } + + /** + * Java implementation of ChecksumIsValid + * from https://github.com/norblik/KeeYaOtp/blob/dev/KeeYaOtp/Core/Secret.cs + */ + public static void validateSecret(byte[] secret) throws OtpInfoException { + /* + When secret comes from QR code - we can't test it, + cause it's only 16 byte long. + */ + if (secret.length == APPROVED_SECRET_LENGTH) return; + + if (secret.length != YandexInfo.SECRET_LENGTH) + throw new OtpInfoException("Wrong secret size"); + + char originalChecksum = (char) ((secret[secret.length - 2] & 0x0F) << 8 | secret[secret.length - 1] & 0xff); + + char accum = 0; + int accumBits = 0; + + int inputTotalBitsAvailable = secret.length * 8 - 12; + int inputIndex = 0; + int inputBitsAvailable = 8; + + while (inputTotalBitsAvailable > 0) { + int requiredBits = 13 - accumBits; + if (inputTotalBitsAvailable < requiredBits) requiredBits = inputTotalBitsAvailable; + + while (requiredBits > 0) { + int curInput = (secret[inputIndex] & (1 << inputBitsAvailable) - 1) & 0xff; + int bitsToRead = Math.min(requiredBits, inputBitsAvailable); + + curInput >>= inputBitsAvailable - bitsToRead; + accum = (char) (accum << bitsToRead | curInput); + + inputTotalBitsAvailable -= bitsToRead; + requiredBits -= bitsToRead; + inputBitsAvailable -= bitsToRead; + accumBits += bitsToRead; + + if (inputBitsAvailable == 0) { + inputIndex += 1; + inputBitsAvailable = 8; + } + } + + if (accumBits == 13) accum ^= CHECKSUM_POLY; + accumBits = 16 - getNumberOfLeadingZeros(accum); + } + + if (accum != originalChecksum) { + throw new OtpInfoException("Secret is corrupted. Checksum is not valid"); + } + } +} diff --git a/app/src/main/res/drawable/ic_baseline_fiber_pin_24.xml b/app/src/main/res/drawable/ic_baseline_fiber_pin_24.xml new file mode 100644 index 0000000000..957a249fa5 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_fiber_pin_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_edit_entry.xml b/app/src/main/res/layout/activity_edit_entry.xml index 885d06ba9e..5d2149820e 100644 --- a/app/src/main/res/layout/activity_edit_entry.xml +++ b/app/src/main/res/layout/activity_edit_entry.xml @@ -45,13 +45,12 @@ android:id="@+id/krop_view" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerHorizontal="true" - android:layout_centerVertical="true" + android:layout_centerInParent="true" android:visibility="invisible" app:krop_aspectX="1" app:krop_aspectY="1" app:krop_offset="70dp" - app:krop_overlayColor="#aadddddd" > + app:krop_overlayColor="#aadddddd"> + + + + + + + + TOTP HOTP Steam + Yandex diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e50b47c9bd..22ee5630b9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -15,6 +15,7 @@ Discard Save Issuer + PIN (4–16 digits) Suggested Usage count diff --git a/app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java b/app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java new file mode 100644 index 0000000000..7d48528e63 --- /dev/null +++ b/app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java @@ -0,0 +1,53 @@ +package com.beemdevelopment.aegis.crypto.otp; + +import static org.junit.Assert.assertEquals; + +import com.beemdevelopment.aegis.crypto.CryptoUtils; +import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.EncodingException; + +import org.junit.Test; + +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +public class YAOTPTest { + + private static final Vector[] TEST_CASES = new Vector[]{ + new Vector("5239", "6SB2IKNM6OBZPAVBVTOHDKS4FAAAAAAADFUTQMBTRY", 1641559648L, "umozdicq"), + new Vector("7586", "LA2V6KMCGYMWWVEW64RNP3JA3IAAAAAAHTSG4HRZPI", 1581064020L, "oactmacq"), + new Vector("7586", "LA2V6KMCGYMWWVEW64RNP3JA3IAAAAAAHTSG4HRZPI", 1581090810L, "wemdwrix"), + new Vector("5210481216086702", "JBGSAU4G7IEZG6OY4UAXX62JU4AAAAAAHTSG4HXU3M", 1581091469L, "dfrpywob"), + new Vector("5210481216086702", "JBGSAU4G7IEZG6OY4UAXX62JU4AAAAAAHTSG4HXU3M", 1581093059L, "vunyprpd"), + }; + + @Test + public void validateYaOtp() throws InvalidKeyException, NoSuchAlgorithmException, IOException { + for (Vector testCase : TEST_CASES) { + YAOTP otp = YAOTP.generateOTP( + Base32.decode(testCase.secret.substring(0, 26)), + CryptoUtils.toBytes(testCase.pin.toCharArray()), + 8, + "HmacSHA256", + testCase.timestamp, + 30 + ); + assertEquals(testCase.expected, otp.toString()); + } + } + + public static class Vector { + public String pin; + public String secret; + public long timestamp; + public String expected; + + public Vector(String pin, String secret, long timestamp, String expected) { + this.pin = pin; + this.secret = secret; + this.timestamp = timestamp; + this.expected = expected; + } + } +} diff --git a/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java b/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java new file mode 100644 index 0000000000..aec675b68f --- /dev/null +++ b/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java @@ -0,0 +1,35 @@ +package com.beemdevelopment.aegis.util; + +import static org.junit.Assert.assertThrows; + +import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.otp.OtpInfoException; + +import org.junit.Test; + +public class YandexUtilsTest { + + private static final String[] vectors = new String[]{ + "LA2V6KMCGYMWWVEW64RNP3JA3IAAAAAAHTSG4HRZPI", // correct + "LA2V6KMCGYMWWVEW64RNP3JA3I", // secret from QR - no validation + "AA2V6KMCGYMWWVEW64RNP3JA3IAAAAAAHTSG4HRZPI", // first letter is different + "AA2V6KMCGJA3IAAAAAAHTSG4HRZPI" // size is wrong + }; + + @Test(expected = Test.None.class) + public void testValidationOk() throws EncodingException, OtpInfoException { + YandexUtils.validateSecret(getBase32Vector(0)); + YandexUtils.validateSecret(getBase32Vector(1)); + } + + @Test + public void testYandexSecretValidation() { + assertThrows(OtpInfoException.class, () -> YandexUtils.validateSecret(getBase32Vector(2))); + assertThrows(OtpInfoException.class, () -> YandexUtils.validateSecret(getBase32Vector(3))); + } + + private byte[] getBase32Vector(int vectorIndex) throws EncodingException { + return Base32.decode(vectors[vectorIndex]); + } +} From a04020aed0718cd4d947917ccf36eebe9df3582d Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Wed, 26 Jan 2022 13:35:30 +0100 Subject: [PATCH 012/389] Add an FAQ document --- CONTRIBUTING.md | 3 ++ FAQ.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 3 files changed, 106 insertions(+) create mode 100644 FAQ.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 687c917b0e..667db4bca5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,9 @@ Looking to contribute to Aegis? That's great! There are a couple of ways to help out. This document contains some general guidelines for each type of contribution. +Please review [the FAQ](FAQ.md) before reporting a bug, asking a question or +requesting a feature. + ## Translations We use [Crowdin](https://crowdin.com/project/aegis-authenticator) to crowdsource diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000000..cad7975898 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,101 @@ +# FAQ + +## General + +### How can I contribute? + +There are lots of ways! Please refer to our [contributing +guide](https://github.com/beemdevelopment/Aegis/blob/master/CONTRIBUTING.md). + +### Why is the latest version not on F-Droid yet? + +We don't release new versions of Aegis on F-Droid ourselves. Once we've released +a new version on GitHub, F-Droid will usually kick off their automatic build +process a day later and publish the app to their repository a couple of days +afterwards. It can sometimes take up to a week for a new version to appear on +F-Droid. + +### Can you port Aegis to iOS/Windows/MacOS/Browser Extension? + +We don't have plans to port Aegis to other platforms. + +### Can you add support for Autofill? + +On Android, only one app can be active in the Autofill slot at a time, and since +this is typically occupied by the password manager, we don't see much value in +adding support for this feature in Aegis. + +### What is the difference between exporting and backing up? + +Exporting is done manually and backups are done automatically. The format of the +vault file is exactly the same for both. + +## Security + +### I can no longer use biometrics to unlock the app. What should I do? + +If you could previously unlock Aegis with biometrics, but suddenly can't do so +anymore, this is probably caused by a change made to the security settings of +your device. The app will tell you when this happened in most cases. To resolve +this, unlock the app with your password, disable biometric unlock in the +settings of Aegis and re-enable it. + +### Why does Aegis keep prompting me for my password, even though I have enabled biometric authentication? + +You're probably encountering the password reminder. Try entering your password +to unlock the vault once. After that, Aegis will prompt for biometrics by +default again until it's time for another password reminder. + +Since forgetting your password will result in loss of access to the contents of +the vault, __we do NOT recommend disabling the password reminder__. + +### Aegis uses SHA1 for most/all of my tokens. Isn't that insecure? + +The hash algorithm is imposed by the service you're setting up 2FA for (e.g. +Google, Facebook, GitHub, etc). There is nothing we can do about that. If we +were to change this on Aegis' end, the tokens would stop working. Furthermore, +when using SHA1 in an HMAC calculation, the currently known issues in SHA1 are +not of concern. + +### Why doesn't Aegis support biometric unlock for my device, even though it works with other apps? + +The reason for this is pretty technical. In short, since you're not entering +your password when using biometric unlock, Aegis needs some other way to decrypt +the vault. For this purpose, we generate and use a key in the Android Keystore, +telling it to only allow us to use that key if the user authenticates using +their biometrics first. Some devices have buggy implementations of this feature, +resulting in the error displayed to you by Aegis in an error dialog. + +If biometrics works with other apps, but not with Aegis, that means those other +apps probably perform a weaker form of biometric authentication. + +## Backups + +### How can I back up my Aegis vault to the cloud automatically? + +Aegis can only automatically back up to the cloud of the app of your cloud +provider is installed on your device and fully participates in the Android +Storage Access Framework. Aegis doesn't have access to the internet and we don't +have plans to change this, so adding support for specific cloud providers in the +app is not possible. + +Cloud providers currently known to be supported: +- Nextcloud + +Another common setup is to configure Aegis to back up to a folder on local +storage of your device and then have a separate app (like +[Syncthing](https://syncthing.net/)) sync that folder anywhere you want. + +## Importing + +### When importing from Authenticator Plus, an error is shown claiming that Accounts.txt is missing + +Make sure you supply an Authenticator Plus export file obtained through +__Settings -> Backup & Restore -> Export as Text and HTML__. The ``.db`` format +is not supported. + +If it still doesn't work, please report the issue to us. As a temporary +workaround, you can try extracting the ZIP archive on a computer, recreating it +without a password and then importing that into Aegis. Another option is +extracting the ZIP archive on a computer and importing the resulting +Accounts.txt file into Aegis with the "Plain text" import option. diff --git a/README.md b/README.md index a7b0c784bf..2e3dd19f95 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ including some features missing in existing authenticator apps, like proper encryption and backups. Aegis supports HOTP and TOTP, making it compatible with thousands of services. +For a list of frequently asked questions, please check out [the FAQ](FAQ.md). + The security design of the app and the vault format is described in detail in [this document](docs/vault.md). From e88c3ea6dbd44e13e0d5cb8f1afca8127ac61b30 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Mon, 31 Jan 2022 21:59:02 +0100 Subject: [PATCH 013/389] Update dependencies --- app/build.gradle | 24 ++++++------- .../beemdevelopment/aegis/OverallTest.java | 34 +++++++++---------- build.gradle | 4 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index edba9d0013..67bc800219 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,25 +124,25 @@ dependencies { def androidTestVersion = '1.4.0' def cameraxVersion = '1.0.2' def glideVersion = '4.12.0' - def guavaVersion = '30.1.1' + def guavaVersion = '31.0.1' def junitVersion = '4.13.2' - def libsuVersion = '3.1.2' + def libsuVersion = '3.2.1' annotationProcessor 'androidx.annotation:annotation:1.3.0' annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}" implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'androidx.appcompat:appcompat:1.4.1' implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.camera:camera-camera2:$cameraxVersion" implementation "androidx.camera:camera-lifecycle:$cameraxVersion" - implementation "androidx.camera:camera-view:1.0.0-alpha31" + implementation "androidx.camera:camera-view:1.1.0-beta01" implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.core:core:1.7.0" - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.documentfile:documentfile:1.0.1' implementation "androidx.lifecycle:lifecycle-process:2.4.0" - implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.viewpager2:viewpager2:1.0.0" implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' @@ -156,8 +156,8 @@ dependencies { implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" implementation "com.github.topjohnwu.libsu:io:${libsuVersion}" implementation "com.google.guava:guava:${guavaVersion}-android" - implementation 'com.google.android.material:material:1.4.0' - implementation 'com.google.protobuf:protobuf-javalite:3.17.3' + implementation 'com.google.android.material:material:1.5.0' + implementation 'com.google.protobuf:protobuf-javalite:3.19.4' implementation 'com.google.zxing:core:3.4.1' implementation "com.mikepenz:iconics-core:3.2.5" implementation 'com.mikepenz:material-design-iconic-typeface:2.2.0.5@aar' @@ -167,7 +167,7 @@ dependencies { // NOTE: this is kept at an old version on purpose (something in newer versions breaks the Authenticator Plus importer) implementation 'net.lingala.zip4j:zip4j:2.6.0' implementation 'info.guardianproject.trustedintents:trustedintents:0.2' - implementation 'org.bouncycastle:bcprov-jdk15on:1.69' + implementation 'org.bouncycastle:bcprov-jdk15on:1.70' androidTestImplementation "androidx.test:core:${androidTestVersion}" androidTestImplementation "androidx.test:runner:${androidTestVersion}" @@ -177,13 +177,13 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0' androidTestImplementation "junit:junit:${junitVersion}" - androidTestUtil 'androidx.test:orchestrator:1.4.0' + androidTestUtil 'androidx.test:orchestrator:1.4.1' testImplementation "androidx.test:core:${androidTestVersion}" testImplementation "com.google.guava:guava:${guavaVersion}-jre" testImplementation "junit:junit:${junitVersion}" - testImplementation "org.json:json:20210307" - testImplementation 'org.robolectric:robolectric:4.6.1' + testImplementation "org.json:json:20211205" + testImplementation 'org.robolectric:robolectric:4.7.3' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' } diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java index 4db3a94cc8..09ff1577b6 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java @@ -1,5 +1,21 @@ package com.beemdevelopment.aegis; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu; +import static androidx.test.espresso.action.ViewActions.clearText; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.longClick; +import static androidx.test.espresso.action.ViewActions.pressBack; +import static androidx.test.espresso.action.ViewActions.typeText; +import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; + import androidx.annotation.IdRes; import androidx.test.espresso.ViewInteraction; import androidx.test.espresso.contrib.RecyclerViewActions; @@ -25,22 +41,6 @@ import java.util.Arrays; import java.util.List; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.Espresso.openContextualActionModeOverflowMenu; -import static androidx.test.espresso.action.ViewActions.clearText; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; -import static androidx.test.espresso.action.ViewActions.longClick; -import static androidx.test.espresso.action.ViewActions.pressBack; -import static androidx.test.espresso.action.ViewActions.typeText; -import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; -import static androidx.test.espresso.matcher.ViewMatchers.isRoot; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static androidx.test.espresso.matcher.ViewMatchers.withText; -import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertNull; -import static junit.framework.TestCase.assertTrue; - @RunWith(AndroidJUnit4.class) @LargeTest public class OverallTest extends AegisTest { @@ -55,7 +55,7 @@ public void doOverallTest() { next.perform(click()); onView(withId(R.id.rb_password)).perform(click()); next.perform(click()); - onView(withId(R.id.text_password)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard()); + onView(withId(R.id.text_password)).perform(click()).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard()); onView(withId(R.id.text_password_confirm)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard()); next.perform(click()); onView(withId(R.id.btnNext)).perform(click()); diff --git a/build.gradle b/build.gradle index 6ff5850050..8ddcc852b3 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,8 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.17' + classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.18' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 185e22a82e02ec86e1ef9acf3004282c3f3e0d33 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Mon, 31 Jan 2022 22:07:40 +0100 Subject: [PATCH 014/389] Downgrade camera-view back to 1.0.0-alpha32 Can't mix and match 1.0.0 and 1.1.0. --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 67bc800219..f9502fc808 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,7 +136,7 @@ dependencies { implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.camera:camera-camera2:$cameraxVersion" implementation "androidx.camera:camera-lifecycle:$cameraxVersion" - implementation "androidx.camera:camera-view:1.1.0-beta01" + implementation "androidx.camera:camera-view:1.0.0-alpha32" implementation 'androidx.cardview:cardview:1.0.0' implementation "androidx.core:core:1.7.0" implementation 'androidx.constraintlayout:constraintlayout:2.1.3' From 3d13117752491de81d3779dae34407c651954f7b Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Wed, 2 Feb 2022 17:55:09 +0100 Subject: [PATCH 015/389] Refactor Yandex support a bit and fix some style issues --- .../com/beemdevelopment/aegis/AegisTest.java | 6 +- .../beemdevelopment/aegis/OverallTest.java | 16 +- .../beemdevelopment/aegis/crypto/otp/OTP.java | 4 +- .../aegis/crypto/otp/YAOTP.java | 58 +++---- .../aegis/encoding/Base32.java | 6 + .../aegis/encoding/EncodingException.java | 4 + .../aegis/otp/GoogleAuthInfo.java | 30 ++-- .../beemdevelopment/aegis/otp/OtpInfo.java | 5 +- .../beemdevelopment/aegis/otp/YandexInfo.java | 143 +++++++++++++++--- .../aegis/ui/EditEntryActivity.java | 9 +- .../aegis/util/YandexUtils.java | 89 ----------- .../aegis/crypto/otp/YAOTPTest.java | 12 +- .../aegis/util/YandexUtilsTest.java | 9 +- 13 files changed, 199 insertions(+), 192 deletions(-) delete mode 100644 app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java index ea9a077ae5..996c7a20c9 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java @@ -78,7 +78,11 @@ protected VaultFileCredentials generateCredentials() { } protected static VaultEntry generateEntry(Class type, String name, String issuer) { - byte[] secret = CryptoUtils.generateRandomBytes(20); + return generateEntry(type, name, issuer, 20); + } + + protected static VaultEntry generateEntry(Class type, String name, String issuer, int secretLength) { + byte[] secret = CryptoUtils.generateRandomBytes(secretLength); OtpInfo info; try { diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java index 35ceb8e8f8..f41b49257e 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java @@ -70,7 +70,7 @@ public void doOverallTest() { generateEntry(HotpInfo.class, "John", "GitHub"), generateEntry(TotpInfo.class, "Alice", "Office 365"), generateEntry(SteamInfo.class, "Gaben", "Steam"), - generateEntry(YandexInfo.class, "Ivan", "Yandex") + generateEntry(YandexInfo.class, "Ivan", "Yandex", 16) ); for (VaultEntry entry : entries) { addEntry(entry); @@ -108,9 +108,9 @@ public void doOverallTest() { changeGroupFilter(_groupName); changeGroupFilter(null); - onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(1, longClick())); - onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(2, click())); + onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(2, longClick())); onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(3, click())); + onView(withId(R.id.rvKeyProfiles)).perform(RecyclerViewActions.actionOnItemAtPosition(4, click())); onView(withId(R.id.action_share_qr)).perform(click()); onView(withId(R.id.btnNext)).perform(click()).perform(click()).perform(click()); @@ -171,10 +171,10 @@ private void addEntry(VaultEntry entry) { otpType = "HOTP"; } else if (entry.getInfo() instanceof SteamInfo) { otpType = "Steam"; - } else if (entry.getInfo() instanceof TotpInfo) { - otpType = "TOTP"; } else if (entry.getInfo() instanceof YandexInfo) { otpType = "Yandex"; + } else if (entry.getInfo() instanceof TotpInfo) { + otpType = "TOTP"; } else { throw new RuntimeException(String.format("Unexpected entry type: %s", entry.getInfo().getClass().getSimpleName())); } @@ -186,6 +186,12 @@ private void addEntry(VaultEntry entry) { String secret = Base32.encode(entry.getInfo().getSecret()); onView(withId(R.id.text_secret)).perform(typeText(secret), closeSoftKeyboard()); + if (entry.getInfo() instanceof YandexInfo) { + String pin = "123456"; + ((YandexInfo) entry.getInfo()).setPin(pin); + onView(withId(R.id.text_yandex_pin)).perform(typeText(pin), closeSoftKeyboard()); + } + onView(withId(R.id.action_save)).perform(click()); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/OTP.java b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/OTP.java index 868074f927..9480f9729a 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/OTP.java +++ b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/OTP.java @@ -5,8 +5,8 @@ public class OTP { private static final String STEAM_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY"; - private int _code; - private int _digits; + private final int _code; + private final int _digits; public OTP(int code, int digits) { _code = code; diff --git a/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java index 6689ede0c5..c8f1f52f7c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java +++ b/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java @@ -2,12 +2,11 @@ import androidx.annotation.NonNull; -import com.beemdevelopment.aegis.util.YandexUtils; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -23,48 +22,37 @@ private YAOTP(long code, int digits) { _digits = digits; } - public static YAOTP generateOTP(byte[] secret, byte[] pin, int digits, String otpAlgo, long period) + public static YAOTP generateOTP(byte[] secret, String pin, int digits, String otpAlgo, long period) throws NoSuchAlgorithmException, InvalidKeyException, IOException { long seconds = System.currentTimeMillis() / 1000; return generateOTP(secret, pin, digits, otpAlgo, seconds, period); } - public static YAOTP generateOTP(byte[] secret, byte[] pin, int digits, String otpAlgo, long seconds, long period) + public static YAOTP generateOTP(byte[] secret, String pin, int digits, String otpAlgo, long seconds, long period) throws NoSuchAlgorithmException, InvalidKeyException, IOException { - - long counter = (long) Math.floor((double) seconds / period); - - try (ByteArrayOutputStream pinWithHashStream = - new ByteArrayOutputStream(pin.length + secret.length)) { - - pinWithHashStream.write(pin); - pinWithHashStream.write(secret, 0, YandexUtils.APPROVED_SECRET_LENGTH); - - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] keyHash = md.digest(pinWithHashStream.toByteArray()); - - if (keyHash[0] == 0) { - keyHash = Arrays.copyOfRange(keyHash, 1, keyHash.length); - } - - byte[] periodHash = HOTP.getHash(keyHash, otpAlgo, counter); - int offset = periodHash[periodHash.length - 1] & 0xf; - - periodHash[offset] &= 0x7f; - long otp = ByteBuffer.wrap(periodHash) - .order(ByteOrder.BIG_ENDIAN) - .getLong(offset); - - return new YAOTP(otp, digits); + byte[] pinWithHash; + byte[] pinBytes = pin.getBytes(StandardCharsets.UTF_8); + try (ByteArrayOutputStream stream = new ByteArrayOutputStream(pinBytes.length + secret.length)) { + stream.write(pinBytes); + stream.write(secret); + pinWithHash = stream.toByteArray(); } - } - public long getCode() { - return _code; - } + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] keyHash = md.digest(pinWithHash); + if (keyHash[0] == 0) { + keyHash = Arrays.copyOfRange(keyHash, 1, keyHash.length); + } - public int getDigits() { - return _digits; + long counter = (long) Math.floor((double) seconds / period); + byte[] periodHash = HOTP.getHash(keyHash, otpAlgo, counter); + int offset = periodHash[periodHash.length - 1] & 0xf; + periodHash[offset] &= 0x7f; + long otp = ByteBuffer.wrap(periodHash) + .order(ByteOrder.BIG_ENDIAN) + .getLong(offset); + + return new YAOTP(otp, digits); } @NonNull diff --git a/app/src/main/java/com/beemdevelopment/aegis/encoding/Base32.java b/app/src/main/java/com/beemdevelopment/aegis/encoding/Base32.java index df30049f6c..85fe61faae 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/encoding/Base32.java +++ b/app/src/main/java/com/beemdevelopment/aegis/encoding/Base32.java @@ -2,6 +2,7 @@ import com.google.common.io.BaseEncoding; +import java.nio.charset.StandardCharsets; import java.util.Locale; public class Base32 { @@ -20,4 +21,9 @@ public static byte[] decode(String s) throws EncodingException { public static String encode(byte[] data) { return BaseEncoding.base32().omitPadding().encode(data); } + + public static String encode(String s) { + byte[] bytes = s.getBytes(StandardCharsets.UTF_8); + return encode(bytes); + } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/encoding/EncodingException.java b/app/src/main/java/com/beemdevelopment/aegis/encoding/EncodingException.java index ac75a34788..f85ec0ecea 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/encoding/EncodingException.java +++ b/app/src/main/java/com/beemdevelopment/aegis/encoding/EncodingException.java @@ -6,4 +6,8 @@ public class EncodingException extends IOException { public EncodingException(Throwable cause) { super(cause); } + + public EncodingException(String message) { + super(message); + } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java index b220762856..9a485ba1a0 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/GoogleAuthInfo.java @@ -9,6 +9,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import java.io.Serializable; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -48,7 +49,7 @@ public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException { byte[] secret; try { - secret = uri.getHost().equals(YandexInfo.OTP_SCHEMA_ID) ? parseYandexSecret(encodedSecret) : parseSecret(encodedSecret); + secret = parseSecret(encodedSecret); } catch (EncodingException e) { throw new GoogleAuthInfoException(uri, "Bad secret", e); } @@ -87,9 +88,13 @@ public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException { hotpInfo.setCounter(Long.parseLong(counter)); info = hotpInfo; break; - case YandexInfo.OTP_SCHEMA_ID: - String pinValue = uri.getQueryParameter("pin"); - info = pinValue != null ? new YandexInfo(secret, parseSecret(pinValue)) : new YandexInfo(secret); + case YandexInfo.HOST_ID: + String pin = uri.getQueryParameter("pin"); + if (pin != null) { + pin = new String(parseSecret(pin), StandardCharsets.UTF_8); + } + + info = new YandexInfo(secret, pin); issuer = info.getType(); break; default: @@ -151,19 +156,6 @@ public static byte[] parseSecret(String s) throws EncodingException { return Base32.decode(s); } - /** - * When arrives from Yandex site QR code - there will always be 26 symbols (secret only) - * If it arrives from Aegis Export - it can be 42 (if was manually created) - * Just to be sure, let's check secret length (until final RFC comes up) - */ - public static byte[] parseYandexSecret(String s) throws EncodingException { - if (s.length() == YandexInfo.SECRET_LENGTH || s.length() == YandexInfo.SECRET_FULL_LENGTH) { - return parseSecret(s); - } else { - throw new EncodingException(new Throwable("Length differs from expected")); - } - } - public static Export parseExportUri(String s) throws GoogleAuthInfoException { Uri uri = Uri.parse(s); if (uri == null) { @@ -275,7 +267,7 @@ public Uri getUri() { if (_info instanceof SteamInfo) { builder.authority("steam"); } else if (_info instanceof YandexInfo) { - builder.authority(YandexInfo.OTP_SCHEMA_ID); + builder.authority(YandexInfo.HOST_ID); } else { builder.authority("totp"); } @@ -292,7 +284,7 @@ public Uri getUri() { builder.appendQueryParameter("secret", Base32.encode(_info.getSecret())); if (_info instanceof YandexInfo) { - builder.appendQueryParameter("pin", Base32.encode(((YandexInfo) _info).getPinBytes())); + builder.appendQueryParameter("pin", Base32.encode(((YandexInfo) _info).getPin())); } if (_issuer != null && !_issuer.equals("")) { diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java index 52f035fab7..a717c38941 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/OtpInfo.java @@ -40,7 +40,7 @@ public JSONObject toJson() { JSONObject obj = new JSONObject(); try { - obj.put("secret", new String(Base32.encode(getSecret()))); + obj.put("secret", Base32.encode(getSecret())); obj.put("algo", getAlgorithm(false)); obj.put("digits", getDigits()); } catch (JSONException e) { @@ -116,8 +116,7 @@ public static OtpInfo fromJson(String type, JSONObject obj) throws OtpInfoExcept info = new HotpInfo(secret, algo, digits, obj.getLong("counter")); break; case YandexInfo.ID: - byte[] pin = Base32.decode(obj.getString("pin")); - info = new YandexInfo(secret, pin); + info = new YandexInfo(secret, obj.getString("pin")); break; default: throw new OtpInfoException("unsupported otp type: " + type); diff --git a/app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java b/app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java index 04ebf107b2..b5da60de19 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java +++ b/app/src/main/java/com/beemdevelopment/aegis/otp/YandexInfo.java @@ -1,54 +1,63 @@ package com.beemdevelopment.aegis.otp; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.beemdevelopment.aegis.crypto.otp.YAOTP; -import com.beemdevelopment.aegis.encoding.Base32; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Locale; +import java.util.Objects; public class YandexInfo extends TotpInfo { public static final String DEFAULT_ALGORITHM = "SHA256"; public static final int DIGITS = 8; - public static final int SECRET_LENGTH = 26; - public static final int SECRET_FULL_LENGTH = 42; + public static final int SECRET_LENGTH = 16; + public static final int SECRET_FULL_LENGTH = 26; public static final String ID = "yandex"; - public static final String OTP_SCHEMA_ID = "yaotp"; + public static final String HOST_ID = "yaotp"; - private byte[] _pin; + @Nullable + private String _pin; - public YandexInfo(byte[] secret) throws OtpInfoException { - super(secret, DEFAULT_ALGORITHM, DIGITS, TotpInfo.DEFAULT_PERIOD); + public YandexInfo(@NonNull byte[] secret) throws OtpInfoException { + this(secret, null); } - public YandexInfo(byte[] secret, byte[] pin) throws OtpInfoException { + public YandexInfo(@NonNull byte[] secret, @Nullable String pin) throws OtpInfoException { super(secret, DEFAULT_ALGORITHM, DIGITS, TotpInfo.DEFAULT_PERIOD); - this._pin = pin; + setSecret(parseSecret(secret)); + _pin = pin; } @Override public String getOtp() { + if (_pin == null) { + throw new IllegalStateException("PIN must be set before generating an OTP"); + } + try { - YAOTP otp = YAOTP.generateOTP(getSecret(), _pin, getDigits(), getAlgorithm(true), getPeriod()); + YAOTP otp = YAOTP.generateOTP(getSecret(), getPin(), getDigits(), getAlgorithm(true), getPeriod()); return otp.toString(); } catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) { throw new RuntimeException(e); } } + @Nullable public String getPin() { - return _pin != null ? new String(_pin, StandardCharsets.UTF_8) : ""; + return _pin; } - public byte[] getPinBytes() { - return _pin; + public void setPin(@NonNull String pin) { + _pin = pin; } @Override @@ -66,7 +75,7 @@ public String getType() { public JSONObject toJson() { JSONObject result = super.toJson(); try { - result.put("pin", Base32.encode(getPinBytes())); + result.put("pin", getPin()); } catch (JSONException e) { throw new RuntimeException(e); } @@ -75,15 +84,105 @@ public JSONObject toJson() { @Override public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof YandexInfo)) return false; + if (!(o instanceof YandexInfo)) { + return false; + } - YandexInfo that = (YandexInfo) o; - return super.equals(o) && Arrays.equals(_pin, that._pin); + YandexInfo info = (YandexInfo) o; + return super.equals(o) && Objects.equals(getPin(), info.getPin()); } - @Override - public int hashCode() { - return super.hashCode() + Arrays.hashCode(_pin); + public static byte[] parseSecret(byte[] secret) throws OtpInfoException { + validateSecret(secret); + + if (secret.length != SECRET_LENGTH) { + return Arrays.copyOfRange(secret, 0, SECRET_LENGTH); + } + + return secret; + } + + /** + * Java implementation of ChecksumIsValid + * From: https://github.com/norblik/KeeYaOtp/blob/188a1a99f13f82e4ef8df8a1b9b9351ba236e2a1/KeeYaOtp/Core/Secret.cs + * License: GPLv3+ + */ + public static void validateSecret(byte[] secret) throws OtpInfoException { + if (secret.length != SECRET_LENGTH && secret.length != SECRET_FULL_LENGTH) { + throw new OtpInfoException(String.format("Invalid Yandex secret length: %d bytes", secret.length)); + } + + // Secrets originating from a QR code do not have a checksum, so we assume those are valid + if (secret.length == SECRET_LENGTH) { + return; + } + + char originalChecksum = (char) ((secret[secret.length - 2] & 0x0F) << 8 | secret[secret.length - 1] & 0xff); + + char accum = 0; + int accumBits = 0; + + int inputTotalBitsAvailable = secret.length * 8 - 12; + int inputIndex = 0; + int inputBitsAvailable = 8; + + while (inputTotalBitsAvailable > 0) { + int requiredBits = 13 - accumBits; + if (inputTotalBitsAvailable < requiredBits) { + requiredBits = inputTotalBitsAvailable; + } + + while (requiredBits > 0) { + int curInput = (secret[inputIndex] & (1 << inputBitsAvailable) - 1) & 0xff; + int bitsToRead = Math.min(requiredBits, inputBitsAvailable); + + curInput >>= inputBitsAvailable - bitsToRead; + accum = (char) (accum << bitsToRead | curInput); + + inputTotalBitsAvailable -= bitsToRead; + requiredBits -= bitsToRead; + inputBitsAvailable -= bitsToRead; + accumBits += bitsToRead; + + if (inputBitsAvailable == 0) { + inputIndex += 1; + inputBitsAvailable = 8; + } + } + + if (accumBits == 13) { + accum ^= 0b1_1000_1111_0011; + } + accumBits = 16 - getNumberOfLeadingZeros(accum); + } + + if (accum != originalChecksum) { + throw new OtpInfoException("Yandex secret checksum invalid"); + } + } + + private static int getNumberOfLeadingZeros(char value) { + if (value == 0) { + return 16; + } + + int n = 0; + if ((value & 0xFF00) == 0) { + n += 8; + value <<= 8; + } + if ((value & 0xF000) == 0) { + n += 4; + value <<= 4; + } + if ((value & 0xC000) == 0) { + n += 2; + value <<= 2; + } + if ((value & 0x8000) == 0) { + n++; + } + + return n; } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index a3b86b92c6..87e17d82d1 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -32,7 +32,6 @@ import com.amulyakhare.textdrawable.TextDrawable; import com.avito.android.krop.KropView; import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.crypto.CryptoUtils; import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.EncodingException; import com.beemdevelopment.aegis.helpers.DropdownHelper; @@ -55,7 +54,6 @@ import com.beemdevelopment.aegis.ui.views.IconAdapter; import com.beemdevelopment.aegis.util.Cloner; import com.beemdevelopment.aegis.util.IOUtils; -import com.beemdevelopment.aegis.util.YandexUtils; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultManager; import com.bumptech.glide.Glide; @@ -666,7 +664,7 @@ private VaultEntry parseEntry() throws ParseException { if (lowerCasedType.equals(YandexInfo.ID)) { int pinLength = _textYandexPin.length(); if (pinLength < 4) { - throw new ParseException("PIN is a required field. Min 4 digits."); + throw new ParseException("PIN is a required field. Must have a minimum length of 4 digits."); } } @@ -706,11 +704,8 @@ private VaultEntry parseEntry() throws ParseException { } info = new HotpInfo(secret, algo, digits, counter); break; - case YandexInfo.OTP_SCHEMA_ID: case YandexInfo.ID: - YandexUtils.validateSecret(secret); - byte[] pin = CryptoUtils.toBytes(_textYandexPin.getText().toString().toCharArray()); - info = new YandexInfo(secret, pin); + info = new YandexInfo(secret, _textYandexPin.getText().toString()); break; default: throw new RuntimeException(String.format("Unsupported OTP type: %s", type)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java b/app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java deleted file mode 100644 index 208fdf292e..0000000000 --- a/app/src/main/java/com/beemdevelopment/aegis/util/YandexUtils.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.beemdevelopment.aegis.util; - -import com.beemdevelopment.aegis.otp.OtpInfoException; -import com.beemdevelopment.aegis.otp.YandexInfo; - -public class YandexUtils { - private static final char CHECKSUM_POLY = 0b1_1000_1111_0011; - public static final int APPROVED_SECRET_LENGTH = 16; - - private YandexUtils() { - } - - private static int getNumberOfLeadingZeros(char value) { - if (value == 0) return 16; - - int n = 0; - if ((value & 0xFF00) == 0) { - n += 8; - value <<= 8; - } - if ((value & 0xF000) == 0) { - n += 4; - value <<= 4; - } - if ((value & 0xC000) == 0) { - n += 2; - value <<= 2; - } - if ((value & 0x8000) == 0) { - n++; - } - - return n; - } - - /** - * Java implementation of ChecksumIsValid - * from https://github.com/norblik/KeeYaOtp/blob/dev/KeeYaOtp/Core/Secret.cs - */ - public static void validateSecret(byte[] secret) throws OtpInfoException { - /* - When secret comes from QR code - we can't test it, - cause it's only 16 byte long. - */ - if (secret.length == APPROVED_SECRET_LENGTH) return; - - if (secret.length != YandexInfo.SECRET_LENGTH) - throw new OtpInfoException("Wrong secret size"); - - char originalChecksum = (char) ((secret[secret.length - 2] & 0x0F) << 8 | secret[secret.length - 1] & 0xff); - - char accum = 0; - int accumBits = 0; - - int inputTotalBitsAvailable = secret.length * 8 - 12; - int inputIndex = 0; - int inputBitsAvailable = 8; - - while (inputTotalBitsAvailable > 0) { - int requiredBits = 13 - accumBits; - if (inputTotalBitsAvailable < requiredBits) requiredBits = inputTotalBitsAvailable; - - while (requiredBits > 0) { - int curInput = (secret[inputIndex] & (1 << inputBitsAvailable) - 1) & 0xff; - int bitsToRead = Math.min(requiredBits, inputBitsAvailable); - - curInput >>= inputBitsAvailable - bitsToRead; - accum = (char) (accum << bitsToRead | curInput); - - inputTotalBitsAvailable -= bitsToRead; - requiredBits -= bitsToRead; - inputBitsAvailable -= bitsToRead; - accumBits += bitsToRead; - - if (inputBitsAvailable == 0) { - inputIndex += 1; - inputBitsAvailable = 8; - } - } - - if (accumBits == 13) accum ^= CHECKSUM_POLY; - accumBits = 16 - getNumberOfLeadingZeros(accum); - } - - if (accum != originalChecksum) { - throw new OtpInfoException("Secret is corrupted. Checksum is not valid"); - } - } -} diff --git a/app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java b/app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java index 7d48528e63..c215962aa1 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/crypto/otp/YAOTPTest.java @@ -2,9 +2,9 @@ import static org.junit.Assert.assertEquals; -import com.beemdevelopment.aegis.crypto.CryptoUtils; import com.beemdevelopment.aegis.encoding.Base32; -import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.YandexInfo; import org.junit.Test; @@ -23,11 +23,13 @@ public class YAOTPTest { }; @Test - public void validateYaOtp() throws InvalidKeyException, NoSuchAlgorithmException, IOException { + public void validateYaOtp() + throws InvalidKeyException, NoSuchAlgorithmException, IOException, OtpInfoException { for (Vector testCase : TEST_CASES) { + byte[] secret = YandexInfo.parseSecret(Base32.decode(testCase.secret)); YAOTP otp = YAOTP.generateOTP( - Base32.decode(testCase.secret.substring(0, 26)), - CryptoUtils.toBytes(testCase.pin.toCharArray()), + secret, + testCase.pin, 8, "HmacSHA256", testCase.timestamp, diff --git a/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java b/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java index aec675b68f..27eab00efb 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java @@ -5,6 +5,7 @@ import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.EncodingException; import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.YandexInfo; import org.junit.Test; @@ -19,14 +20,14 @@ public class YandexUtilsTest { @Test(expected = Test.None.class) public void testValidationOk() throws EncodingException, OtpInfoException { - YandexUtils.validateSecret(getBase32Vector(0)); - YandexUtils.validateSecret(getBase32Vector(1)); + YandexInfo.validateSecret(getBase32Vector(0)); + YandexInfo.validateSecret(getBase32Vector(1)); } @Test public void testYandexSecretValidation() { - assertThrows(OtpInfoException.class, () -> YandexUtils.validateSecret(getBase32Vector(2))); - assertThrows(OtpInfoException.class, () -> YandexUtils.validateSecret(getBase32Vector(3))); + assertThrows(OtpInfoException.class, () -> YandexInfo.validateSecret(getBase32Vector(2))); + assertThrows(OtpInfoException.class, () -> YandexInfo.validateSecret(getBase32Vector(3))); } private byte[] getBase32Vector(int vectorIndex) throws EncodingException { From 473cb7bfebff0000ac7c0fbbfcc6cab6a551b8dc Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Wed, 2 Feb 2022 21:01:15 +0100 Subject: [PATCH 016/389] Set FLAG_IMMUTABLE on the intent of the lock status notification This fixes a crash on API 31 --- .../aegis/services/NotificationService.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java b/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java index 489d8b6b26..43bb28b2d0 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java +++ b/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java @@ -1,8 +1,10 @@ package com.beemdevelopment.aegis.services; +import android.annotation.SuppressLint; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; +import android.os.Build; import android.os.IBinder; import androidx.annotation.Nullable; @@ -24,9 +26,14 @@ public int onStartCommand(Intent intent,int flags, int startId){ return Service.START_STICKY; } + @SuppressLint("LaunchActivityFromNotification") public void serviceMethod() { + int flags = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + flags |= PendingIntent.FLAG_IMMUTABLE; + } Intent intentAction = new Intent(CODE_LOCK_VAULT_ACTION); - PendingIntent lockDatabaseIntent = PendingIntent.getBroadcast(this, 1, intentAction, 0); + PendingIntent lockDatabaseIntent = PendingIntent.getBroadcast(this, 1, intentAction, flags); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CODE_LOCK_STATUS_ID) .setSmallIcon(R.drawable.ic_fingerprint_black_24dp) .setContentTitle(getString(R.string.app_name_full)) From 39a5dc3ee8fcc12be0490e9fac24356dae913479 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Fri, 4 Feb 2022 17:02:37 +0100 Subject: [PATCH 017/389] Update the vault format documentation and improve the diagram --- docs/diagram.svg | 5 +++- docs/vault.md | 72 +++++++++++++++++++++++++----------------------- 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/docs/diagram.svg b/docs/diagram.svg index 9c09d50189..76d5e8e8fd 100644 --- a/docs/diagram.svg +++ b/docs/diagram.svg @@ -1 +1,4 @@ -
Master key
256-bit
[Not supported by viewer]
Password slot
- Encrypted key
- Parameters
- Salt
[Not supported by viewer]
Biometric slot
- Encrypted key
[Not supported by viewer]
Raw slot
- Encrypted key
[Not supported by viewer]
AES-256 GCM
Encrypt master key
[Not supported by viewer]
Android KeyStore
Authorize use of key using biometrics
[Not supported by viewer]
scrypt
Derive key from password
<b>scrypt</b><br>Derive key from password<br>
Vault
[Not supported by viewer]
\ No newline at end of file + + + +
Master key
Generate 256-bit key
Master key...
Password slot
- Encrypted master key
- Scrypt parameters
- Salt
Password slot...
Biometric slot
- Encrypted master key
Biometric slot...
AES-256 GCM
Encrypt master key
AES-256 GCM...
Android KeyStore
Generate key and authorize
using biometrics
Android KeyStore...
scrypt
Derive key from user's password
scrypt...
AES-256 GCM
Encrypt vault contents
AES-256 GCM...
Master key
Master key
AES-256 GCM
Decrypt master key
AES-256 GCM...
Android KeyStore
Authorize usage of key
using biometrics
Android KeyStore...
scrypt
Derive key from user's password
scrypt...
Vault
Unlocked
Vault...
Vault
Locked
Vault...
Vault
Unlocked
Vault...
AES-256 GCM
Decrypt vault contents
AES-256 GCM...
Password slot
- Encrypted master key
- Scrypt parameters
- Salt
Password slot...
Biometric slot
- Encrypted master key
Biometric slot...
Vault
Locked
Vault...
Encryption
Encryption
Decryption
Decryp...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/docs/vault.md b/docs/vault.md index 50b42c228a..a95be41f68 100644 --- a/docs/vault.md +++ b/docs/vault.md @@ -1,8 +1,8 @@ # Aegis Vault -Aegis persists the user's tokens to a file. This file is referred to as the -__vault__. Users can configure the app to store the vault in plain text or to -encrypt it with a password. +Aegis persists the user's token secrets and related information to a file. This +file is referred to as the __vault__. Users can configure the app to store the +vault in plain text or to encrypt it with a password. This document describes Aegis' security design and file format. It's split up into two parts. First, the cryptographic primitives and use of them for @@ -22,22 +22,21 @@ Encryption with Associated Data (AEAD) cipher and a Key Derivation Function __AES-256__ in __GCM__ mode is used as the AEAD cipher to ensure the confidentiality, integrity and authenticity of the vault contents. -It requires a unique 96-bit nonce for each invocation with the same key. -However, it is not possible to use a monotically increasing counter for this in -this case, because a future use case could involve using the vault on multiple -devices simultaneously, which would almost certainly result in nonce reuse. This -is suboptimal, because 96 bits is not large enough to comfortably generate an -unlimited amount of random numbers without getting collisions at some point -either. As a repeat of the nonce would have catastrophic consequences for the -confidentiality and integrity of the ciphertext, NIST strongly recommends not -exceeding 232 invocations when using random nonces with GCM. As such, -the security of the Aegis vault also relies on the assumption that this limit is -never exceeded. In the case of Aegis, this is a reasonable assumption to make, -as it's highly unlikely that a user will ever come close to saving the vault -232 times. +This cipher requires a unique 96-bit nonce for each invocation with the same +key. This is not ideal, because 96 bits is not large enough to comfortably +generate an unlimited amount of random numbers without getting collisions at +some point. It is not possible to use a monotonically increasing counter in this +case, because a future use case could involve using the vault on multiple +devices simultaneously, which would almost certainly result in nonce reuse. As a +repeat of the nonce would have catastrophic consequences for the confidentiality +of the ciphertext, NIST strongly recommends not exceeding 232 +invocations when using random nonces with GCM. As such, the security of the +Aegis vault also relies on the assumption that this limit is never exceeded. +This is a reasonable assumption to make, because it's highly unlikely that an +Aegis user will ever come close to saving the vault 232 times. _Switching to a nonce misuse-resistant cipher like AES-GCM-SIV or a cipher with -a larger (192 bits) nonce like XChaCha-Poly1305 will be explored in the future._ +a larger (192 bits) nonce like XChaCha-Poly1305 will be considered in the future._ #### KDF @@ -52,15 +51,13 @@ with the following parameters: These are the same parameters as Android itself uses to derive a key for full-disk encryption. Because of the memory limitations Android apps have, it's -not possible to increase them without running into OOM conditions on most -devices. +not possible to increase these parameters without running into OOM conditions on +most devices. -_Argon2 is a more modern KDF that provides an advantage over scrypt because it +_Argon2 is a more modern KDF that's a bit more flexible than scrypt, because it allows tweaking the memory-hardness parameter and CPU-hardness parameter -separately, whereas scrypt ties those together into one cost parameter (N). As -many applications have started using Argon2 in production, it seems that it has -withstood the test of time. It will be considered as an alternative option to -switch to in the future._ +separately, whereas scrypt ties those together into one cost parameter (N). It +will be considered as an alternative option to switch to in the future._ ### Encryption @@ -70,8 +67,8 @@ __master key__. Aegis supports unlocking a vault with multiple different credentials. The main credential is a key derived from a user-provided password. In addition to that, -users can also add a key backed by the Android KeyStore (authorized by -biometrics) as a credential. +users can also add a key backed by the Android KeyStore as a credential, which +is only usable after biometrics authentication. #### Slots @@ -92,8 +89,6 @@ the file is not. ### Overview -An attempt was made to create a clear overview of the encryption system. - ![](diagram.svg) ## Format @@ -231,18 +226,23 @@ padding. The ``info`` object holds information specific to the OTP type. The There are a number of supported types: -| Type | ID | -| :------------------ | :------ | -| TOTP | "totp" | -| HOTP | "hotp" | -| Steam | "steam" | +| Type | ID | Spec | +| :------------------ | :------- | :-------- | +| HOTP | "hotp" | [RFC 4226](https://datatracker.ietf.org/doc/html/rfc4226) +| TOTP | "totp" | [RFC 6238](https://datatracker.ietf.org/doc/html/rfc6238) +| Steam | "steam" | N/A +| Yandex | "yandex" | N/A There is no specification available for Steam's OTP algorithm. It's essentially -the same as TOTP, but it uses a different final encoding step. Aegis's +the same as TOTP, but it uses a different final encoding step. Aegis' implementation of it can be found in [crypto/otp/OTP.java](https://github.com/beemdevelopment/Aegis/blob/master/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/OTP.java). -The following algorithms are supported for all OTP types: +There is also no specification available for Yandex's OTP algorithm. Aegis' +implementation can be found in +[crypto/otp/YAOTP.java](https://github.com/beemdevelopment/Aegis/blob/master/app/src/main/java/com/beemdevelopment/aegis/crypto/otp/YAOTP.java) + +The following algorithms are supported for HOTP and TOTP: | Algorithm | ID | | :-------- | :------- | @@ -250,6 +250,8 @@ The following algorithms are supported for all OTP types: | SHA-256 | "SHA256" | | SHA-512 | "SHA512" | +For Steam, only SHA-1 is supported. For Yandex, only SHA-256 is supported. + Example of a TOTP entry: ```json From 119c3838e1e0e6b597d79002b1ffc5f39ab4e19a Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Fri, 4 Feb 2022 20:39:56 +0100 Subject: [PATCH 018/389] Silence lint error related to calling private method through reflection --- .../main/java/com/beemdevelopment/aegis/ui/AegisActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java index 6838f94beb..8d0ba0262b 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java @@ -1,5 +1,6 @@ package com.beemdevelopment.aegis.ui; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -71,6 +72,7 @@ protected void onResume() { _app.setBlockAutoLock(false); } + @SuppressLint("SoonBlockedPrivateApi") @Override public void onLocked(boolean userInitiated) { setResult(RESULT_CANCELED, null); From acfb70c267055c60216508c45dbe6dcae9e0bc00 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 6 Feb 2022 12:54:43 +0100 Subject: [PATCH 019/389] Move Yandex secret validation tests to a different folder --- .../beemdevelopment/aegis/otp/HotpInfoTest.java | 17 +++++++++++++++++ .../otp/{OtpTest.java => TotpInfoTest.java} | 15 +++------------ .../YandexInfoTest.java} | 9 +++------ 3 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 app/src/test/java/com/beemdevelopment/aegis/otp/HotpInfoTest.java rename app/src/test/java/com/beemdevelopment/aegis/otp/{OtpTest.java => TotpInfoTest.java} (56%) rename app/src/test/java/com/beemdevelopment/aegis/{util/YandexUtilsTest.java => otp/YandexInfoTest.java} (81%) diff --git a/app/src/test/java/com/beemdevelopment/aegis/otp/HotpInfoTest.java b/app/src/test/java/com/beemdevelopment/aegis/otp/HotpInfoTest.java new file mode 100644 index 0000000000..dd079536df --- /dev/null +++ b/app/src/test/java/com/beemdevelopment/aegis/otp/HotpInfoTest.java @@ -0,0 +1,17 @@ +package com.beemdevelopment.aegis.otp; + +import static org.junit.Assert.assertEquals; + +import com.beemdevelopment.aegis.crypto.otp.HOTPTest; + +import org.junit.Test; + +public class HotpInfoTest { + @Test + public void testHotpInfoOtp() throws OtpInfoException { + for (int i = 0; i < HOTPTest.VECTORS.length; i++) { + HotpInfo info = new HotpInfo(HOTPTest.SECRET, OtpInfo.DEFAULT_ALGORITHM, OtpInfo.DEFAULT_DIGITS, i); + assertEquals(HOTPTest.VECTORS[i], info.getOtp()); + } + } +} diff --git a/app/src/test/java/com/beemdevelopment/aegis/otp/OtpTest.java b/app/src/test/java/com/beemdevelopment/aegis/otp/TotpInfoTest.java similarity index 56% rename from app/src/test/java/com/beemdevelopment/aegis/otp/OtpTest.java rename to app/src/test/java/com/beemdevelopment/aegis/otp/TotpInfoTest.java index c760e29f73..cc20e64668 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/otp/OtpTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/otp/TotpInfoTest.java @@ -1,21 +1,12 @@ package com.beemdevelopment.aegis.otp; -import com.beemdevelopment.aegis.crypto.otp.HOTPTest; +import static org.junit.Assert.assertEquals; + import com.beemdevelopment.aegis.crypto.otp.TOTPTest; import org.junit.Test; -import static org.junit.Assert.assertEquals; - -public class OtpTest { - @Test - public void testHotpInfoOtp() throws OtpInfoException { - for (int i = 0; i < HOTPTest.VECTORS.length; i++) { - HotpInfo info = new HotpInfo(HOTPTest.SECRET, OtpInfo.DEFAULT_ALGORITHM, OtpInfo.DEFAULT_DIGITS, i); - assertEquals(HOTPTest.VECTORS[i], info.getOtp()); - } - } - +public class TotpInfoTest { @Test public void testTotpInfoOtp() throws OtpInfoException { for (TOTPTest.Vector vector : TOTPTest.VECTORS) { diff --git a/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java b/app/src/test/java/com/beemdevelopment/aegis/otp/YandexInfoTest.java similarity index 81% rename from app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java rename to app/src/test/java/com/beemdevelopment/aegis/otp/YandexInfoTest.java index 27eab00efb..39c41c5f09 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/util/YandexUtilsTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/otp/YandexInfoTest.java @@ -1,16 +1,13 @@ -package com.beemdevelopment.aegis.util; +package com.beemdevelopment.aegis.otp; import static org.junit.Assert.assertThrows; import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.EncodingException; -import com.beemdevelopment.aegis.otp.OtpInfoException; -import com.beemdevelopment.aegis.otp.YandexInfo; import org.junit.Test; -public class YandexUtilsTest { - +public class YandexInfoTest { private static final String[] vectors = new String[]{ "LA2V6KMCGYMWWVEW64RNP3JA3IAAAAAAHTSG4HRZPI", // correct "LA2V6KMCGYMWWVEW64RNP3JA3I", // secret from QR - no validation @@ -19,7 +16,7 @@ public class YandexUtilsTest { }; @Test(expected = Test.None.class) - public void testValidationOk() throws EncodingException, OtpInfoException { + public void testYandexSecretValidationOk() throws EncodingException, OtpInfoException { YandexInfo.validateSecret(getBase32Vector(0)); YandexInfo.validateSecret(getBase32Vector(1)); } From fcb7bf032b00781b61e1c8bd46ec4a4357eb7bc8 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 10 Feb 2022 20:58:06 +0100 Subject: [PATCH 020/389] Add support for importing 2FAS schema v2 backups --- .../aegis/importers/TwoFASImporter.java | 113 ++++++++++++++++-- app/src/main/res/values/strings.xml | 1 + .../aegis/importers/DatabaseImporterTest.java | 41 ++++++- .../2fas_authenticator_encrypted.2fas | 11 ++ .../importers/2fas_authenticator_plain.2fas | 76 ++++++++++++ 5 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_encrypted.2fas create mode 100644 app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_plain.2fas diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java index 6a76474aac..f13b3db39c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java @@ -2,12 +2,17 @@ import android.content.Context; +import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.crypto.CryptoUtils; import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.Base64; import com.beemdevelopment.aegis.encoding.EncodingException; import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.TotpInfo; +import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.util.IOUtils; +import com.beemdevelopment.aegis.util.JsonUtils; import com.beemdevelopment.aegis.vault.VaultEntry; import com.topjohnwu.superuser.io.SuFile; @@ -18,10 +23,27 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; import java.util.ArrayList; import java.util.List; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + public class TwoFASImporter extends DatabaseImporter { + private static final int ITERATION_COUNT = 10_000; + private static final int KEY_SIZE = 256; // bits + public TwoFASImporter(Context context) { super(context); } @@ -37,26 +59,96 @@ public State read(InputStream stream, boolean isInternal) throws DatabaseImporte String json = new String(IOUtils.readAll(stream), StandardCharsets.UTF_8); JSONObject obj = new JSONObject(json); int version = obj.getInt("schemaVersion"); - if (version > 1) { + if (version > 2) { throw new DatabaseImporterException(String.format("Unsupported schema version: %d", version)); } - JSONArray array = obj.getJSONArray("services"); - List entries = new ArrayList<>(); - for (int i = 0; i < array.length(); i++) { - entries.add(array.getJSONObject(i)); + String encryptedString = JsonUtils.optString(obj, "servicesEncrypted"); + if (encryptedString == null) { + JSONArray array = obj.getJSONArray("services"); + List entries = arrayToList(array); + return new DecryptedState(entries); + } + + String[] parts = encryptedString.split(":"); + if (parts.length < 3) { + throw new DatabaseImporterException(String.format("Unexpected format of encrypted data (parts: %d)", parts.length)); } - return new TwoFASImporter.State(entries); + byte[] data = Base64.decode(parts[0]); + byte[] salt = Base64.decode(parts[1]); + byte[] iv = Base64.decode(parts[2]); + return new EncryptedState(data, salt, iv); } catch (IOException | JSONException e) { throw new DatabaseImporterException(e); } } - public static class State extends DatabaseImporter.State { + private static List arrayToList(JSONArray array) throws JSONException { + List list = new ArrayList<>(); + for (int i = 0; i < array.length(); i++) { + list.add(array.getJSONObject(i)); + } + + return list; + } + + public static class EncryptedState extends State { + private final byte[] _data; + private final byte[] _salt; + private final byte[] _iv; + + private EncryptedState(byte[] data, byte[] salt, byte[] iv) { + super(true); + _data = data; + _salt = salt; + _iv = iv; + } + + private SecretKey deriveKey(char[] password) + throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(password, _salt, ITERATION_COUNT, KEY_SIZE); + SecretKey key = factory.generateSecret(spec); + return new SecretKeySpec(key.getEncoded(), "AES"); + } + + public DecryptedState decrypt(char[] password) throws DatabaseImporterException { + try { + SecretKey key = deriveKey(password); + Cipher cipher = CryptoUtils.createDecryptCipher(key, _iv); + byte[] decrypted = cipher.doFinal(_data); + String json = new String(decrypted, StandardCharsets.UTF_8); + return new DecryptedState(arrayToList(new JSONArray(json))); + } catch (BadPaddingException | JSONException e) { + throw new DatabaseImporterException(e); + } catch (NoSuchAlgorithmException + | InvalidKeySpecException + | InvalidAlgorithmParameterException + | NoSuchPaddingException + | InvalidKeyException + | IllegalBlockSizeException e) { + throw new RuntimeException(e); + } + } + + @Override + public void decrypt(Context context, DecryptListener listener) { + Dialogs.showPasswordInputDialog(context, R.string.enter_password_2fas_message, 0, password -> { + try { + DecryptedState state = decrypt(password); + listener.onStateDecrypted(state); + } catch (DatabaseImporterException e) { + listener.onError(e); + } + }, dialog -> listener.onCanceled()); + } + } + + public static class DecryptedState extends DatabaseImporter.State { private final List _entries; - public State(List entries) { + public DecryptedState(List entries) { super(false); _entries = entries; } @@ -83,8 +175,11 @@ private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEn JSONObject info = obj.getJSONObject("otp"); String issuer = info.getString("issuer"); String name = info.optString("account"); + int digits = info.optInt("digits", TotpInfo.DEFAULT_DIGITS); + int period = info.optInt("period", TotpInfo.DEFAULT_PERIOD); + String algorithm = info.optString("algorithm", TotpInfo.DEFAULT_ALGORITHM); - OtpInfo otp = new TotpInfo(secret); + OtpInfo otp = new TotpInfo(secret, algorithm, digits, period); return new VaultEntry(otp, name, issuer); } catch (OtpInfoException | JSONException | EncodingException e) { throw new DatabaseImporterEntryException(e, obj.toString()); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6af9d300a9..1c30e727bb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -119,6 +119,7 @@ The password is incorrect A change in your device\'s security settings has been detected. Please go to \"Aegis -> Settings -> Security -> Biometric unlock\" to disable and re-enable biometric unlock. Please enter your password. We occasionally ask you to do this so that don\'t forget it. + It looks like this 2FAS backup is encrypted. Please enter the password below. It looks like your Authy tokens are encrypted. Please close Aegis, open Authy and unlock the tokens with your password. Instead, Aegis can also attempt to decrypt your Authy tokens for you, if you enter your password below. Please enter the import password diff --git a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java index b213805984..5ba589e5d5 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java @@ -1,5 +1,10 @@ package com.beemdevelopment.aegis.importers; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import android.content.Context; import android.os.Build; @@ -27,11 +32,6 @@ import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - @Config(sdk = { Build.VERSION_CODES.P }) @RunWith(RobolectricTestRunner.class) public class DatabaseImporterTest { @@ -233,7 +233,7 @@ public void testImportAuthenticatorPlus() throws IOException, DatabaseImporterEx } @Test - public void testImportTwoFASAuthenticator() throws DatabaseImporterException, IOException, OtpInfoException { + public void testImportTwoFASAuthenticatorSchema1() throws DatabaseImporterException, IOException, OtpInfoException { List entries = importPlain(TwoFASImporter.class, "2fas_authenticator.json"); for (VaultEntry entry : entries) { // 2FAS Authenticator doesn't support HOTP, different hash algorithms, periods or digits, so fix those up here @@ -243,6 +243,21 @@ public void testImportTwoFASAuthenticator() throws DatabaseImporterException, IO } } + @Test + public void testImportTwoFASAuthenticatorSchema2Plain() throws DatabaseImporterException, IOException, OtpInfoException { + List entries = importPlain(TwoFASImporter.class, "2fas_authenticator_plain.2fas"); + checkImportedTwoFASEntries(entries); + } + + @Test + public void testImportTwoFASAuthenticatorSchema2Encrypted() throws DatabaseImporterException, IOException, OtpInfoException { + List entries = importEncrypted(TwoFASImporter.class, "2fas_authenticator_encrypted.2fas", encryptedState -> { + final char[] password = "test".toCharArray(); + return ((TwoFASImporter.EncryptedState) encryptedState).decrypt(password); + }); + checkImportedTwoFASEntries(entries); + } + private List importPlain(Class type, String resName) throws IOException, DatabaseImporterException { return importPlain(type, resName, false); @@ -285,6 +300,20 @@ private static UUIDMap getEntries(DatabaseImporter.Result result) { return result.getEntries(); } + private void checkImportedTwoFASEntries(List entries) throws OtpInfoException { + for (VaultEntry entry : entries) { + // 2FAS Authenticator doesn't support certain features, so fix those entries up here + VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret()); + OtpInfo info = entryVector.getInfo(); + int period = TotpInfo.DEFAULT_PERIOD; + if (info instanceof TotpInfo) { + period = ((TotpInfo) info).getPeriod(); + } + entryVector.setInfo(new TotpInfo(info.getSecret(), info.getAlgorithm(false), info.getDigits(), period)); + checkImportedEntry(entryVector, entry); + } + } + private void checkImportedAuthyEntries(List entries) throws OtpInfoException { for (VaultEntry entry : entries) { // Authy doesn't support different hash algorithms or periods, so fix those up here diff --git a/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_encrypted.2fas b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_encrypted.2fas new file mode 100644 index 0000000000..8979e56067 --- /dev/null +++ b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_encrypted.2fas @@ -0,0 +1,11 @@ +{ + "services": [], + "updatedAt": 1644513883359, + "schemaVersion": 2, + "appVersionCode": 3080200, + "appVersionName": "3.8.2", + "appOrigin": "android", + "groups": [], + "servicesEncrypted": "5UGWlz8mZARCfCtbDgwAr0pjWORfch9aK+El1Eed9Axi6cQcaJKPTfwlZ3XDS7rGRZbw0yt9fs2ALp3SBcfTAVAHyw3sE9kyQobPieTtdQLeUrOp8JT6HrfGQKwpuZHLGmSmXXjWPmX8cBnESYijg8Es5J6pnsgCkOIMluJndkD8BI3HK8tcVWfk28UA2LZrvtLXY8W3\/+MSL0qJrjMsldqC6+NszCuWAJpqDDPZK9CyD1DbxTYplTHm+UlJWzdNpB0v1VVtGFHhRbrrRuG0BKXXN65nkw9aAUXpSSvgGgNGHZMwsmBVy\/192gKN\/pR8c31gqP4Yv959mjAGob1Wme1SaE0Oc4RGuOM3H8PINSf6acQSH1P3EnIjLm79snANmKW24B78CDvFeVcr9AupfbOYE7Tr2jJ23Gp06r73Jb55beZ5OYKwYGw\/EyzeV6qNd6A2e\/D98gTpyFP5Tcvxh3NWIMmc\/hzUZ449ZbZ1D3daxZN5pjkNHdb6616tpjtj1uxQTQBxwkAU6Gb0PrPasUtHD+QDLL5r1MvO4LRdiFWWrD2aTlhf2OOrrdsG4L6\/CZWgf\/TnyAy7f\/gCpdJ\/VVyESixlb7+AlqlZhOltQNOWd7j+aWUTxhmLpQ55vXOL5slYtibuQkSRVwL6CvY9SB5DpBYp7Yq5tD3ydV+S8S1imk+lq9PcxUO5Ex1TbwXVFucwuRKLlAA1TILIvMHJIyu1Zc8U409E1BThUxKI2S+t3fA9cl3ahTb8hmI1tGojLnwL+9OAIoIoTqZSd\/EWeECpqEadveboGuhdZsCXoi8UYaEYXrtApaGJ+V27POUDQbIALkgqJO0XVpSfeogPvyw08olOs36aUIfsDZ2Uwc8G9\/pg3u1KPeIf87SJO+HSGnoYMw0BjRXwbOuMANoJQrSbm5c34iOklJ7W0IDIIgPfVPKrJaE+D2pzbm0rBnh4\/p8w8an1V3y5b1kEsDWqU0uWI4zToToBrbqVxnEvQEvWqAa2\/MWCUC1QMy+t3KcZCl9wEWiNQ7JuHdY0WnWWjXRCq2AG3tIcqYsidX3MX8ku+Gyl+VqIthyB1RLPDQiNnZOIsfO2fscIfipuPSIDZMsaB4x04KAfhBG1RJHtFWAjgGrjPK\/u6WGydGABxAgNv\/CjmjgKx4ZzIksvkvARiDtcypy09pJz0FsbGEUr7q2vv7VXCHYprhM=:sOQnCCWbD76OERGCIMeG3Y1pg1skE4yKkqCg0iKW3HKyfPeOEEQ+xqSIhyNJiW4Q6HPYa4tYl2zViNv+JK\/pj65fpSEna55myY101Gx2gF7VUjuj1ujd+iz\/ib17DAedbMRu2IY\/bxjJ295yAX+iKxQZSBSt4MwXKCtT1\/HyfTNNmkCSblXQpfhuN0WiwFWGoQB1IPFVwKejD0JknBviVtbFCMg9VPvNYXGk7hyhOh2KnRrNQVGGYwGqoZI20lg1VK0Nn0E6eDc8MMD0ljUHKWQWGDRsriojCA4beaxtWTdtHHy2LBA5kiOv6nb62O5TsInscTxcJ\/2CBUJOVY+eJQ==:fCOmxT+T1J6nT5OX", + "reference": "EIqX5g2b2C5gXoDSmKs\/j6Xby2Opz7rTqDWCO\/AxKAiniUVWV15U5f9\/fpAxr4dygurCZ5ctrj7NSmbAVAF\/FfPyMaGFqT028yVmt499nm8kLqeq64Yx77oQ\/ADeh70cR4VmVfsC6DxPeSCLRQHcUNRm+7y8kEQ8sPu9QRSxVoU\/lEy2fltQSByqgzk3eIjsBxVrHgVG4mVqV6yLT47GLbBdh6CiZiqkNx9pUXhhXR0ej1Sp7tF0hXuZTh7+CTZvfJp2QA\/KA77F9Aa\/u1N1b8+5br8\/legiMjsMvVmKCXyyKzm6+Yt7VdrapKucyB+zJpH6IsTyxQpBsUV4cFGgqJhnX4qfoxzcWV6KqdFi22E=:sOQnCCWbD76OERGCIMeG3Y1pg1skE4yKkqCg0iKW3HKyfPeOEEQ+xqSIhyNJiW4Q6HPYa4tYl2zViNv+JK\/pj65fpSEna55myY101Gx2gF7VUjuj1ujd+iz\/ib17DAedbMRu2IY\/bxjJ295yAX+iKxQZSBSt4MwXKCtT1\/HyfTNNmkCSblXQpfhuN0WiwFWGoQB1IPFVwKejD0JknBviVtbFCMg9VPvNYXGk7hyhOh2KnRrNQVGGYwGqoZI20lg1VK0Nn0E6eDc8MMD0ljUHKWQWGDRsriojCA4beaxtWTdtHHy2LBA5kiOv6nb62O5TsInscTxcJ\/2CBUJOVY+eJQ==:+W6Bn5NEHwBE8S32" +} \ No newline at end of file diff --git a/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_plain.2fas b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_plain.2fas new file mode 100644 index 0000000000..8719a72e91 --- /dev/null +++ b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_plain.2fas @@ -0,0 +1,76 @@ +{ + "services": [ + { + "name": "Deno", + "secret": "4SJHB4GSD43FZBAI7C2HLRJGPQ", + "updatedAt": 1644513743278, + "type": "Unknown", + "otp": { + "label": "Deno:Mason", + "account": "Mason", + "issuer": "Deno", + "digits": 6, + "period": 30, + "algorithm": "SHA1" + }, + "order": { + "position": 0 + } + }, + { + "name": "Airbnb", + "secret": "7ELGJSGXNCCTV3O6LKJWYFV2RA", + "updatedAt": 1644513778354, + "type": "Unknown", + "otp": { + "label": "Airbnb:Elijah", + "account": "Elijah", + "issuer": "Airbnb", + "digits": 8, + "period": 50, + "algorithm": "SHA512" + }, + "order": { + "position": 1 + } + }, + { + "name": "Issuu", + "secret": "YOOMIXWS5GN6RTBPUFFWKTW5M4", + "updatedAt": 1644513786834, + "type": "Unknown", + "otp": { + "label": "Issuu:James", + "account": "James", + "issuer": "Issuu", + "digits": 6, + "algorithm": "SHA1" + }, + "order": { + "position": 2 + } + }, + { + "name": "WWE", + "secret": "5VAML3X35THCEBVRLV24CGBKOY", + "updatedAt": 1644513801596, + "type": "Unknown", + "otp": { + "label": "WWE:Mason", + "account": "Mason", + "issuer": "WWE", + "digits": 8, + "algorithm": "SHA512" + }, + "order": { + "position": 3 + } + } + ], + "updatedAt": 1644513857707, + "schemaVersion": 2, + "appVersionCode": 3080200, + "appVersionName": "3.8.2", + "appOrigin": "android", + "groups": [] +} \ No newline at end of file From 0938d3688a840a7f61bbcb0be3a258648897cd02 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 17 Feb 2022 12:20:02 +0100 Subject: [PATCH 021/389] Set dataExtractionRules in AndroidManifest for Android 12+ --- app/src/main/AndroidManifest.xml | 3 ++- app/src/main/res/xml/backup_rules.xml | 14 ++++++++++---- app/src/main/res/xml/backup_rules_old.xml | 5 +++++ 3 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/xml/backup_rules_old.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 729ce17f4d..f0f3894288 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,8 @@ android:name=".AegisApplication" android:allowBackup="true" android:fullBackupOnly="true" - android:fullBackupContent="@xml/backup_rules" + android:fullBackupContent="@xml/backup_rules_old" + android:dataExtractionRules="@xml/backup_rules" android:backupAgent=".AegisBackupAgent" android:icon="@mipmap/${iconName}" android:label="Aegis" diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml index 617fec3d63..429734787c 100644 --- a/app/src/main/res/xml/backup_rules.xml +++ b/app/src/main/res/xml/backup_rules.xml @@ -1,5 +1,11 @@ - - - - + + + + + + + + + + diff --git a/app/src/main/res/xml/backup_rules_old.xml b/app/src/main/res/xml/backup_rules_old.xml new file mode 100644 index 0000000000..617fec3d63 --- /dev/null +++ b/app/src/main/res/xml/backup_rules_old.xml @@ -0,0 +1,5 @@ + + + + + From b9a81bdd7553d4d10bdb8382506529298f63b2dd Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 17 Feb 2022 13:04:50 +0100 Subject: [PATCH 022/389] Always allow D2D transfers in backup agent --- .../java/com/beemdevelopment/aegis/AegisBackupAgent.java | 9 +++++++-- app/src/main/res/values/strings.xml | 7 ++++--- app/src/main/res/xml/preferences_backups.xml | 4 ++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java b/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java index deb8cc68c6..1a55396bde 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java +++ b/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java @@ -18,7 +18,7 @@ import java.io.InputStream; public class AegisBackupAgent extends BackupAgent { - private static final String TAG = BackupAgent.class.getSimpleName(); + private static final String TAG = AegisBackupAgent.class.getSimpleName(); private Preferences _prefs; @@ -34,7 +34,12 @@ public synchronized void onFullBackup(FullBackupDataOutput data) throws IOExcept Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? data.getTransportFlags() : -1, Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? data.getQuota() : -1)); - if (!_prefs.isAndroidBackupsEnabled()) { + boolean isD2D = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + && (data.getTransportFlags() & FLAG_DEVICE_TO_DEVICE_TRANSFER) == FLAG_DEVICE_TO_DEVICE_TRANSFER; + + if (isD2D) { + Log.i(TAG, "onFullBackup(): allowing D2D transfer"); + } else if (!_prefs.isAndroidBackupsEnabled()) { Log.i(TAG, "onFullBackup() skipped: Android backups disabled in preferences"); return; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c30e727bb..a29f47e671 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,7 +33,7 @@ Import & Export Import backups of Aegis or other authenticator apps. Create manual exports of your Aegis vault. Backups - Set up automatic backups to a location of your choosing or enable participation in Android\'s backup system. + Set up automatic backups to a location of your choosing or enable participation in Android\'s cloud backup system. Icon packs Manage and import icon packs Theme @@ -49,8 +49,9 @@ Manage the list of keys that can decrypt the vault Import from file Import tokens from a file - Participate in Android\'s backup system - Allow Android\'s backup system to include Aegis\' vault in its backups. This is only supported for encrypted vaults. + Android cloud backups + Allow Android\'s cloud backup system to include Aegis\' vault in its backups. This is only supported for encrypted vaults. + Device-to-device (D2D) backups are always allowed, regardless of the setting above Automatically back up the vault Automatically create backups of the vault on external storage when changes are made. This is only supported for encrypted vaults. Directory for backup files diff --git a/app/src/main/res/xml/preferences_backups.xml b/app/src/main/res/xml/preferences_backups.xml index c258e64d9f..3ec41ac326 100644 --- a/app/src/main/res/xml/preferences_backups.xml +++ b/app/src/main/res/xml/preferences_backups.xml @@ -35,5 +35,9 @@ android:title="@string/pref_android_backups_title" android:summary="@string/pref_android_backups_summary" app:iconSpaceReserved="false"/> + From 43c064bb2625a4e3c96c677ebc1b0d897106a0cf Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 17 Feb 2022 13:25:11 +0100 Subject: [PATCH 023/389] Disable the Crowdin workflow for forks This was always failing in forks because the Crowdin API key is only available for the upstream repository. As it should be. --- .github/workflows/crowdin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index 9a637664e3..c7b965450f 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -8,6 +8,7 @@ concurrency: "crowdin-upload-${{ github.ref }}" jobs: upload-sources: runs-on: ubuntu-latest + if: github.repository == 'beemdevelopment/Aegis' steps: - uses: actions/checkout@v2 - name: Install crowdin-cli From d1d5df8051cb47116d8d12dce20aa8743006ec75 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 17 Feb 2022 14:25:32 +0100 Subject: [PATCH 024/389] Add tests for the Ripple panic trigger action --- .../{IntentTest.java => DeepLinkTest.java} | 12 ++--- .../aegis/PanicTriggerTest.java | 54 +++++++++++++++++++ .../beemdevelopment/aegis/Preferences.java | 10 ++-- .../aegis/crypto/pins/DebugSignaturePin.java | 10 ++++ .../pins/GuardianProjectFDroidRSA4096.java | 14 ----- .../aegis/ui/PanicResponderActivity.java | 20 ++++--- 6 files changed, 90 insertions(+), 30 deletions(-) rename app/src/androidTest/java/com/beemdevelopment/aegis/{IntentTest.java => DeepLinkTest.java} (97%) create mode 100644 app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java delete mode 100644 app/src/main/java/com/beemdevelopment/aegis/crypto/pins/GuardianProjectFDroidRSA4096.java diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/IntentTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/DeepLinkTest.java similarity index 97% rename from app/src/androidTest/java/com/beemdevelopment/aegis/IntentTest.java rename to app/src/androidTest/java/com/beemdevelopment/aegis/DeepLinkTest.java index 89ebffd930..2fbef7cce6 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/IntentTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/DeepLinkTest.java @@ -1,5 +1,10 @@ package com.beemdevelopment.aegis; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static junit.framework.TestCase.assertTrue; + import android.content.Intent; import android.net.Uri; @@ -16,14 +21,9 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static junit.framework.TestCase.assertTrue; - @RunWith(AndroidJUnit4.class) @LargeTest -public class IntentTest extends AegisTest { +public class DeepLinkTest extends AegisTest { @Before public void before() { initVault(); diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java new file mode 100644 index 0000000000..c0b3897058 --- /dev/null +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java @@ -0,0 +1,54 @@ +package com.beemdevelopment.aegis; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.content.Intent; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; + +import com.beemdevelopment.aegis.ui.PanicResponderActivity; +import com.beemdevelopment.aegis.vault.VaultManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class PanicTriggerTest extends AegisTest { + @Before + public void before() { + initVault(); + } + + @Test + public void testPanicTriggerDisabled() { + assertFalse(getApp().getPreferences().isPanicTriggerEnabled()); + launchPanic(); + assertFalse(getApp().isVaultLocked()); + assertNotNull(getApp().getVaultManager()); + assertTrue(VaultManager.fileExists(getApp())); + } + + @Test + public void testPanicTriggerEnabled() { + getApp().getPreferences().setIsPanicTriggerEnabled(true); + assertTrue(getApp().getPreferences().isPanicTriggerEnabled()); + launchPanic(); + assertTrue(getApp().isVaultLocked()); + assertNull(getApp().getVaultManager()); + assertFalse(VaultManager.fileExists(getApp())); + } + + private void launchPanic() { + Intent intent = new Intent(PanicResponderActivity.PANIC_TRIGGER_ACTION); + // we need to use the deprecated ActivityTestRule class because of https://github.com/android/android-test/issues/143 + ActivityTestRule rule = new ActivityTestRule<>(PanicResponderActivity.class); + rule.launchActivity(intent); + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java index e5182c5f46..b85e56069f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java +++ b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java @@ -9,15 +9,13 @@ import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; import java.util.ArrayList; import java.util.Collections; - -import org.json.JSONObject; - import java.util.Date; -import java.util.List; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; @@ -63,6 +61,10 @@ public boolean isPanicTriggerEnabled() { return _prefs.getBoolean("pref_panic_trigger", false); } + public void setIsPanicTriggerEnabled(boolean enabled) { + _prefs.edit().putBoolean("pref_panic_trigger", enabled).apply(); + } + public boolean isSecureScreenEnabled() { // screen security should be enabled by default, but not for debug builds return _prefs.getBoolean("pref_secure_screen", !BuildConfig.DEBUG); diff --git a/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java b/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java new file mode 100644 index 0000000000..c4738534ae --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java @@ -0,0 +1,10 @@ +package com.beemdevelopment.aegis.crypto.pins; + +import info.guardianproject.trustedintents.ApkSignaturePin; + +public class DebugSignaturePin extends ApkSignaturePin { + public DebugSignaturePin(byte[] cert) { + certificates = new byte[][] { cert }; + fingerprints = new String[] { getSHA256Fingerprint() }; + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/GuardianProjectFDroidRSA4096.java b/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/GuardianProjectFDroidRSA4096.java deleted file mode 100644 index eb4a862996..0000000000 --- a/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/GuardianProjectFDroidRSA4096.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.beemdevelopment.aegis.crypto.pins; - -import info.guardianproject.trustedintents.ApkSignaturePin; - -public final class GuardianProjectFDroidRSA4096 extends ApkSignaturePin { - - public GuardianProjectFDroidRSA4096() { - fingerprints = new String[]{ - "927f7e38b6acbecd84e02dace33efa9a7a2f0979750f28f585688ee38b3a4e28", - }; - certificates = new byte[][]{ - {48, -126, 3, 95, 48, -126, 2, 71, -96, 3, 2, 1, 2, 2, 4, 28, -30, 107, -102, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 11, 5, 0, 48, 96, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 75, 49, 12, 48, 10, 6, 3, 85, 4, 8, 19, 3, 79, 82, 71, 49, 12, 48, 10, 6, 3, 85, 4, 7, 19, 3, 79, 82, 71, 49, 19, 48, 17, 6, 3, 85, 4, 10, 19, 10, 102, 100, 114, 111, 105, 100, 46, 111, 114, 103, 49, 15, 48, 13, 6, 3, 85, 4, 11, 19, 6, 70, 68, 114, 111, 105, 100, 49, 15, 48, 13, 6, 3, 85, 4, 3, 19, 6, 70, 68, 114, 111, 105, 100, 48, 30, 23, 13, 49, 55, 49, 50, 48, 55, 49, 55, 51, 48, 52, 50, 90, 23, 13, 52, 53, 48, 52, 50, 52, 49, 55, 51, 48, 52, 50, 90, 48, 96, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 75, 49, 12, 48, 10, 6, 3, 85, 4, 8, 19, 3, 79, 82, 71, 49, 12, 48, 10, 6, 3, 85, 4, 7, 19, 3, 79, 82, 71, 49, 19, 48, 17, 6, 3, 85, 4, 10, 19, 10, 102, 100, 114, 111, 105, 100, 46, 111, 114, 103, 49, 15, 48, 13, 6, 3, 85, 4, 11, 19, 6, 70, 68, 114, 111, 105, 100, 49, 15, 48, 13, 6, 3, 85, 4, 3, 19, 6, 70, 68, 114, 111, 105, 100, 48, -126, 1, 34, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 1, 5, 0, 3, -126, 1, 15, 0, 48, -126, 1, 10, 2, -126, 1, 1, 0, -107, -115, -106, 1, -26, 72, -105, -99, 62, 3, -55, 34, 99, -112, -68, -20, -115, 31, 34, 118, -50, 12, -32, -59, 74, -58, -37, -87, 21, 105, 36, -82, 13, -51, 66, 4, 55, -111, 13, -46, -7, -69, -15, 36, 118, -7, 101, -86, 123, -83, -103, 110, 116, -54, 112, 46, 12, 96, -76, -48, -70, -33, -81, 52, 59, 73, 107, -126, -72, -25, 32, 93, 29, -20, 5, -41, -27, 123, -9, 104, -31, -59, -1, -83, -93, 99, 85, -116, -62, -55, 18, -63, 6, -51, -110, 33, 9, 7, -49, 102, -20, -122, -124, -68, 93, -102, 31, 48, 86, 96, -99, 105, -52, 95, 12, 57, 99, 12, -24, 70, 40, -99, -20, -21, -85, -70, -105, 95, 117, -31, 126, -126, -39, 46, -62, 59, -23, -74, 108, -12, -56, -40, -96, 79, -37, -82, 1, 99, -104, 48, -60, 92, 14, 109, 127, -22, 31, 115, -27, 108, 9, 92, 118, -45, 103, 117, 57, -50, -82, 114, -113, 68, -82, 87, 96, 111, 72, 65, -63, 12, 31, -34, -31, -55, -101, 101, 101, 59, 73, -119, -122, 82, 28, 47, -108, -85, 59, 46, 89, -93, -1, 9, -11, -51, 63, -44, 109, -76, -103, -26, -49, -80, 6, 52, -27, 73, -104, 40, 2, -101, -124, 60, -52, -105, -70, -24, -62, 88, 38, 53, -99, -92, 31, 119, 26, 79, 60, -124, 25, -115, -89, -115, -109, 0, 6, 122, -78, 116, 82, 3, 39, -67, 45, -43, 17, -39, 2, 3, 1, 0, 1, -93, 33, 48, 31, 48, 29, 6, 3, 85, 29, 14, 4, 22, 4, 20, 63, 109, -42, -109, 25, 22, 7, -37, -22, -41, -38, 58, -56, 2, -68, -38, -22, 65, -28, -60, 48, 13, 6, 9, 42, -122, 72, -122, -9, 13, 1, 1, 11, 5, 0, 3, -126, 1, 1, 0, 94, 17, 31, 36, 85, -11, 85, 44, 19, -80, -20, -92, -118, 93, 40, 45, 96, 31, -3, -37, -110, -96, 102, 81, 61, -74, -125, -117, -112, 58, -47, 17, 78, -18, 111, -116, 26, -91, 73, 100, 84, -99, 21, 87, 73, -106, 108, -51, -125, -21, 119, -88, -78, 2, 82, -109, -64, -9, -86, -112, -115, 66, -86, 46, 71, 107, -65, 96, -102, 47, 35, -45, -126, 33, 34, 121, -25, -85, -121, -56, -42, 22, -1, -95, -86, 81, 100, -70, 113, 104, -73, 22, -19, 79, -19, 52, 62, 42, 76, -112, 94, -34, 42, -57, -75, -90, -58, 118, 127, -106, -39, 108, -56, -79, 103, -33, 22, 3, 47, 103, -76, -81, 53, -22, -44, -26, -102, 63, -99, 39, 38, -108, 75, 33, 10, 25, -110, -125, -115, 114, -69, 73, -112, 36, 74, 77, -82, -44, 29, -123, -8, -117, 71, -105, 15, -109, 51, 22, 4, 80, 1, 43, 118, 121, -113, -70, 83, -56, 82, -110, 4, -63, 16, -57, 126, -70, 81, 73, 61, 2, -61, 24, -14, -10, 4, -21, 90, 24, 66, 41, -57, -60, -113, -18, -54, -1, 103, -75, 32, -64, 67, 103, 109, -79, -12, -113, -27, 114, 89, 116, 115, -13, -123, -70, 61, -41, -46, -118, 29, -105, -97, -75, 39, -51, 60, 88, 125, 55, -46, -95, 52, 57, 52, -115, 80, 44, 109, 119, -116, -62, -77, -74, -88, 41, 57, -65, -71, -115, -67, 23, 66, -21, 56, 51, -91, 109},}; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java index abb7d879af..b9cac3b7bc 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java @@ -4,6 +4,7 @@ import android.os.Bundle; import android.widget.Toast; +import com.beemdevelopment.aegis.BuildConfig; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.crypto.pins.GuardianProjectFDroidRSA2048; @@ -20,20 +21,28 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Preferences prefs = getPreferences(); - if(!prefs.isPanicTriggerEnabled()) { + if (!prefs.isPanicTriggerEnabled()) { Toast.makeText(this, R.string.panic_trigger_ignore_toast, Toast.LENGTH_SHORT).show(); finish(); + return; } - TrustedIntents trustedIntents = TrustedIntents.get(this); - trustedIntents.addTrustedSigner(GuardianProjectRSA4096.class); - trustedIntents.addTrustedSigner(GuardianProjectFDroidRSA2048.class); + Intent intent; + if (!BuildConfig.DEBUG) { + TrustedIntents trustedIntents = TrustedIntents.get(this); + trustedIntents.addTrustedSigner(GuardianProjectRSA4096.class); + trustedIntents.addTrustedSigner(GuardianProjectFDroidRSA2048.class); + + intent = trustedIntents.getIntentFromTrustedSender(this); + } else { + intent = getIntent(); + } - Intent intent = trustedIntents.getIntentFromTrustedSender(this); if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) { getApp().lock(false); VaultManager.deleteFile(this); finishApp(); + return; } finish(); @@ -41,7 +50,6 @@ protected void onCreate(Bundle savedInstanceState) { private void finishApp() { ExitActivity.exitAppAndRemoveFromRecents(this); - finishAndRemoveTask(); } } From 5ce21a94ea70a31e6bbe1cfc6655f4ed3adcd3b4 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 17 Feb 2022 15:47:27 +0100 Subject: [PATCH 025/389] Only skip panic trigger signature check if running under a test --- app/build.gradle | 1 + .../java/com/beemdevelopment/aegis/AegisTestRunner.java | 4 ++++ .../com/beemdevelopment/aegis/ui/PanicResponderActivity.java | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index f9502fc808..5cf73040d1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,6 +29,7 @@ android { multiDexEnabled true buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\"" buildConfigField "String", "GIT_BRANCH", "\"${getGitBranch()}\"" + buildConfigField "java.util.concurrent.atomic.AtomicBoolean", "TEST", "new java.util.concurrent.atomic.AtomicBoolean(false)" testInstrumentationRunner "com.beemdevelopment.aegis.AegisTestRunner" testInstrumentationRunnerArguments clearPackageData: 'true' diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java index da76f50176..d82f6620f1 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java @@ -10,6 +10,10 @@ import com.beemdevelopment.aegis.util.IOUtils; public class AegisTestRunner extends AndroidJUnitRunner { + static { + BuildConfig.TEST.set(true); + } + @Override public void callApplicationOnCreate(Application app) { Context context = app.getApplicationContext(); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java index b9cac3b7bc..95d5deb5ed 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java @@ -28,7 +28,7 @@ protected void onCreate(Bundle savedInstanceState) { } Intent intent; - if (!BuildConfig.DEBUG) { + if (!BuildConfig.TEST.get()) { TrustedIntents trustedIntents = TrustedIntents.get(this); trustedIntents.addTrustedSigner(GuardianProjectRSA4096.class); trustedIntents.addTrustedSigner(GuardianProjectFDroidRSA2048.class); From 0f3e5c537cd10772e4f489b3832f18eadc4a6113 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 17 Feb 2022 17:46:48 +0100 Subject: [PATCH 026/389] Allow customization of password reminder frequency Close #769 --- .../aegis/PassReminderFreq.java | 56 ++++++++++++++++++ .../beemdevelopment/aegis/Preferences.java | 36 +++++++++--- .../aegis/crypto/pins/DebugSignaturePin.java | 10 ---- .../SecurityPreferencesFragment.java | 41 +++++++++++-- app/src/main/res/values-ar-rSA/strings.xml | 1 - app/src/main/res/values-bg-rBG/strings.xml | 1 - app/src/main/res/values-cs-rCZ/strings.xml | 1 - app/src/main/res/values-da-rDK/strings.xml | 1 - app/src/main/res/values-de-rDE/strings.xml | 1 - app/src/main/res/values-el-rGR/strings.xml | 1 - app/src/main/res/values-es-rES/strings.xml | 1 - app/src/main/res/values-eu-rES/strings.xml | 1 - app/src/main/res/values-fa-rIR/strings.xml | 1 - app/src/main/res/values-fi-rFI/strings.xml | 1 - app/src/main/res/values-fr-rFR/strings.xml | 1 - app/src/main/res/values-hi-rIN/strings.xml | 1 - app/src/main/res/values-hu-rHU/strings.xml | 1 - app/src/main/res/values-in-rID/strings.xml | 1 - app/src/main/res/values-it-rIT/strings.xml | 1 - app/src/main/res/values-ja-rJP/strings.xml | 1 - app/src/main/res/values-lv-rLV/strings.xml | 1 - app/src/main/res/values-nl-rNL/strings.xml | 1 - app/src/main/res/values-pl-rPL/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-pt-rPT/strings.xml | 1 - app/src/main/res/values-ro-rRO/strings.xml | 1 - app/src/main/res/values-ru-rRU/strings.xml | 1 - app/src/main/res/values-sv-rSE/strings.xml | 1 - app/src/main/res/values-tr-rTR/strings.xml | 1 - app/src/main/res/values-uk-rUA/strings.xml | 1 - app/src/main/res/values-vi-rVN/strings.xml | 1 - app/src/main/res/values-zh-rCN/strings.xml | 1 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/strings.xml | 8 ++- app/src/main/res/xml/preferences_security.xml | 5 +- .../aegis/PreferencesTest.java | 58 +++++++++++++++++++ 36 files changed, 189 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/PassReminderFreq.java delete mode 100644 app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java create mode 100644 app/src/test/java/com/beemdevelopment/aegis/PreferencesTest.java diff --git a/app/src/main/java/com/beemdevelopment/aegis/PassReminderFreq.java b/app/src/main/java/com/beemdevelopment/aegis/PassReminderFreq.java new file mode 100644 index 0000000000..56c9df3a6d --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/PassReminderFreq.java @@ -0,0 +1,56 @@ +package com.beemdevelopment.aegis; + +import androidx.annotation.StringRes; + +import java.util.concurrent.TimeUnit; + +public enum PassReminderFreq { + NEVER, + WEEKLY, + BIWEEKLY, + MONTHLY, + QUARTERLY; + + public long getDurationMillis() { + long weeks; + switch (this) { + case WEEKLY: + weeks = 1; + break; + case BIWEEKLY: + weeks = 2; + break; + case MONTHLY: + weeks = 4; + break; + case QUARTERLY: + weeks = 13; + break; + default: + weeks = 0; + break; + } + + return TimeUnit.MILLISECONDS.convert(weeks * 7L, TimeUnit.DAYS); + } + + @StringRes + public int getStringRes() { + switch (this) { + case WEEKLY: + return R.string.password_reminder_freq_weekly; + case BIWEEKLY: + return R.string.password_reminder_freq_biweekly; + case MONTHLY: + return R.string.password_reminder_freq_monthly; + case QUARTERLY: + return R.string.password_reminder_freq_quarterly; + default: + return R.string.password_reminder_freq_never; + } + } + + public static PassReminderFreq fromInteger(int i) { + return PassReminderFreq.values()[i]; + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java index b85e56069f..b8125ba725 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/Preferences.java +++ b/app/src/main/java/com/beemdevelopment/aegis/Preferences.java @@ -19,7 +19,6 @@ import java.util.Locale; import java.util.Map; import java.util.UUID; -import java.util.concurrent.TimeUnit; public class Preferences { public static final int AUTO_LOCK_OFF = 1 << 0; @@ -70,22 +69,44 @@ public boolean isSecureScreenEnabled() { return _prefs.getBoolean("pref_secure_screen", !BuildConfig.DEBUG); } - public boolean isPasswordReminderEnabled() { - return _prefs.getBoolean("pref_password_reminder", true); + public PassReminderFreq getPasswordReminderFrequency() { + final String key = "pref_password_reminder_freq"; + if (_prefs.contains(key) || _prefs.getBoolean("pref_password_reminder", true)) { + int i = _prefs.getInt(key, PassReminderFreq.BIWEEKLY.ordinal()); + return PassReminderFreq.fromInteger(i); + } + + return PassReminderFreq.NEVER; + } + + public void setPasswordReminderFrequency(PassReminderFreq freq) { + _prefs.edit().putInt("pref_password_reminder_freq", freq.ordinal()).apply(); } public boolean isPasswordReminderNeeded() { - long diff = new Date().getTime() - getPasswordReminderTimestamp().getTime(); - long days = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS); - return isPasswordReminderEnabled() && days >= 30; + return isPasswordReminderNeeded(new Date().getTime()); + } + + boolean isPasswordReminderNeeded(long currTime) { + PassReminderFreq freq = getPasswordReminderFrequency(); + if (freq == PassReminderFreq.NEVER) { + return false; + } + + long duration = currTime - getPasswordReminderTimestamp().getTime(); + return duration >= freq.getDurationMillis(); } public Date getPasswordReminderTimestamp() { return new Date(_prefs.getLong("pref_password_reminder_counter", 0)); } + void setPasswordReminderTimestamp(long timestamp) { + _prefs.edit().putLong("pref_password_reminder_counter", timestamp).apply(); + } + public void resetPasswordReminderTimestamp() { - _prefs.edit().putLong("pref_password_reminder_counter", new Date().getTime()).apply(); + setPasswordReminderTimestamp(new Date().getTime()); } public boolean isAccountNameVisible() { @@ -325,4 +346,5 @@ public List getGroupFilter() { return Collections.emptyList(); } } + } diff --git a/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java b/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java deleted file mode 100644 index c4738534ae..0000000000 --- a/app/src/main/java/com/beemdevelopment/aegis/crypto/pins/DebugSignaturePin.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.beemdevelopment.aegis.crypto.pins; - -import info.guardianproject.trustedintents.ApkSignaturePin; - -public class DebugSignaturePin extends ApkSignaturePin { - public DebugSignaturePin(byte[] cert) { - certificates = new byte[][] { cert }; - fingerprints = new String[] { getSHA256Fingerprint() }; - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java index 3595539a45..523a1792fc 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java @@ -1,5 +1,7 @@ package com.beemdevelopment.aegis.ui.fragments; +import static android.text.TextUtils.isDigitsOnly; + import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -14,6 +16,7 @@ import androidx.preference.SwitchPreferenceCompat; import com.beemdevelopment.aegis.BuildConfig; +import com.beemdevelopment.aegis.PassReminderFreq; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.crypto.KeyStoreHandle; @@ -21,8 +24,8 @@ import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer; import com.beemdevelopment.aegis.helpers.BiometricsHelper; import com.beemdevelopment.aegis.services.NotificationService; -import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.SlotManagerActivity; +import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.preferences.SwitchPreference; import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; import com.beemdevelopment.aegis.vault.VaultFileCredentials; @@ -33,12 +36,11 @@ import com.beemdevelopment.aegis.vault.slots.SlotException; import com.beemdevelopment.aegis.vault.slots.SlotList; +import java.util.Arrays; import java.util.List; import javax.crypto.Cipher; -import static android.text.TextUtils.isDigitsOnly; - public class SecurityPreferencesFragment extends PreferencesFragment { private SwitchPreference _encryptionPreference; private SwitchPreference _biometricsPreference; @@ -231,7 +233,28 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { return false; }); - _passwordReminderPreference = findPreference("pref_password_reminder"); + _passwordReminderPreference = findPreference("pref_password_reminder_freq"); + _passwordReminderPreference.setSummary(getPasswordReminderSummary()); + _passwordReminderPreference.setOnPreferenceClickListener((preference) -> { + final PassReminderFreq currFreq = getPreferences().getPasswordReminderFrequency(); + final PassReminderFreq[] items = PassReminderFreq.values(); + final String[] textItems = Arrays.stream(items) + .map(f -> getString(f.getStringRes())) + .toArray(String[]::new); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setTitle(R.string.pref_password_reminder_title) + .setSingleChoiceItems(textItems, currFreq.ordinal(), (dialog, which) -> { + int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); + PassReminderFreq freq = PassReminderFreq.fromInteger(i); + getPreferences().setPasswordReminderFrequency(freq); + _passwordReminderPreference.setSummary(getPasswordReminderSummary()); + dialog.dismiss(); + }) + .setNegativeButton(android.R.string.cancel, null); + Dialogs.showSecureDialog(builder.create()); + return false; + }); } @Override @@ -281,6 +304,16 @@ private void updateEncryptionPreferences() { } } + private String getPasswordReminderSummary() { + PassReminderFreq freq = getPreferences().getPasswordReminderFrequency(); + if (freq == PassReminderFreq.NEVER) { + return getString(R.string.pref_password_reminder_summary_disabled); + } + + String freqString = getString(freq.getStringRes()).toLowerCase(); + return getString(R.string.pref_password_reminder_summary, freqString); + } + private String getAutoLockSummary() { final int[] settings = Preferences.AUTO_LOCK_SETTINGS; final String[] descriptions = getResources().getStringArray(R.array.pref_auto_lock_types); diff --git a/app/src/main/res/values-ar-rSA/strings.xml b/app/src/main/res/values-ar-rSA/strings.xml index 4664677568..61ab12fb3a 100644 --- a/app/src/main/res/values-ar-rSA/strings.xml +++ b/app/src/main/res/values-ar-rSA/strings.xml @@ -49,7 +49,6 @@ تصدير قم بتصدير المخزن تذكير كلمة المرور - اظهار تذكير لإدخال كلمة المرور من حين لآخر حتى لا تنساها أمان الشاشة حظر صور الشاشة والمحاولات الأخرى لالتقاط شاشة التطبيق انقر للكشف diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index 4ad7dfea66..64cf94573d 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -56,7 +56,6 @@ Експортиране Експортирайте трезора Напомняне за парола - Показвайте напомняне за въвеждане на паролата от време на време, за да не я забравите. Сигурност на екрана Блокирайте екранни снимки и други опити за заснемане на екрана в приложението Докоснете, за да разкриете diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 8eb0b45197..9d7e15a118 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -64,7 +64,6 @@ Exportovat Exportovat trezor Připomenutí hesla - Jednou za čas zobrazit výzvu pro zadání hesla, abyste ho nezapomněli. Zabezpečení obrazovky Blokovat snímky obrazovky a další pokusy o zachycení obrazovky v rámci aplikace Zobrazit klepnutím diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index aa1993882f..8823a5a8a5 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -62,7 +62,6 @@ Export Eksportér Boksen Adgangskodepåmindelse - Vis en gang imellem en påmindelse om at angive adgangskoden, så den ikke glemmes. Skærmsikkerhed Blokér skærmfotos og andre forsøg på at optage skærmen inde i appen Tryk for at vise diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index cebb9987bf..a12f24b23a 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -62,7 +62,6 @@ Exportieren Exportiere die Datenbank Passworterinnerung - Von Zeit zu Zeit nach dem Passwort fragen, damit du es nicht vergisst. Bildschirmsicherheit Blockiere Bildschirmfotos und andere Versuche, den Bildschirm innerhalb der App aufzunehmen Zum Aufdecken tippen diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index ae0a38bd24..abd0f7826b 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -62,7 +62,6 @@ Εξαγωγή Εξαγωγή κρύπτης Υπενθύμιση κωδικού - Προβολή υπενθύμισης για εισαγωγή κωδικού περιστασιακά, έτσι ώστε να μην τον ξεχάσετε. Ασφάλεια οθόνης Αποκλεισμός στιγμιότυπων οθόνης και άλλων προσπαθειών για καταγραφή της οθόνης μέσα στην εφαρμογή Πατήστε για εμφάνιση diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index ac2a3e2404..5a94bfa606 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -62,7 +62,6 @@ Exportar Exportar la bóveda Recordatorio de contraseña - Muestra un recordatorio para introducir la contraseña de vez en cuando, para que no la olvide Seguridad en pantalla Bloquea las capturas de pantalla y otros intentos de captura dentro de la aplicación Pulsar para mostrar diff --git a/app/src/main/res/values-eu-rES/strings.xml b/app/src/main/res/values-eu-rES/strings.xml index 6ac2159e55..8b61328ad8 100644 --- a/app/src/main/res/values-eu-rES/strings.xml +++ b/app/src/main/res/values-eu-rES/strings.xml @@ -62,7 +62,6 @@ Esportatu Esportatu biltegia Pasahitza gogoan izan - Pasahitza tarteka sartzeko gogorarazlea erakusten du, ahaztu ez dezazun. Pantaila segurtasuna Blokeatu pantaila-argazkiak eta beste saiakera batzuk aplikazioaren barruan pantaila kapturatzeko Sakatu erakusteko diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 58aa91cea8..ee2158f7c4 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -55,7 +55,6 @@ صادر کردن صادر کردن مخزن یادآوری کلمه عبور - نمایش یادآور برای وارد کردن کلمه عبور. در این صورت کلمه عبور خود را فراموش نمی‌کنید. امنیت صفحه عدم امکان گرفتن اسکرین شات در برنامه نمایش هنگام لمس diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 7299c14044..8f6e071afe 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -61,7 +61,6 @@ Vie Vie holvi Salasanamuistutus - Muistuttaa sinua silloin tällöin syöttämään salasanasi, jottet unohda sitä. Näyttöturvallisuus Estä kuvakaappaukset ja muut yritykset tallentaa näyttöä sovelluksen ollessa käytössä Paljasta napauttamalla diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index eda67b8b1c..b9ea94c153 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -62,7 +62,6 @@ Exporter Exporter le coffre-fort Rappel du mot de passe - Afficher un rappel pour entrer le mot de passe de temps en temps, afin que vous ne l\'oubliiez pas. Sécurité de l\'écran Bloquer les captures d\'écran et autres tentatives de capture de l\'écran dans l\'application Appuyer pour révéler diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 98ff79aeed..ef35e19f8a 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -38,7 +38,6 @@ निर्यात वॉल्ट निर्यात करें पासवर्ड अनुस्मारक - पासवर्ड को दर्ज करने के लिए कभी-कभी एक अनुस्मारक दिखाएं, ताकि आप इसे न भूलें। स्क्रीन सुरक्षा एप्लीकेशन के भीतर स्क्रीनशॉट और स्क्रीन पर कब्जा करने के अन्य प्रयास ब्लॉक करें। देखने के लिए टैप करें diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index e41ccad751..ec8f69f253 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -32,7 +32,6 @@ Exportálás A széf exportálása Jelszóemlékeztető - Időnként a jelszó beírásárá lesz kérve, hogy ne felejtse el azt. Képernyőbiztonság A képernyőképek és a képernyő rögzítésére irányuló egyéb kísérletek blokkolása Koppintás a megjelenítéshez diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml index a8d270bf08..b5e1ef6d21 100644 --- a/app/src/main/res/values-in-rID/strings.xml +++ b/app/src/main/res/values-in-rID/strings.xml @@ -61,7 +61,6 @@ Ekspor Ekspor ke brankas Pengingat kata sandi - Tampilkan pengingat sesekali untuk memasukkan kata sandi, agar Anda tidak lupa. Keamanan layar Blokir tangkapan layar dan upaya lain untuk menangkap layar di dalam aplikasi Sentuh untuk melihat diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 3843e46ed1..02c8c94ac4 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -62,7 +62,6 @@ Esporta Esporta la cassaforte Promemoria password - Mostra un promemoria per inserire la password una volta ogni tanto, in modo da non dimenticarla. Sicurezza schermo Blocca i tentativi di catturare lo schermo all\'interno dell\'app Tocca per mostrare diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 793ade5ef1..358126b677 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -61,7 +61,6 @@ エクスポート 保管庫をエクスポート パスワードを忘れた場合 - パスワードを忘れないように、時々パスワードを入力するリマインダーを表示します。 画面のセキュリティ アプリ内でスクリーンショットやその他のキャプチャをブロックする タップして表示 diff --git a/app/src/main/res/values-lv-rLV/strings.xml b/app/src/main/res/values-lv-rLV/strings.xml index 38944be188..543c82c7ef 100644 --- a/app/src/main/res/values-lv-rLV/strings.xml +++ b/app/src/main/res/values-lv-rLV/strings.xml @@ -63,7 +63,6 @@ Izdot Izdot glabātavas saturu Paroles atgādinājums - Rādīt atgādinājumu, lai laiku pa laikam ievadītu paroli, lai to neaizmirstu. Ekrāna drošība Lietotnē neļaut ekrānuzņēmumus un citus ekrāna tveršanas mēģinājumus Piesist, lai atklātu diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index 72f320e7cb..e0cf285c4e 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -62,7 +62,6 @@ Exporteren Exporteer de kluis Wachtwoordherinnering - Toon een herinnering om het wachtwoord af en toe in te voeren, zodat je deze niet vergeet. Schermbeveiliging Blokkeer screenshots en andere pogingen tot schermopnamen binnen deze app Aantikken om te laten zien diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index dffa6b0d35..4d99ba0f44 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -61,7 +61,6 @@ Eksportuj Eksportuj sejf Przypomnienie hasła - Co pewien czas pokazuj przypomnienie, aby wpisać hasło. Dzięki temu go nie zapomnisz. Zabezpieczenie ekranu Zablokuj zrzuty ekranu i inne próby przechwycenia ekranu w aplikacji Kliknij, aby odkryć diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 90cc12d88a..8396e63e71 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -62,7 +62,6 @@ Exportar Exporta o cofre Lembrete de senha - Exibe um lembrete para inserir a senha de vez em quando, para que você não a esqueça. Segurança de tela Bloqueia screenshots e outras tentativas de capturar a tela dentro do app Tocar para exibir diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index bf1fc80e78..b420d7887c 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -38,7 +38,6 @@ Exportar Exportar o cofre Lembrete de senha - Mostrar um lembrete para inserir a senha de vez em quando, para que você não se esqueça. Segurança da tela Bloquear capturas de tela e outras tentativas de capturar a tela dentro do app Tocar para mostrar diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index a2b909108e..e2d51cf5d9 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -63,7 +63,6 @@ Exportă Exportă seiful Reamintire parolă - Arată un memento pentru a introduce parola o dată la un interval de timp pentru nu o uita. Securitatea ecranului Blochează capturile de ecran și alte încercări de a captura ecranul în cadrul aplicației Atinge pentru a descoperi diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index d5fe5aaf7e..1c30d75395 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -64,7 +64,6 @@ Экспорт в файл Экспорт хранилища Напоминание о пароле - Показывать иногда напоминание с просьбой ввести пароль, чтобы вы не забыли его Безопасность экрана Блокировка снимков экрана и других попыток захвата экрана в приложении Отображение по нажатию diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 302b97ce10..552455c538 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -62,7 +62,6 @@ Exportera Exportera valvet Lösenordspåminnelse - Visa en påminnelse om att ange lösenordet då och då, så att du inte glömmer bort det. Skärmsäkerhet Blockera skärmdumpar och andra försök att avbilda skärmen inne i appen Tryck för att visa diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index 824264e7fa..a350cd501a 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -62,7 +62,6 @@ Dışarıya aktar Kasayı dışarıya aktar Parola hatırlatma - Parolayı periyodik olarak girmem için bir hatırlatma göster ki bu sayede unutmayayım Ekran güvenliği Uygulama içindeyken ekran görüntüsü almayı ve diğer ekran yakalama girişimlerini engelle. Görmek için dokunun diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 2d7b675ef0..bd10b4fdb7 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -58,7 +58,6 @@ Експорт Експортувати сховище Нагадування пароля - Час від часу показувати нагадування з проханням ввести пароль, щоб ви його не забули. Безпека екрану Блокувати знімки екрану та інші спроби захоплення екрану в додатку Торкніться, щоб відобразити diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml index 1d634dfab4..a7a32e9b92 100644 --- a/app/src/main/res/values-vi-rVN/strings.xml +++ b/app/src/main/res/values-vi-rVN/strings.xml @@ -61,7 +61,6 @@ Xuất Xuất kho Lời nhắc mật khẩu - Thỉnh thoảng hiện một lời nhắc nhập mật khẩu, để cho bạn không quên nó. Bảo mật màn hình Chặn ảnh chụp màn hình và các cố gắng khác để chụp lại màn hình trong ứng dụng Nhấn để hiện ra diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c386fd9079..e3a22e52b0 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -61,7 +61,6 @@ 导出 导出数据库 密码提醒 - 每隔一段时间提醒您输入一次密码,以免忘记密码。 屏幕安全 阻止屏幕截图,以及其他试图在本应用中捕获屏幕的行为 点击显示 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b5a40abfb8..3a7dbe4f24 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -51,7 +51,6 @@ 匯出 匯出保險箱 密碼提醒 - 每隔一段時間提醒您輸入一次密碼,以免忘記密碼 螢幕安全性 禁止螢幕截圖,以及阻止其他擷取程式畫面的嘗試。 輕觸以顯示 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a29f47e671..83abe41139 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -68,7 +68,8 @@ Export Export the vault Password reminder - Show a reminder to enter the password every once in a while, so that you don\'t forget it. + Show a %s reminder to enter the password, so that you don\'t forget it. + Disabled Screen security Block screenshots and other attempts to capture the screen within the app Tap to reveal @@ -120,6 +121,11 @@ The password is incorrect A change in your device\'s security settings has been detected. Please go to \"Aegis -> Settings -> Security -> Biometric unlock\" to disable and re-enable biometric unlock. Please enter your password. We occasionally ask you to do this so that don\'t forget it. + Never + Weekly + Biweekly + Monthly + Quarterly It looks like this 2FAS backup is encrypted. Please enter the password below. It looks like your Authy tokens are encrypted. Please close Aegis, open Authy and unlock the tokens with your password. Instead, Aegis can also attempt to decrypt your Authy tokens for you, if you enter your password below. Please enter the import password diff --git a/app/src/main/res/xml/preferences_security.xml b/app/src/main/res/xml/preferences_security.xml index 990212ea74..ea98ce26d2 100644 --- a/app/src/main/res/xml/preferences_security.xml +++ b/app/src/main/res/xml/preferences_security.xml @@ -26,9 +26,8 @@ android:persistent="false" app:iconSpaceReserved="false"/> - Date: Fri, 18 Feb 2022 12:53:29 +0100 Subject: [PATCH 027/389] Use Aegis icon as lock status notification icon --- .../aegis/services/NotificationService.java | 2 +- app/src/main/res/drawable/ic_aegis_notification.xml | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_aegis_notification.xml diff --git a/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java b/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java index 43bb28b2d0..fa22d0af92 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java +++ b/app/src/main/java/com/beemdevelopment/aegis/services/NotificationService.java @@ -35,7 +35,7 @@ public void serviceMethod() { Intent intentAction = new Intent(CODE_LOCK_VAULT_ACTION); PendingIntent lockDatabaseIntent = PendingIntent.getBroadcast(this, 1, intentAction, flags); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CODE_LOCK_STATUS_ID) - .setSmallIcon(R.drawable.ic_fingerprint_black_24dp) + .setSmallIcon(R.drawable.ic_aegis_notification) .setContentTitle(getString(R.string.app_name_full)) .setContentText(getString(R.string.vault_unlocked_state)) .setPriority(NotificationCompat.PRIORITY_DEFAULT) diff --git a/app/src/main/res/drawable/ic_aegis_notification.xml b/app/src/main/res/drawable/ic_aegis_notification.xml new file mode 100644 index 0000000000..1ad42a317a --- /dev/null +++ b/app/src/main/res/drawable/ic_aegis_notification.xml @@ -0,0 +1,12 @@ + + + From c0020684def8e3ae44180058dce9d9e9e55cd187 Mon Sep 17 00:00:00 2001 From: Praveen Kumar Date: Thu, 4 Nov 2021 16:59:19 +0530 Subject: [PATCH 028/389] Change the layout of the entry delete dialog and include more info --- .../aegis/ui/dialogs/Dialogs.java | 19 ++++++--- .../main/res/layout/dialog_delete_entry.xml | 41 +++++++++++-------- app/src/main/res/values-bg-rBG/strings.xml | 1 - app/src/main/res/values-cs-rCZ/strings.xml | 1 - app/src/main/res/values-da-rDK/strings.xml | 1 - app/src/main/res/values-de-rDE/strings.xml | 1 - app/src/main/res/values-el-rGR/strings.xml | 1 - app/src/main/res/values-es-rES/strings.xml | 1 - app/src/main/res/values-eu-rES/strings.xml | 1 - app/src/main/res/values-fr-rFR/strings.xml | 1 - app/src/main/res/values-in-rID/strings.xml | 1 - app/src/main/res/values-it-rIT/strings.xml | 1 - app/src/main/res/values-ja-rJP/strings.xml | 1 - app/src/main/res/values-lt-rLT/strings.xml | 1 - app/src/main/res/values-lv-rLV/strings.xml | 1 - app/src/main/res/values-nl-rNL/strings.xml | 1 - app/src/main/res/values-pl-rPL/strings.xml | 1 - app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values-ro-rRO/strings.xml | 1 - app/src/main/res/values-ru-rRU/strings.xml | 1 - app/src/main/res/values-tr-rTR/strings.xml | 1 - app/src/main/res/values-uk-rUA/strings.xml | 1 - app/src/main/res/values-vi-rVN/strings.xml | 1 - app/src/main/res/values-zh-rCN/strings.xml | 1 - app/src/main/res/values/strings.xml | 2 +- 25 files changed, 38 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/Dialogs.java b/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/Dialogs.java index 2209d19e4b..3108c91d49 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/Dialogs.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/Dialogs.java @@ -71,11 +71,8 @@ public static void showDeleteEntriesDialog(Activity activity, List s TextView textMessage = view.findViewById(R.id.text_message); TextView textExplanation = view.findViewById(R.id.text_explanation); String entries = services.stream() - .map(entry -> !entry.getIssuer().isEmpty() ? entry.getIssuer() - : !entry.getName().isEmpty() ? entry.getName() - : activity.getString(R.string.unknown_issuer) - ) - .collect(Collectors.joining(", ")); + .map(entry -> String.format("• %s", getVaultEntryName(activity, entry))) + .collect(Collectors.joining("\n")); textExplanation.setText(activity.getString(R.string.delete_entry_explanation, entries)); String title, message; @@ -96,6 +93,18 @@ public static void showDeleteEntriesDialog(Activity activity, List s .create()); } + private static String getVaultEntryName(Context context, VaultEntry entry) { + if (!entry.getIssuer().isEmpty() && !entry.getName().isEmpty()) { + return String.format("%s (%s)", entry.getIssuer(), entry.getName()); + } else if (entry.getIssuer().isEmpty() && entry.getName().isEmpty()) { + return context.getString(R.string.unknown_issuer); + } else if (entry.getIssuer().isEmpty()) { + return entry.getName(); + } else { + return entry.getIssuer(); + } + } + public static void showDiscardDialog(Activity activity, DialogInterface.OnClickListener onSave, DialogInterface.OnClickListener onDiscard) { showSecureDialog(new AlertDialog.Builder(activity) .setTitle(activity.getString(R.string.discard_changes)) diff --git a/app/src/main/res/layout/dialog_delete_entry.xml b/app/src/main/res/layout/dialog_delete_entry.xml index fb8b01ed06..0f3215b39d 100644 --- a/app/src/main/res/layout/dialog_delete_entry.xml +++ b/app/src/main/res/layout/dialog_delete_entry.xml @@ -1,21 +1,26 @@ - - + - - \ No newline at end of file + android:paddingStart="25dp" + android:paddingBottom="10dp" + android:paddingEnd="25dp" + android:paddingTop="10dp" + android:orientation="vertical"> + + + + diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index 4ad7dfea66..32b346ea15 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -142,7 +142,6 @@ Четене на файла Изтрий запис Сигурни ли сте, че искате да изтриете този запис? - Това действие не деактивира 2FA за %s. За да предотвратите загуба на достъп, уверете се, че сте деактивирали 2FA или че имате алтернативен начин за генериране на кодове за тази услуга. Изтрий записите Наистина ли искате да изтриете %d записа? diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 8eb0b45197..65a0385206 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -151,7 +151,6 @@ Importování balíčku ikon Odstranit položku Opravdu chcete odstranit tuto položku? - Tato akce nezakáže 2FA pro službu %s. Abyste zabránili ztrátě přístupu, ujistěte se, že jste zakázali 2FA nebo že máte jiný způsob generování kódů pro tuto službu. Odstranit položky Opravdu chcete smazat %d položku? diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index aa1993882f..cca50594ad 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -149,7 +149,6 @@ Importerer ikonpakke Slet post Er du sikker på, du vil slette denne post? - Denne handling deaktiverer ikke 2FA for %s. For at undgå at miste adgang, skal du sørge for, at du har deaktiveret 2FA eller at du har en alternativ måde at generere koder til denne tjeneste. Slet poster Er du sikker på, du vil slette %d post? diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index cebb9987bf..3a991ecda7 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -149,7 +149,6 @@ Symbolpaket wird importiert Eintrag löschen Bist du sicher, dass du diesen Eintrag löschen möchtest? - Diese Aktion deaktiviert nicht die 2FA für %s. Um zu verhindern, dass du den Zugriff verlierst, stelle sicher, dass du 2FA deaktiviert hast oder dass du eine alternative Möglichkeit verwendest, um Codes für diesen Dienst zu generieren. Einträge löschen Bist du sicher, dass du %d Eintrag löschen möchtest? diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index ae0a38bd24..eb68b58378 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -149,7 +149,6 @@ Εισαγωγή πακέτου εικονιδίων Διαγραφή καταχώρησης Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή την καταχώρηση; - Αυτή η ενέργεια δεν απενεργοποιεί το 2FA για %s. Για να αποτρέψετε την απώλεια πρόσβασης, βεβαιωθείτε ότι έχετε απενεργοποιήσει το 2FA ή ότι έχετε έναν εναλλακτικό τρόπο να δημιουργήσετε κωδικούς για αυτήν την υπηρεσία. Διαγραφή καταχωρήσεων Είστε βέβαιοι ότι θέλετε να διαγράψετε %d καταχώρηση; diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index ac2a3e2404..84bed9f6bb 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -149,7 +149,6 @@ Importando paquete de iconos Eliminar entrada ¿Está seguro de que desea eliminar esta entrada? - Esta acción no desactiva 2FA para %s. Para evitar la pérdida de acceso, asegúrese de haber desactivado 2FA o de tener una forma alternativa de generar códigos para este servicio. Eliminar entradas ¿Está seguro de que desea eliminar %d entrada? diff --git a/app/src/main/res/values-eu-rES/strings.xml b/app/src/main/res/values-eu-rES/strings.xml index 6ac2159e55..415320b37f 100644 --- a/app/src/main/res/values-eu-rES/strings.xml +++ b/app/src/main/res/values-eu-rES/strings.xml @@ -149,7 +149,6 @@ Ikono multzoa inportatzen Ezabatu sarrera Ziur al zaude sarrera hau ezabatu nahi duzula? - Akzio honek ez du %s zerbitzuaren 2FA desgaitzen. Sartzen jarraitzeko desaktibatu 2FA edo ziurtatu beste era batera sartu zaitezkeela. Ezabatu sarrerak Ziur al zaude %d sarrera ezabatu nahi duzula? diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index eda67b8b1c..be71e9918c 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -149,7 +149,6 @@ Import du pack d\'icônes Supprimer entrée Êtes-vous sûr de vouloir supprimer cette entrée ? - Cette action ne désactive pas l\'A2F pour %s. Pour éviter de perdre l\'accès, assurez-vous que vous avez désactivé l\'A2F ou que vous avez un autre moyen de générer des codes pour ce service. Supprimer les entrées Êtes-vous sûr de vouloir supprimer %d entrée ? diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml index a8d270bf08..be03b5c088 100644 --- a/app/src/main/res/values-in-rID/strings.xml +++ b/app/src/main/res/values-in-rID/strings.xml @@ -148,7 +148,6 @@ Impor paket ikon Hapus entri Apakah Anda yakin ingin menghapus entri ini? - Tindakan ini tidak mematikan 2FA untuk %s. Untuk menghidari kehilangan akses, pastikan bahwa Anda sudah mematikan 2FA atau Anda memiliki cara alternatif untuk menghasilkan kode untuk layanan ini. Hapus catatan Apakah Anda yakin ingin menghapus %d entri ini? diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 3843e46ed1..a9e20ab20d 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -149,7 +149,6 @@ Importando pacchetto icone Elimina elemento Sei sicuro di voler eliminare questa voce? - L\'autenticazione a due fattori di %s non verrà disabilitata. Per evitare di perdere l\'accesso al tuo account, assicurati di avere disabilitato l\'autenticazione a due fattori o di avere un altro modo per generare codici per questo servizio. Elimina elemento Sei sicuro di voler eliminare %d elemento? diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 793ade5ef1..f4f60f0159 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -148,7 +148,6 @@ アイコンパックをインポート中 エントリーを削除 このエントリーを削除してもよろしいですか? - この操作は、%sの2FAを無効にするものではありません。アクセスできなくなるのを防ぐために、2FAを無効にするか、このサービスのコードを生成する別の方法を用意してください。 エントリーを削除 %d 件のエントリーを削除してもよろしいですか? diff --git a/app/src/main/res/values-lt-rLT/strings.xml b/app/src/main/res/values-lt-rLT/strings.xml index 9b306c01b1..19b80391fa 100644 --- a/app/src/main/res/values-lt-rLT/strings.xml +++ b/app/src/main/res/values-lt-rLT/strings.xml @@ -115,7 +115,6 @@ Šifruojama slėptuvė Ištrinti įrašą Ar tikrai norite ištrinti šį įrašą? - Šis veiksmas neišjungia dviejų faktorių tapatybės nustatymo (2FA), skirto %s. Norėdami neprarasti prieigos, įsitikinkite, kad esate išjungę 2FA arba, kad turite kitą būdą kaip galite sugeneruoti kodus šiai paslaugai. Ištrinti įrašus Ar tikrai norite ištrinti %d įrašą? diff --git a/app/src/main/res/values-lv-rLV/strings.xml b/app/src/main/res/values-lv-rLV/strings.xml index 38944be188..90c23c47c1 100644 --- a/app/src/main/res/values-lv-rLV/strings.xml +++ b/app/src/main/res/values-lv-rLV/strings.xml @@ -150,7 +150,6 @@ Ievieto ikonu pakotni Dzēst ierakstu Vai tiešām izdzēst šo ierakstu? - Šī darbība neatspējo %s 2FA. Lai novērstu piekļuves zaudēšanu, jāpārliecinās, ka ir atspējots 2FA vai ka ir cits veids, kā izveidot kodus šim pakalpojumam. Dzēst ierakstus Vai tiešām izdēst %d ierakstu? diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index 72f320e7cb..bff97151aa 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -149,7 +149,6 @@ Pictogrampakket importeren Item verwijderen Weet je zeker dat je dit item wilt verwijderen? - Deze actie schakelt 2FA voor %s niet uit. Om te voorkomen dat u de toegang verliest, zorg ervoor dat u 2FA hebt uitgeschakeld of dat u een alternatieve manier hebt om codes voor deze dienst te genereren. Items verwijderen Weet je zeker dat je het item %d wilt verwijderen? diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index dffa6b0d35..b6131d6b8f 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -148,7 +148,6 @@ Importowanie pakietu ikon Usuń wpis Czy na pewno chcesz usunąć ten wpis? - Ta akcja nie wyłącza uwierzytelniania dwustopniowego dla %s. Aby zapobiec utracie dostępu, upewnij się, że dla tej usługi wyłączyłeś uwierzytelnianie lub posiadasz alternatywny sposób generowania kodów. Usuń wpisy Czy na pewno chcesz usunąć %d wpis? diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 90cc12d88a..e2991c2829 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -149,7 +149,6 @@ Importando pacote de ícones Deletar entrada Você tem certeza que deseja deletar essa entrada? - Esta ação não desativa a 2FA para %s. Para evitar a perda de acesso, certifique-se de ter desabilitado a 2FA ou de ter uma maneira alternativa de gerar códigos para este serviço. Deletar entradas Você tem certeza que deseja deletar %d entrada? diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index a2b909108e..8bbb695fe3 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -150,7 +150,6 @@ Se importă pachetul de pictograme Ștergere intrare Ești sigur că dorești să ştergi această intrare? - Această acțiune nu dezactivează 2FA pentru %s. Pentru a preveni pierderea accesului, asigurați-vă că ați dezactivat 2FA sau că aveți o modalitate alternativă de a genera coduri pentru acest serviciu. Ştergere intrări Sigur vrei să ștergi %d element? diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index cadf7b53c6..48852d65d9 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -151,7 +151,6 @@ Импорт набора значков Удалить запись Удалить эту запись? - Это действие не отключает 2FA в %s. Чтобы не потерять доступ, убедитесь, что вы отключили 2FA или у вас есть альтернативный способ генерации кодов для данной сервиса. Удалить записи Удалить %d запись? diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index 824264e7fa..f0efc74b56 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -148,7 +148,6 @@ İkon paketi içe aktarılıyor Girdiyi sil Bu girdiyi silmek istediğinize emin misiniz? - Bu eylem %s için çift faktörlü doğrulamayı devre dışı bırakmaz. Erişiminizi kaybetmeyi önlemek için, çift faktörlü doğrulamayı devre dışı bıraktığınıza veya bu hizmet için başka bir kod oluşturma yöntemine sahip olduğunuza emin olun. Girdileri sil %d girdisini silmek istediğinize emin misiniz? diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index cc4c4d86ef..a451c95164 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -144,7 +144,6 @@ Читання файлу Видалити запис Ви впевнені, що хочете видалити цей запис? - Ця дія не вимикає двофакторну автентифікацію (2FA) для %s. Щоб запобігти втраті доступу, переконайтеся, що ви вимкнули 2FA або ж у вас є альтернативний спосіб генерування кодів для цієї служби. Видалити записи Ви впевнені, що хочете видалити %d запис? diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml index 1d634dfab4..96763640d7 100644 --- a/app/src/main/res/values-vi-rVN/strings.xml +++ b/app/src/main/res/values-vi-rVN/strings.xml @@ -148,7 +148,6 @@ Đang nhập gói biểu tượng Xóa mục Bạn có chắc bạn muốn xóa mục này không? - Hành động này không tắt Xác thực 2 yếu tố cho %s. Để tránh việc mất quyền truy cập, hãy chắc chắn rằng bạn đã tắt Xác thực 2 yếu tố hoặc bạn có một cách khác để tạo các mã cho dịch vụ này. Xoá các mục Bạn có chắc bạn muốn xoá %d mục không? diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c386fd9079..4862c99e57 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -148,7 +148,6 @@ 导入图标包 删除条目 您确定要删除此条目吗? - 此操作不会对 %s 禁用2FA。 为了防止失去访问权限,请确保您已禁用 2FA 或者您有其他方式为此服务生成代码。 删除条目 您确定要删除 %d 个项目吗? diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd888a74af..5d47eab1d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,7 +158,7 @@ Importing icon pack Delete entry Are you sure you want to delete this entry? - This action does not disable 2FA for %s. To prevent losing access, make sure that you have disabled 2FA or that you have an alternative way to generate codes for this service. + This action does not disable 2FA for:\n%s\n\nTo prevent losing access, make sure that you have disabled 2FA or that you have an alternative way to generate codes for this service. Delete entries Are you sure you want to delete %d entry? From 71f2b54debf298bbd93b6b46469d4de9bf37f3aa Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 6 Feb 2022 19:00:01 +0100 Subject: [PATCH 029/389] Use Dagger Hilt for dependency injection This gets rid of our own janky dependency injection through the AegisApplication class --- app/build.gradle | 8 +- .../com/beemdevelopment/aegis/AegisTest.java | 37 +- .../aegis/AegisTestApplication.java | 7 + .../aegis/AegisTestRunner.java | 7 + .../beemdevelopment/aegis/DeepLinkTest.java | 5 +- .../com/beemdevelopment/aegis/IntroTest.java | 37 +- .../beemdevelopment/aegis/OverallTest.java | 9 +- .../aegis/PanicTriggerTest.java | 33 +- ...agerTest.java => VaultRepositoryTest.java} | 32 +- .../aegis/AegisApplication.java | 237 +--------- .../aegis/AegisApplicationBase.java | 138 ++++++ .../aegis/AegisBackupAgent.java | 29 +- .../beemdevelopment/aegis/AegisModule.java | 35 ++ .../aegis/ui/AboutActivity.java | 7 +- .../aegis/ui/AegisActivity.java | 168 +++---- .../aegis/ui/AuthActivity.java | 25 +- .../aegis/ui/EditEntryActivity.java | 33 +- .../aegis/ui/GroupManagerActivity.java | 3 + .../aegis/ui/ImportEntriesActivity.java | 13 +- .../aegis/ui/IntroActivity.java | 39 +- .../aegis/ui/MainActivity.java | 188 +++----- .../aegis/ui/PanicResponderActivity.java | 10 +- .../aegis/ui/PreferencesActivity.java | 7 +- .../aegis/ui/ScannerActivity.java | 3 + .../aegis/ui/SlotManagerActivity.java | 3 + .../aegis/ui/TransferEntriesActivity.java | 7 +- .../AppearancePreferencesFragment.java | 26 +- .../BackupsPreferencesFragment.java | 42 +- .../BehaviorPreferencesFragment.java | 7 +- .../IconPacksManagerFragment.java | 22 +- .../ImportExportPreferencesFragment.java | 35 +- .../MainPreferencesFragment.java | 2 +- .../PreferencesFragment.java | 41 +- .../SecurityPreferencesFragment.java | 77 ++-- .../aegis/ui/views/EntryListView.java | 3 +- .../aegis/vault/VaultBackupManager.java | 21 +- .../aegis/vault/VaultManager.java | 431 ++++++++++-------- .../aegis/vault/VaultManagerException.java | 11 - .../aegis/vault/VaultRepository.java | 236 ++++++++++ .../aegis/vault/VaultRepositoryException.java | 11 + app/src/main/res/xml/preferences.xml | 12 +- build.gradle | 1 + 42 files changed, 1139 insertions(+), 959 deletions(-) create mode 100644 app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestApplication.java rename app/src/androidTest/java/com/beemdevelopment/aegis/vault/{VaultManagerTest.java => VaultRepositoryTest.java} (53%) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/AegisModule.java rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/AppearancePreferencesFragment.java (87%) rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/BackupsPreferencesFragment.java (77%) rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/BehaviorPreferencesFragment.java (89%) rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/IconPacksManagerFragment.java (94%) rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/ImportExportPreferencesFragment.java (90%) rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/MainPreferencesFragment.java (85%) rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/PreferencesFragment.java (70%) rename app/src/main/java/com/beemdevelopment/aegis/ui/fragments/{ => preferences}/SecurityPreferencesFragment.java (86%) delete mode 100644 app/src/main/java/com/beemdevelopment/aegis/vault/VaultManagerException.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java create mode 100644 app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepositoryException.java diff --git a/app/build.gradle b/app/build.gradle index 5cf73040d1..d6eed9ec46 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.protobuf' +apply plugin: 'dagger.hilt.android.plugin' def getCmdOutput = { cmd -> def stdout = new ByteArrayOutputStream() @@ -126,10 +127,12 @@ dependencies { def cameraxVersion = '1.0.2' def glideVersion = '4.12.0' def guavaVersion = '31.0.1' + def hiltVersion = '2.38.1' def junitVersion = '4.13.2' def libsuVersion = '3.2.1' annotationProcessor 'androidx.annotation:annotation:1.3.0' + annotationProcessor "com.google.dagger:hilt-compiler:$hiltVersion" annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}" implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -142,12 +145,13 @@ dependencies { implementation "androidx.core:core:1.7.0" implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.documentfile:documentfile:1.0.1' - implementation "androidx.lifecycle:lifecycle-process:2.4.0" + implementation "androidx.lifecycle:lifecycle-process:2.4.1" implementation 'androidx.preference:preference:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation "androidx.viewpager2:viewpager2:1.0.0" implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.caverock:androidsvg-aar:1.4' + implementation "com.google.dagger:hilt-android:$hiltVersion" implementation 'com.github.avito-tech:krop:0.52' implementation "com.github.bumptech.glide:annotations:${glideVersion}" implementation "com.github.bumptech.glide:glide:${glideVersion}" @@ -170,6 +174,8 @@ dependencies { implementation 'info.guardianproject.trustedintents:trustedintents:0.2' implementation 'org.bouncycastle:bcprov-jdk15on:1.70' + androidTestAnnotationProcessor "com.google.dagger:hilt-android-compiler:$hiltVersion" + androidTestImplementation "com.google.dagger:hilt-android-testing:$hiltVersion" androidTestImplementation "androidx.test:core:${androidTestVersion}" androidTestImplementation "androidx.test:runner:${androidTestVersion}" androidTestImplementation "androidx.test:rules:${androidTestVersion}" diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java index 996c7a20c9..6047af78f5 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTest.java @@ -9,15 +9,17 @@ import com.beemdevelopment.aegis.crypto.CryptoUtils; import com.beemdevelopment.aegis.crypto.SCryptParameters; import com.beemdevelopment.aegis.otp.OtpInfo; -import com.beemdevelopment.aegis.vault.Vault; import com.beemdevelopment.aegis.vault.VaultEntry; import com.beemdevelopment.aegis.vault.VaultFileCredentials; import com.beemdevelopment.aegis.vault.VaultManager; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.vault.VaultRepository; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; import com.beemdevelopment.aegis.vault.slots.PasswordSlot; import com.beemdevelopment.aegis.vault.slots.SlotException; import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Rule; import java.lang.reflect.InvocationTargetException; import java.security.InvalidAlgorithmParameterException; @@ -26,28 +28,41 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; +import javax.inject.Inject; + +import dagger.hilt.android.testing.HiltAndroidRule; public abstract class AegisTest { public static final String VAULT_PASSWORD = "test"; - protected AegisApplication getApp() { - return (AegisApplication) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); + @Rule + public HiltAndroidRule hiltRule = new HiltAndroidRule(this); + + @Inject + protected VaultManager _vaultManager; + + @Inject + protected Preferences _prefs; + + @Before + public void init() { + hiltRule.inject(); } - protected VaultManager getVault() { - return getApp().getVaultManager(); + protected AegisApplicationBase getApp() { + return (AegisApplicationBase) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext(); } - protected VaultManager initVault() { + protected VaultRepository initVault() { VaultFileCredentials creds = generateCredentials(); - VaultManager vault = getApp().initVaultManager(new Vault(), creds); + VaultRepository vault; try { - vault.save(false); - } catch (VaultManagerException e) { + vault = _vaultManager.init(creds); + } catch (VaultRepositoryException e) { throw new RuntimeException(e); } - getApp().getPreferences().setIntroDone(true); + _prefs.setIntroDone(true); return vault; } diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestApplication.java b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestApplication.java new file mode 100644 index 0000000000..62f5b7f21e --- /dev/null +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestApplication.java @@ -0,0 +1,7 @@ +package com.beemdevelopment.aegis; + +import dagger.hilt.android.testing.CustomTestApplication; + +@CustomTestApplication(AegisApplicationBase.class) +public interface AegisTestApplication { +} diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java index d82f6620f1..44c0684546 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/AegisTestRunner.java @@ -1,6 +1,7 @@ package com.beemdevelopment.aegis; import android.app.Application; +import android.app.Instrumentation; import android.content.Context; import android.preference.PreferenceManager; @@ -14,6 +15,12 @@ public class AegisTestRunner extends AndroidJUnitRunner { BuildConfig.TEST.set(true); } + @Override + public Application newApplication(ClassLoader cl, String name, Context context) + throws ClassNotFoundException, IllegalAccessException, InstantiationException { + return Instrumentation.newApplication(AegisTestApplication_Application.class, context); + } + @Override public void callApplicationOnCreate(Application app) { Context context = app.getApplicationContext(); diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/DeepLinkTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/DeepLinkTest.java index 2fbef7cce6..c6572cc8d3 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/DeepLinkTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/DeepLinkTest.java @@ -21,7 +21,10 @@ import org.junit.Test; import org.junit.runner.RunWith; +import dagger.hilt.android.testing.HiltAndroidTest; + @RunWith(AndroidJUnit4.class) +@HiltAndroidTest @LargeTest public class DeepLinkTest extends AegisTest { @Before @@ -37,7 +40,7 @@ public void doDeepLinkIntent() { onView(withId(R.id.action_save)).perform(click()); - VaultEntry createdEntry = (VaultEntry) getVault().getEntries().toArray()[0]; + VaultEntry createdEntry = (VaultEntry) _vaultManager.getVault().getEntries().toArray()[0]; assertTrue(createdEntry.equivalates(entry)); } diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/IntroTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/IntroTest.java index ecc40163de..ac438b839d 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/IntroTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/IntroTest.java @@ -1,12 +1,25 @@ package com.beemdevelopment.aegis; +import static androidx.test.espresso.Espresso.onView; +import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; +import static androidx.test.espresso.action.ViewActions.replaceText; +import static androidx.test.espresso.action.ViewActions.typeText; +import static androidx.test.espresso.assertion.ViewAssertions.matches; +import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.withId; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.Matchers.not; + import androidx.test.espresso.ViewInteraction; import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; import com.beemdevelopment.aegis.ui.IntroActivity; -import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepository; import com.beemdevelopment.aegis.vault.slots.BiometricSlot; import com.beemdevelopment.aegis.vault.slots.PasswordSlot; import com.beemdevelopment.aegis.vault.slots.SlotList; @@ -15,20 +28,10 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static androidx.test.espresso.Espresso.onView; -import static androidx.test.espresso.action.ViewActions.click; -import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard; -import static androidx.test.espresso.action.ViewActions.replaceText; -import static androidx.test.espresso.action.ViewActions.typeText; -import static androidx.test.espresso.assertion.ViewAssertions.matches; -import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; -import static androidx.test.espresso.matcher.ViewMatchers.withId; -import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertNull; -import static junit.framework.TestCase.assertTrue; -import static org.hamcrest.Matchers.not; +import dagger.hilt.android.testing.HiltAndroidTest; @RunWith(AndroidJUnit4.class) +@HiltAndroidTest @LargeTest public class IntroTest extends AegisTest { @Rule @@ -50,9 +53,9 @@ public void doIntro_None() { next.perform(click()); next.perform(click()); - VaultManager vault = getVault(); + VaultRepository vault = _vaultManager.getVault(); assertFalse(vault.isEncryptionEnabled()); - assertNull(getVault().getCredentials()); + assertNull(vault.getCredentials()); } @Test @@ -79,8 +82,8 @@ public void doIntro_Password() { next.perform(click()); next.perform(click()); - VaultManager vault = getVault(); - SlotList slots = getVault().getCredentials().getSlots(); + VaultRepository vault = _vaultManager.getVault(); + SlotList slots = vault.getCredentials().getSlots(); assertTrue(vault.isEncryptionEnabled()); assertTrue(slots.has(PasswordSlot.class)); assertFalse(slots.has(BiometricSlot.class)); diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java index f41b49257e..ff4e62d9a2 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/OverallTest.java @@ -31,7 +31,7 @@ import com.beemdevelopment.aegis.otp.YandexInfo; import com.beemdevelopment.aegis.ui.MainActivity; import com.beemdevelopment.aegis.vault.VaultEntry; -import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepository; import com.beemdevelopment.aegis.vault.slots.PasswordSlot; import org.junit.Rule; @@ -42,7 +42,10 @@ import java.util.Arrays; import java.util.List; +import dagger.hilt.android.testing.HiltAndroidTest; + @RunWith(AndroidJUnit4.class) +@HiltAndroidTest @LargeTest public class OverallTest extends AegisTest { private static final String _groupName = "Test"; @@ -61,7 +64,7 @@ public void doOverallTest() { next.perform(click()); onView(withId(R.id.btnNext)).perform(click()); - VaultManager vault = getVault(); + VaultRepository vault = _vaultManager.getVault(); assertTrue(vault.isEncryptionEnabled()); assertTrue(vault.getCredentials().getSlots().has(PasswordSlot.class)); @@ -122,7 +125,7 @@ public void doOverallTest() { onView(withText(R.string.lock)).perform(click()); onView(withId(R.id.text_password)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard()); onView(withId(R.id.button_decrypt)).perform(click()); - vault = getVault(); + vault = _vaultManager.getVault(); openContextualActionModeOverflowMenu(); onView(withText(R.string.action_settings)).perform(click()); diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java index c0b3897058..13a94741a9 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/PanicTriggerTest.java @@ -1,25 +1,28 @@ package com.beemdevelopment.aegis; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import android.content.Intent; import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; +import androidx.test.filters.SmallTest; import androidx.test.rule.ActivityTestRule; import com.beemdevelopment.aegis.ui.PanicResponderActivity; -import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepository; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import dagger.hilt.android.testing.HiltAndroidTest; + @RunWith(AndroidJUnit4.class) -@LargeTest +@HiltAndroidTest +@SmallTest public class PanicTriggerTest extends AegisTest { @Before public void before() { @@ -28,21 +31,25 @@ public void before() { @Test public void testPanicTriggerDisabled() { - assertFalse(getApp().getPreferences().isPanicTriggerEnabled()); + assertFalse(_prefs.isPanicTriggerEnabled()); launchPanic(); - assertFalse(getApp().isVaultLocked()); - assertNotNull(getApp().getVaultManager()); - assertTrue(VaultManager.fileExists(getApp())); + assertTrue(_vaultManager.isVaultLoaded()); + _vaultManager.getVault(); + assertFalse(_vaultManager.isVaultFileLoaded()); + assertNull(_vaultManager.getVaultFileError()); + assertTrue(VaultRepository.fileExists(getApp())); } @Test public void testPanicTriggerEnabled() { - getApp().getPreferences().setIsPanicTriggerEnabled(true); - assertTrue(getApp().getPreferences().isPanicTriggerEnabled()); + _prefs.setIsPanicTriggerEnabled(true); + assertTrue(_prefs.isPanicTriggerEnabled()); launchPanic(); - assertTrue(getApp().isVaultLocked()); - assertNull(getApp().getVaultManager()); - assertFalse(VaultManager.fileExists(getApp())); + assertFalse(_vaultManager.isVaultLoaded()); + assertThrows(IllegalStateException.class, () -> _vaultManager.getVault()); + assertFalse(_vaultManager.isVaultFileLoaded()); + assertNull(_vaultManager.getVaultFileError()); + assertFalse(VaultRepository.fileExists(getApp())); } private void launchPanic() { diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/vault/VaultManagerTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/vault/VaultRepositoryTest.java similarity index 53% rename from app/src/androidTest/java/com/beemdevelopment/aegis/vault/VaultManagerTest.java rename to app/src/androidTest/java/com/beemdevelopment/aegis/vault/VaultRepositoryTest.java index 163f108f57..79a365cb31 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/vault/VaultManagerTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/vault/VaultRepositoryTest.java @@ -1,5 +1,11 @@ package com.beemdevelopment.aegis.vault; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -10,30 +16,28 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import dagger.hilt.android.testing.HiltAndroidTest; @RunWith(AndroidJUnit4.class) +@HiltAndroidTest @SmallTest -public class VaultManagerTest extends AegisTest { +public class VaultRepositoryTest extends AegisTest { @Before public void before() { initVault(); } @Test - public void testToggleEncryption() throws VaultManagerException { - getVault().disableEncryption(); - assertFalse(getVault().isEncryptionEnabled()); - assertNull(getVault().getCredentials()); + public void testToggleEncryption() throws VaultRepositoryException { + VaultRepository vault = _vaultManager.getVault(); + _vaultManager.disableEncryption(); + assertFalse(vault.isEncryptionEnabled()); + assertNull(vault.getCredentials()); VaultFileCredentials creds = generateCredentials(); - getVault().enableEncryption(creds); - assertTrue(getVault().isEncryptionEnabled()); - assertNotNull(getVault().getCredentials()); - assertEquals(getVault().getCredentials().getSlots().findAll(PasswordSlot.class).size(), 1); + _vaultManager.enableEncryption(creds); + assertTrue(vault.isEncryptionEnabled()); + assertNotNull(vault.getCredentials()); + assertEquals(vault.getCredentials().getSlots().findAll(PasswordSlot.class).size(), 1); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/AegisApplication.java b/app/src/main/java/com/beemdevelopment/aegis/AegisApplication.java index 9411d4e808..b1c3e22325 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/AegisApplication.java +++ b/app/src/main/java/com/beemdevelopment/aegis/AegisApplication.java @@ -1,239 +1,8 @@ package com.beemdevelopment.aegis; -import android.app.Application; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ShortcutInfo; -import android.content.pm.ShortcutManager; -import android.graphics.drawable.Icon; -import android.os.Build; +import dagger.hilt.android.HiltAndroidApp; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.ProcessLifecycleOwner; +@HiltAndroidApp +public class AegisApplication extends AegisApplicationBase { -import com.beemdevelopment.aegis.icons.IconPackManager; -import com.beemdevelopment.aegis.services.NotificationService; -import com.beemdevelopment.aegis.ui.MainActivity; -import com.beemdevelopment.aegis.util.IOUtils; -import com.beemdevelopment.aegis.vault.Vault; -import com.beemdevelopment.aegis.vault.VaultFile; -import com.beemdevelopment.aegis.vault.VaultFileCredentials; -import com.beemdevelopment.aegis.vault.VaultManager; -import com.beemdevelopment.aegis.vault.VaultManagerException; -import com.mikepenz.iconics.Iconics; -import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic; -import com.topjohnwu.superuser.Shell; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class AegisApplication extends Application { - private VaultFile _vaultFile; - private VaultManager _manager; - private Preferences _prefs; - private List _lockListeners; - private boolean _blockAutoLock; - private IconPackManager _iconPackManager; - - private static final String CODE_LOCK_STATUS_ID = "lock_status_channel"; - private static final String CODE_LOCK_VAULT_ACTION = "lock_vault"; - - static { - // to access other app's internal storage directory, run libsu commands inside the global mount namespace - Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER)); - } - - @Override - public void onCreate() { - super.onCreate(); - _prefs = new Preferences(this); - _lockListeners = new ArrayList<>(); - _iconPackManager = new IconPackManager(this); - - Iconics.init(this); - Iconics.registerFont(new MaterialDesignIconic()); - - // listen for SCREEN_OFF events - ScreenOffReceiver receiver = new ScreenOffReceiver(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_SCREEN_OFF); - intentFilter.addAction(CODE_LOCK_VAULT_ACTION); - registerReceiver(receiver, intentFilter); - - // lock the app if the user moves the application to the background - ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleObserver()); - - // clear the cache directory on startup, to make sure no temporary vault export files remain - IOUtils.clearDirectory(getCacheDir(), false); - - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { - initAppShortcuts(); - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - initNotificationChannels(); - } - } - - public boolean isVaultLocked() { - return _manager == null; - } - - /** - * Loads the vault file from disk at the default location, stores an internal - * reference to it for future use and returns it. This must only be called before - * initVaultManager() or after lock(). - */ - public VaultFile loadVaultFile() throws VaultManagerException { - if (!isVaultLocked()) { - throw new AssertionError("loadVaultFile() may only be called before initVaultManager() or after lock()"); - } - - if (_vaultFile == null) { - _vaultFile = VaultManager.readVaultFile(this); - } - - return _vaultFile; - } - - /** - * Initializes the vault manager by decrypting the given vaultFile with the given - * creds. This removes the internal reference to the raw vault file. - */ - public VaultManager initVaultManager(VaultFile vaultFile, VaultFileCredentials creds) throws VaultManagerException { - _vaultFile = null; - _manager = VaultManager.init(this, vaultFile, creds); - return _manager; - } - - /** - * Initializes the vault manager with the given vault and creds. This removes the - * internal reference to the raw vault file. - */ - public VaultManager initVaultManager(Vault vault, VaultFileCredentials creds) { - _vaultFile = null; - _manager = new VaultManager(this, vault, creds); - return _manager; - } - - public VaultManager getVaultManager() { - return _manager; - } - - public IconPackManager getIconPackManager() { - return _iconPackManager; - } - - public Preferences getPreferences() { - return _prefs; - } - - public boolean isAutoLockEnabled(int autoLockType) { - return _prefs.isAutoLockTypeEnabled(autoLockType) && !isVaultLocked() && _manager.isEncryptionEnabled(); - } - - public void registerLockListener(LockListener listener) { - _lockListeners.add(listener); - } - - public void unregisterLockListener(LockListener listener) { - _lockListeners.remove(listener); - } - - /** - * Sets whether to block automatic lock on minimization. This should only be called - * by activities before invoking an intent that shows a DocumentsUI, because that - * action leads AppLifecycleObserver to believe that the app has been minimized. - */ - public void setBlockAutoLock(boolean block) { - _blockAutoLock = block; - } - - /** - * Locks the vault and the app. - * @param userInitiated whether or not the user initiated the lock in MainActivity. - */ - public void lock(boolean userInitiated) { - _manager.destroy(); - _manager = null; - - for (LockListener listener : _lockListeners) { - listener.onLocked(userInitiated); - } - - stopService(new Intent(AegisApplication.this, NotificationService.class)); - } - - @RequiresApi(api = Build.VERSION_CODES.N_MR1) - private void initAppShortcuts() { - ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); - if (shortcutManager == null) { - return; - } - - Intent intent = new Intent(this, MainActivity.class); - intent.putExtra("action", "scan"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.setAction(Intent.ACTION_MAIN); - - ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "shortcut_new") - .setShortLabel(getString(R.string.new_entry)) - .setLongLabel(getString(R.string.add_new_entry)) - .setIcon(Icon.createWithResource(this, R.drawable.ic_qr_code)) - .setIntent(intent) - .build(); - - shortcutManager.setDynamicShortcuts(Collections.singletonList(shortcut)); - } - - private void initNotificationChannels() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = getString(R.string.channel_name_lock_status); - String description = getString(R.string.channel_description_lock_status); - int importance = NotificationManager.IMPORTANCE_LOW; - - NotificationChannel channel = new NotificationChannel(CODE_LOCK_STATUS_ID, name, importance); - channel.setDescription(description); - - NotificationManager notificationManager = getSystemService(NotificationManager.class); - notificationManager.createNotificationChannel(channel); - } - } - - private class AppLifecycleObserver implements LifecycleEventObserver { - @Override - public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { - if (event == Lifecycle.Event.ON_STOP - && isAutoLockEnabled(Preferences.AUTO_LOCK_ON_MINIMIZE) - && !_blockAutoLock) { - lock(false); - } - } - } - - private class ScreenOffReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (isAutoLockEnabled(Preferences.AUTO_LOCK_ON_DEVICE_LOCK)) { - lock(false); - } - } - } - - public interface LockListener { - /** - * When called, the app/vault has been locked and the listener should perform its cleanup operations. - * @param userInitiated whether or not the user initiated the lock in MainActivity. - */ - void onLocked(boolean userInitiated); - } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java b/app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java new file mode 100644 index 0000000000..bb8a5ec4cf --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/AegisApplicationBase.java @@ -0,0 +1,138 @@ +package com.beemdevelopment.aegis; + +import android.app.Application; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleEventObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.ProcessLifecycleOwner; + +import com.beemdevelopment.aegis.ui.MainActivity; +import com.beemdevelopment.aegis.util.IOUtils; +import com.beemdevelopment.aegis.vault.VaultManager; +import com.mikepenz.iconics.Iconics; +import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic; +import com.topjohnwu.superuser.Shell; + +import java.util.Collections; + +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPoint; +import dagger.hilt.android.EarlyEntryPoints; +import dagger.hilt.components.SingletonComponent; + +public abstract class AegisApplicationBase extends Application { + private static final String CODE_LOCK_STATUS_ID = "lock_status_channel"; + private static final String CODE_LOCK_VAULT_ACTION = "lock_vault"; + + private VaultManager _vaultManager; + + static { + // to access other app's internal storage directory, run libsu commands inside the global mount namespace + Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER)); + } + + @Override + public void onCreate() { + super.onCreate(); + _vaultManager = EarlyEntryPoints.get(this, EntryPoint.class).getVaultManager(); + + Iconics.init(this); + Iconics.registerFont(new MaterialDesignIconic()); + + // listen for SCREEN_OFF events + ScreenOffReceiver receiver = new ScreenOffReceiver(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_SCREEN_OFF); + intentFilter.addAction(CODE_LOCK_VAULT_ACTION); + registerReceiver(receiver, intentFilter); + + // lock the app if the user moves the application to the background + ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifecycleObserver()); + + // clear the cache directory on startup, to make sure no temporary vault export files remain + IOUtils.clearDirectory(getCacheDir(), false); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + initAppShortcuts(); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + initNotificationChannels(); + } + } + + @RequiresApi(api = Build.VERSION_CODES.N_MR1) + private void initAppShortcuts() { + ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); + if (shortcutManager == null) { + return; + } + + Intent intent = new Intent(this, MainActivity.class); + intent.putExtra("action", "scan"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.setAction(Intent.ACTION_MAIN); + + ShortcutInfo shortcut = new ShortcutInfo.Builder(this, "shortcut_new") + .setShortLabel(getString(R.string.new_entry)) + .setLongLabel(getString(R.string.add_new_entry)) + .setIcon(Icon.createWithResource(this, R.drawable.ic_qr_code)) + .setIntent(intent) + .build(); + + shortcutManager.setDynamicShortcuts(Collections.singletonList(shortcut)); + } + + private void initNotificationChannels() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = getString(R.string.channel_name_lock_status); + String description = getString(R.string.channel_description_lock_status); + int importance = NotificationManager.IMPORTANCE_LOW; + + NotificationChannel channel = new NotificationChannel(CODE_LOCK_STATUS_ID, name, importance); + channel.setDescription(description); + + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } + + private class AppLifecycleObserver implements LifecycleEventObserver { + @Override + public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { + if (event == Lifecycle.Event.ON_STOP + && _vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_MINIMIZE) + && !_vaultManager.isAutoLockBlocked()) { + _vaultManager.lock(false); + } + } + } + + private class ScreenOffReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_DEVICE_LOCK)) { + _vaultManager.lock(false); + } + } + } + + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + interface EntryPoint { + VaultManager getVaultManager(); + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java b/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java index 1a55396bde..df989a7d79 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java +++ b/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java @@ -10,22 +10,31 @@ import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepository; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import dagger.hilt.InstallIn; +import dagger.hilt.android.EarlyEntryPoint; +import dagger.hilt.android.EarlyEntryPoints; +import dagger.hilt.components.SingletonComponent; + public class AegisBackupAgent extends BackupAgent { private static final String TAG = AegisBackupAgent.class.getSimpleName(); + private VaultManager _vaultManager; private Preferences _prefs; @Override public void onCreate() { super.onCreate(); - _prefs = new Preferences(this); + + EntryPoint entryPoint = EarlyEntryPoints.get(this, EntryPoint.class); + _vaultManager = entryPoint.getVaultManager(); + _prefs = entryPoint.getPreferences(); } @Override @@ -47,9 +56,8 @@ public synchronized void onFullBackup(FullBackupDataOutput data) throws IOExcept // first copy the vault to the files/backup directory createBackupDir(); File vaultBackupFile = getVaultBackupFile(); - try (FileInputStream inStream = VaultManager.getAtomicFile(this).openRead(); - FileOutputStream outStream = new FileOutputStream(vaultBackupFile)) { - IOUtils.copy(inStream, outStream); + try { + _vaultManager.getVault().backupTo(vaultBackupFile); } catch (IOException e) { Log.e(TAG, String.format("onFullBackup() failed: %s", e)); deleteBackupDir(); @@ -77,7 +85,7 @@ public synchronized void onRestoreFile(ParcelFileDescriptor data, long size, Fil File vaultBackupFile = getVaultBackupFile(); if (destination.getCanonicalFile().equals(vaultBackupFile.getCanonicalFile())) { try (InputStream inStream = new FileInputStream(vaultBackupFile)) { - VaultManager.writeToFile(this, inStream); + VaultRepository.writeToFile(this, inStream); } catch (IOException e) { Log.e(TAG, String.format("onRestoreFile() failed: dest=%s, error=%s", destination, e)); throw e; @@ -118,6 +126,13 @@ private void deleteBackupDir() { } private File getVaultBackupFile() { - return new File(new File(getFilesDir(), "backup"), VaultManager.FILENAME); + return new File(new File(getFilesDir(), "backup"), VaultRepository.FILENAME); + } + + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + interface EntryPoint { + Preferences getPreferences(); + VaultManager getVaultManager(); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/AegisModule.java b/app/src/main/java/com/beemdevelopment/aegis/AegisModule.java new file mode 100644 index 0000000000..6172c12261 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/AegisModule.java @@ -0,0 +1,35 @@ +package com.beemdevelopment.aegis; + +import android.content.Context; + +import com.beemdevelopment.aegis.icons.IconPackManager; +import com.beemdevelopment.aegis.vault.VaultManager; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.android.qualifiers.ApplicationContext; +import dagger.hilt.components.SingletonComponent; + +@Module +@InstallIn(SingletonComponent.class) +public class AegisModule { + @Provides + @Singleton + public static IconPackManager provideIconPackManager(@ApplicationContext Context context) { + return new IconPackManager(context); + } + + @Provides + @Singleton + public static VaultManager provideVaultManager(@ApplicationContext Context context) { + return new VaultManager(context); + } + + @Provides + public static Preferences providePreferences(@ApplicationContext Context context) { + return new Preferences(context); + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/AboutActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/AboutActivity.java index f732e398ce..823708b164 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/AboutActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/AboutActivity.java @@ -41,8 +41,11 @@ public class AboutActivity extends AegisActivity { @Override protected void onCreate(Bundle savedInstanceState) { LayoutInflaterCompat.setFactory2(getLayoutInflater(), new IconicsLayoutInflater2(getDelegate())); - super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } + setContentView(R.layout.activity_about); setSupportActionBar(findViewById(R.id.toolbar)); @@ -124,7 +127,7 @@ private void openMail(String mailaddress) { mailIntent.putExtra(Intent.EXTRA_EMAIL, mailaddress); mailIntent.putExtra(Intent.EXTRA_SUBJECT, R.string.app_name_full); - startActivity(Intent.createChooser(mailIntent, this.getString(R.string.email))); + startActivity(Intent.createChooser(mailIntent, getString(R.string.email))); } private void showThirdPartyLicenseDialog() { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java index 8d0ba0262b..e90bc7ba3d 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/AegisActivity.java @@ -2,7 +2,6 @@ import android.annotation.SuppressLint; import android.app.Activity; -import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; @@ -10,58 +9,60 @@ import android.widget.Toast; import androidx.annotation.CallSuper; -import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; -import com.beemdevelopment.aegis.AegisApplication; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.Theme; import com.beemdevelopment.aegis.ThemeMap; -import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.icons.IconPackManager; +import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Locale; import java.util.Map; -public abstract class AegisActivity extends AppCompatActivity implements AegisApplication.LockListener { - private AegisApplication _app; +import javax.inject.Inject; + +import dagger.hilt.InstallIn; +import dagger.hilt.android.AndroidEntryPoint; +import dagger.hilt.android.EarlyEntryPoint; +import dagger.hilt.android.EarlyEntryPoints; +import dagger.hilt.components.SingletonComponent; + +@AndroidEntryPoint +public abstract class AegisActivity extends AppCompatActivity implements VaultManager.LockListener { + protected Preferences _prefs; + + @Inject + protected VaultManager _vaultManager; + + @Inject + protected IconPackManager _iconPackManager; @Override protected void onCreate(Bundle savedInstanceState) { - _app = (AegisApplication) getApplication(); - // set the theme and locale before creating the activity - Preferences prefs = getPreferences(); + _prefs = EarlyEntryPoints.get(this, PrefEntryPoint.class).getPreferences(); onSetTheme(); - setLocale(prefs.getLocale()); + setLocale(_prefs.getLocale()); super.onCreate(savedInstanceState); - // if the app was killed, relaunch MainActivity and close everything else - if (savedInstanceState != null && isOrphan()) { - Intent intent = new Intent(this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - finish(); - return; - } - // set FLAG_SECURE on the window of every AegisActivity - if (getPreferences().isSecureScreenEnabled()) { + if (_prefs.isSecureScreenEnabled()) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); } // register a callback to listen for lock events - _app.registerLockListener(this); + _vaultManager.registerLockListener(this); } @Override @CallSuper protected void onDestroy() { - _app.unregisterLockListener(this); + _vaultManager.unregisterLockListener(this); super.onDestroy(); } @@ -69,7 +70,7 @@ protected void onDestroy() { @Override protected void onResume() { super.onResume(); - _app.setBlockAutoLock(false); + _vaultManager.setBlockAutoLock(false); } @SuppressLint("SoonBlockedPrivateApi") @@ -86,14 +87,6 @@ public void onLocked(boolean userInitiated) { } } - protected AegisApplication getApp() { - return _app; - } - - protected Preferences getPreferences() { - return _app.getPreferences(); - } - /** * Called when the activity is expected to set its theme. */ @@ -111,7 +104,7 @@ protected void setTheme(Map themeMap) { } protected Theme getConfiguredTheme() { - Theme theme = getPreferences().getCurrentTheme(); + Theme theme = _prefs.getCurrentTheme(); if (theme == Theme.SYSTEM || theme == Theme.SYSTEM_AMOLED) { int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; @@ -131,87 +124,60 @@ protected void setLocale(Locale locale) { Configuration config = new Configuration(); config.locale = locale; - this.getResources().updateConfiguration(config, this.getResources().getDisplayMetrics()); + getResources().updateConfiguration(config, getResources().getDisplayMetrics()); } - protected boolean saveVault(boolean backup) { + protected boolean saveVault() { try { - getApp().getVaultManager().save(backup); + _vaultManager.save(); return true; - } catch (VaultManagerException e) { + } catch (VaultRepositoryException e) { Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show(); return false; } } - /** - * Reports whether this Activity instance has become an orphan. This can happen if - * the vault was locked by an external trigger while the Activity was still open. - */ - protected boolean isOrphan() { - return !(this instanceof MainActivity) && !(this instanceof AuthActivity) && !(this instanceof IntroActivity) && _app.isVaultLocked(); - } - - public static class Helper { - private Helper() { - + protected boolean saveAndBackupVault() { + try { + _vaultManager.saveAndBackup(); + return true; + } catch (VaultRepositoryException e) { + Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show(); + return false; } + } - /** - * Starts an external activity, temporarily blocks automatic lock of Aegis and - * shows an error dialog if the target activity is not found. - */ - public static void startExtActivityForResult(Activity activity, Intent intent, int requestCode) { - AegisApplication app = (AegisApplication) activity.getApplication(); - app.setBlockAutoLock(true); - - try { - activity.startActivityForResult(intent, requestCode, null); - } catch (ActivityNotFoundException e) { - e.printStackTrace(); - - if (isDocsAction(intent.getAction())) { - Dialogs.showErrorDialog(activity, R.string.documentsui_error, e); - } else { - throw e; - } - } + /** + * Closes this activity if it has become an orphan (isOrphan() == true) and launches MainActivity. + * @param savedInstanceState the bundle passed to onCreate. + * @return whether to abort onCreate. + */ + protected boolean abortIfOrphan(Bundle savedInstanceState) { + if (savedInstanceState == null || !isOrphan()) { + return false; } - /** - * Starts an external activity, temporarily blocks automatic lock of Aegis and - * shows an error dialog if the target activity is not found. - */ - public static void startExtActivity(Fragment fragment, Intent intent) { - startExtActivityForResult(fragment, intent, -1); - } + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + finish(); + return true; + } - /** - * Starts an external activity, temporarily blocks automatic lock of Aegis and - * shows an error dialog if the target activity is not found. - */ - public static void startExtActivityForResult(Fragment fragment, Intent intent, int requestCode) { - AegisApplication app = (AegisApplication) fragment.getActivity().getApplication(); - app.setBlockAutoLock(true); - - try { - fragment.startActivityForResult(intent, requestCode, null); - } catch (ActivityNotFoundException e) { - e.printStackTrace(); - - if (isDocsAction(intent.getAction())) { - Dialogs.showErrorDialog(fragment.getContext(), R.string.documentsui_error, e); - } else { - throw e; - } - } - } + /** + * Reports whether this Activity instance has become an orphan. This can happen if + * the vault was killed/locked by an external trigger while the Activity was still open. + */ + private boolean isOrphan() { + return !(this instanceof MainActivity) + && !(this instanceof AuthActivity) + && !(this instanceof IntroActivity) + && !_vaultManager.isVaultLoaded(); + } - private static boolean isDocsAction(@Nullable String action) { - return action != null && (action.equals(Intent.ACTION_GET_CONTENT) - || action.equals(Intent.ACTION_CREATE_DOCUMENT) - || action.equals(Intent.ACTION_OPEN_DOCUMENT) - || action.equals(Intent.ACTION_OPEN_DOCUMENT_TREE)); - } + @EarlyEntryPoint + @InstallIn(SingletonComponent.class) + public interface PrefEntryPoint { + Preferences getPreferences(); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/AuthActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/AuthActivity.java index b8282644aa..14ec3e934e 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/AuthActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/AuthActivity.java @@ -22,8 +22,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.biometric.BiometricPrompt; -import com.beemdevelopment.aegis.AegisApplication; -import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.ThemeMap; import com.beemdevelopment.aegis.crypto.KeyStoreHandle; @@ -37,7 +35,7 @@ import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; import com.beemdevelopment.aegis.vault.VaultFile; import com.beemdevelopment.aegis.vault.VaultFileCredentials; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; import com.beemdevelopment.aegis.vault.slots.BiometricSlot; import com.beemdevelopment.aegis.vault.slots.PasswordSlot; import com.beemdevelopment.aegis.vault.slots.Slot; @@ -64,12 +62,9 @@ public class AuthActivity extends AegisActivity { // biometric prompt by setting 'inhibitBioPrompt' to true through the intent private boolean _inhibitBioPrompt; - private Preferences _prefs; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - _prefs = new Preferences(this); setContentView(R.layout.activity_auth); _textPassword = findViewById(R.id.text_password); LinearLayout boxBiometricInfo = findViewById(R.id.box_biometric_info); @@ -94,15 +89,14 @@ protected void onCreate(Bundle savedInstanceState) { _inhibitBioPrompt = savedInstanceState.getBoolean("inhibitBioPrompt", false); } - try { - VaultFile vaultFile = getApp().loadVaultFile(); - _slots = vaultFile.getHeader().getSlots(); - } catch (VaultManagerException e) { - e.printStackTrace(); - Dialogs.showErrorDialog(this, R.string.vault_load_error, e, (dialog, which) -> onBackPressed()); + if (_vaultManager.getVaultFileError() != null) { + Dialogs.showErrorDialog(this, R.string.vault_load_error, _vaultManager.getVaultFileError(), (dialog, which) -> onBackPressed()); return; } + VaultFile vaultFile = _vaultManager.getVaultFile(); + _slots = vaultFile.getHeader().getSlots(); + // only show the biometric prompt if the api version is new enough, permission is granted, a scanner is found and a biometric slot is found if (_slots.has(BiometricSlot.class) && BiometricsHelper.isAvailable(this)) { boolean invalidated = false; @@ -266,12 +260,11 @@ private void finish(MasterKey key, boolean isSlotRepaired) { VaultFileCredentials creds = new VaultFileCredentials(key, _slots); try { - AegisApplication app = getApp(); - app.initVaultManager(app.loadVaultFile(), creds); + _vaultManager.unlock(creds); if (isSlotRepaired) { - saveVault(true); + saveAndBackupVault(); } - } catch (VaultManagerException e) { + } catch (VaultRepositoryException e) { e.printStackTrace(); Dialogs.showErrorDialog(this, R.string.decryption_corrupt_error, e); return; diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index 87e17d82d1..948299d79e 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -55,7 +55,7 @@ import com.beemdevelopment.aegis.util.Cloner; import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.vault.VaultEntry; -import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepository; import com.bumptech.glide.Glide; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.request.target.CustomTarget; @@ -118,16 +118,16 @@ public class EditEntryActivity extends AegisActivity { private RelativeLayout _advancedSettingsHeader; private RelativeLayout _advancedSettings; - private VaultManager _vault; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } setContentView(R.layout.activity_edit_entry); setSupportActionBar(findViewById(R.id.toolbar)); - _vault = getApp().getVaultManager(); - _groups = _vault.getGroups(); + _groups = _vaultManager.getVault().getGroups(); ActionBar bar = getSupportActionBar(); bar.setHomeAsUpIndicator(R.drawable.ic_close); @@ -137,7 +137,7 @@ protected void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); UUID entryUUID = (UUID) intent.getSerializableExtra("entryUUID"); if (entryUUID != null) { - _origEntry = _vault.getEntryByUUID(entryUUID); + _origEntry = _vaultManager.getVault().getEntryByUUID(entryUUID); } else { _origEntry = (VaultEntry) intent.getSerializableExtra("newEntry"); _isManual = intent.getBooleanExtra("isManual", false); @@ -170,7 +170,7 @@ protected void onCreate(Bundle savedInstanceState) { DropdownHelper.fillDropdown(this, _dropdownGroup, _dropdownGroupList); // if this is NOT a manually entered entry, move the "Secret" field from basic to advanced settings - if (!_isNew || (_isNew && !_isManual)) { + if (!_isNew || !_isManual) { int secretIndex = 0; LinearLayout layoutSecret = findViewById(R.id.layout_secret); LinearLayout layoutBasic = findViewById(R.id.layout_basic); @@ -313,7 +313,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) } }); - _textUsageCount.setText(getPreferences().getUsageCount(entryUUID).toString()); + _textUsageCount.setText(_prefs.getUsageCount(entryUUID).toString()); } private void updateAdvancedFieldStatus(String otpType) { @@ -476,16 +476,16 @@ private void startImageSelectionActivity() { Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_icon)); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { fileIntent }); - AegisActivity.Helper.startExtActivityForResult(this, chooserIntent, PICK_IMAGE_REQUEST); + _vaultManager.startActivityForResult(this, chooserIntent, PICK_IMAGE_REQUEST); } private void resetUsageCount() { - getPreferences().resetUsageCount(_origEntry.getUUID()); + _prefs.resetUsageCount(_origEntry.getUUID()); _textUsageCount.setText("0"); } private void startIconSelection() { - List iconPacks = getApp().getIconPackManager().getIconPacks().stream() + List iconPacks = _iconPackManager.getIconPacks().stream() .sorted(Comparator.comparing(IconPack::getName)) .collect(Collectors.toList()); if (iconPacks.size() == 0) { @@ -579,17 +579,18 @@ private void addAndFinish(VaultEntry entry) { // vault to disk failed, causing the user to tap 'Save' again. Calling addEntry // again would cause a crash in that case, so the isEntryDuplicate check prevents // that. - if (_isNew && !_vault.isEntryDuplicate(entry)) { - _vault.addEntry(entry); + VaultRepository vault = _vaultManager.getVault(); + if (_isNew && !vault.isEntryDuplicate(entry)) { + vault.addEntry(entry); } else { - _vault.replaceEntry(entry); + vault.replaceEntry(entry); } saveAndFinish(entry, false); } private void deleteAndFinish(VaultEntry entry) { - _vault.removeEntry(entry); + _vaultManager.getVault().removeEntry(entry); saveAndFinish(entry, true); } @@ -598,7 +599,7 @@ private void saveAndFinish(VaultEntry entry, boolean delete) { intent.putExtra("entryUUID", entry.getUUID()); intent.putExtra("delete", delete); - if (saveVault(true)) { + if (saveAndBackupVault()) { setResult(RESULT_OK, intent); finish(); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java index 1e7b81f026..e842c6fb42 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/GroupManagerActivity.java @@ -26,6 +26,9 @@ public class GroupManagerActivity extends AegisActivity implements GroupAdapter. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } setContentView(R.layout.activity_groups); setSupportActionBar(findViewById(R.id.toolbar)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java index a4fc4ccf00..02399c76ad 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ImportEntriesActivity.java @@ -27,7 +27,7 @@ import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter; import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.vault.VaultEntry; -import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepository; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.topjohnwu.superuser.Shell; @@ -47,6 +47,9 @@ public class ImportEntriesActivity extends AegisActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } setContentView(R.layout.activity_import_entries); setSupportActionBar(findViewById(R.id.toolbar)); @@ -71,7 +74,7 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(v -> { - if (getApp().getVaultManager().getEntries().size() > 0 + if (_vaultManager.getVault().getEntries().size() > 0 && _menu.findItem(R.id.toggle_wipe_vault).isChecked()) { showWipeEntriesDialog(); } else { @@ -200,7 +203,7 @@ private void showDetailedErrorDialog(List errors .setMessage(message) .setPositiveButton(android.R.string.ok, null) .setNeutralButton(android.R.string.copy, (dialog2, which2) -> { - ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE); + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("text/plain", message); clipboard.setPrimaryClip(clip); Toast.makeText(this, R.string.errors_copied, Toast.LENGTH_SHORT).show(); @@ -217,7 +220,7 @@ private void showWipeEntriesDialog() { } private void saveAndFinish(boolean wipeEntries) { - VaultManager vault = getApp().getVaultManager(); + VaultRepository vault = _vaultManager.getVault(); if (wipeEntries) { vault.wipeEntries(); } @@ -234,7 +237,7 @@ private void saveAndFinish(boolean wipeEntries) { vault.addEntry(entry); } - if (saveVault(true)) { + if (saveAndBackupVault()) { String toastMessage = getResources().getQuantityString(R.plurals.imported_entries_count, selectedEntries.size(), selectedEntries.size()); Toast.makeText(this, toastMessage, Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/IntroActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/IntroActivity.java index eb9846da27..12e1ca4303 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/IntroActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/IntroActivity.java @@ -1,5 +1,10 @@ package com.beemdevelopment.aegis.ui; +import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC; +import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_INVALID; +import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE; +import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS; + import android.os.Bundle; import android.view.inputmethod.InputMethodManager; @@ -12,22 +17,11 @@ import com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide; import com.beemdevelopment.aegis.ui.slides.SecuritySetupSlide; import com.beemdevelopment.aegis.ui.slides.WelcomeSlide; -import com.beemdevelopment.aegis.vault.Vault; -import com.beemdevelopment.aegis.vault.VaultFile; import com.beemdevelopment.aegis.vault.VaultFileCredentials; -import com.beemdevelopment.aegis.vault.VaultFileException; -import com.beemdevelopment.aegis.vault.VaultManager; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; import com.beemdevelopment.aegis.vault.slots.BiometricSlot; import com.beemdevelopment.aegis.vault.slots.PasswordSlot; -import org.json.JSONObject; - -import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC; -import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_INVALID; -import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE; -import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS; - public class IntroActivity extends IntroBaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { @@ -73,31 +67,16 @@ protected void onDonePressed() { throw new RuntimeException(String.format("State of SecuritySetupSlide not properly propagated, cryptType: %d, creds: %s", cryptType, creds)); } - Vault vault = new Vault(); - VaultFile vaultFile = new VaultFile(); try { - JSONObject obj = vault.toJson(); - if (cryptType == CRYPT_TYPE_NONE) { - vaultFile.setContent(obj); - } else { - vaultFile.setContent(obj, creds); - } - - VaultManager.save(getApplicationContext(), vaultFile); - } catch (VaultManagerException | VaultFileException e) { + _vaultManager.init(creds); + } catch (VaultRepositoryException e) { e.printStackTrace(); Dialogs.showErrorDialog(this, R.string.vault_init_error, e); return; } - if (cryptType == CRYPT_TYPE_NONE) { - getApp().initVaultManager(vault, null); - } else { - getApp().initVaultManager(vault, creds); - } - // skip the intro from now on - getPreferences().setIntroDone(true); + _prefs.setIntroDone(true); setResult(RESULT_OK); finish(); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java index 2b0442b3c5..495b1cd34d 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/MainActivity.java @@ -17,11 +17,9 @@ import android.widget.LinearLayout; import android.widget.Toast; -import androidx.annotation.NonNull; import androidx.appcompat.view.ActionMode; import androidx.appcompat.widget.SearchView; -import com.beemdevelopment.aegis.AegisApplication; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.SortCategory; @@ -33,13 +31,10 @@ import com.beemdevelopment.aegis.otp.GoogleAuthInfo; import com.beemdevelopment.aegis.otp.GoogleAuthInfoException; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.ui.fragments.BackupsPreferencesFragment; -import com.beemdevelopment.aegis.ui.fragments.PreferencesFragment; +import com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFragment; +import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment; import com.beemdevelopment.aegis.ui.views.EntryListView; import com.beemdevelopment.aegis.vault.VaultEntry; -import com.beemdevelopment.aegis.vault.VaultFile; -import com.beemdevelopment.aegis.vault.VaultManager; -import com.beemdevelopment.aegis.vault.VaultManagerException; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.zxing.BinaryBitmap; @@ -58,6 +53,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.TreeSet; import java.util.UUID; public class MainActivity extends AegisActivity implements EntryListView.Listener { @@ -74,13 +70,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene private static final int CODE_PERM_CAMERA = 0; private static final int CODE_PERM_READ_STORAGE = 1; - private AegisApplication _app; - private VaultManager _vault; private boolean _loaded; private boolean _searchSubmitted; - - private boolean _isAuthenticating; - private boolean _isDoingIntro; private boolean _isRecreated; private List _selectedEntries; @@ -100,29 +91,24 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setSupportActionBar(findViewById(R.id.toolbar)); - - _app = (AegisApplication) getApplication(); - _vault = _app.getVaultManager(); _loaded = false; if (savedInstanceState != null) { _isRecreated = true; - _isAuthenticating = savedInstanceState.getBoolean("isAuthenticating"); - _isDoingIntro = savedInstanceState.getBoolean("isDoingIntro"); } _entryListView = (EntryListView) getSupportFragmentManager().findFragmentById(R.id.key_profiles); _entryListView.setListener(this); - _entryListView.setCodeGroupSize(getPreferences().getCodeGroupSize()); - _entryListView.setShowAccountName(getPreferences().isAccountNameVisible()); - _entryListView.setHighlightEntry(getPreferences().isEntryHighlightEnabled()); - _entryListView.setPauseFocused(getPreferences().isPauseFocusedEnabled()); - _entryListView.setTapToReveal(getPreferences().isTapToRevealEnabled()); - _entryListView.setTapToRevealTime(getPreferences().getTapToRevealTime()); - _entryListView.setSortCategory(getPreferences().getCurrentSortCategory(), false); - _entryListView.setViewMode(getPreferences().getCurrentViewMode()); - _entryListView.setIsCopyOnTapEnabled(getPreferences().isCopyOnTapEnabled()); - _entryListView.setPrefGroupFilter(getPreferences().getGroupFilter()); + _entryListView.setCodeGroupSize(_prefs.getCodeGroupSize()); + _entryListView.setShowAccountName(_prefs.isAccountNameVisible()); + _entryListView.setHighlightEntry(_prefs.isEntryHighlightEnabled()); + _entryListView.setPauseFocused(_prefs.isPauseFocusedEnabled()); + _entryListView.setTapToReveal(_prefs.isTapToRevealEnabled()); + _entryListView.setTapToRevealTime(_prefs.getTapToRevealTime()); + _entryListView.setSortCategory(_prefs.getCurrentSortCategory(), false); + _entryListView.setViewMode(_prefs.getCurrentViewMode()); + _entryListView.setIsCopyOnTapEnabled(_prefs.isCopyOnTapEnabled()); + _entryListView.setPrefGroupFilter(_prefs.getGroupFilter()); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(v -> { @@ -155,13 +141,6 @@ protected void onCreate(Bundle savedInstanceState) { _selectedEntries = new ArrayList<>(); } - @Override - protected void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean("isAuthenticating", _isAuthenticating); - outState.putBoolean("isDoingIntro", _isDoingIntro); - } - @Override protected void onDestroy() { _entryListView.setListener(null); @@ -172,18 +151,14 @@ protected void onDestroy() { protected void onPause() { Map usageMap = _entryListView.getUsageCounts(); if (usageMap != null) { - getPreferences().setUsageCount(usageMap); + _prefs.setUsageCount(usageMap); } super.onPause(); } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - _isAuthenticating = false; - _isDoingIntro = false; - if (resultCode != RESULT_OK) { return; } @@ -199,7 +174,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { onEditEntryResult(data); break; case CODE_DO_INTRO: - onDoIntroResult(); + onIntroResult(); break; case CODE_DECRYPT: onDecryptResult(); @@ -239,14 +214,14 @@ private void onPreferencesResult(Intent data) { if (data.getBooleanExtra("needsRecreate", false)) { recreate(); } else if (data.getBooleanExtra("needsRefresh", false)) { - boolean showAccountName = getPreferences().isAccountNameVisible(); - int codeGroupSize = getPreferences().getCodeGroupSize(); - boolean highlightEntry = getPreferences().isEntryHighlightEnabled(); - boolean pauseFocused = getPreferences().isPauseFocusedEnabled(); - boolean tapToReveal = getPreferences().isTapToRevealEnabled(); - int tapToRevealTime = getPreferences().getTapToRevealTime(); - ViewMode viewMode = getPreferences().getCurrentViewMode(); - boolean copyOnTap = getPreferences().isCopyOnTapEnabled(); + boolean showAccountName = _prefs.isAccountNameVisible(); + int codeGroupSize = _prefs.getCodeGroupSize(); + boolean highlightEntry = _prefs.isEntryHighlightEnabled(); + boolean pauseFocused = _prefs.isPauseFocusedEnabled(); + boolean tapToReveal = _prefs.isTapToRevealEnabled(); + int tapToRevealTime = _prefs.getTapToRevealTime(); + ViewMode viewMode = _prefs.getCurrentViewMode(); + boolean copyOnTap = _prefs.isCopyOnTapEnabled(); _entryListView.setShowAccountName(showAccountName); _entryListView.setCodeGroupSize(codeGroupSize); _entryListView.setHighlightEntry(highlightEntry); @@ -286,20 +261,20 @@ private void onScanResult(Intent data) { startEditEntryActivityForNew(CODE_ADD_ENTRY, entries.get(0)); } else { for (VaultEntry entry : entries) { - _vault.addEntry(entry); + _vaultManager.getVault().addEntry(entry); if (_loaded) { _entryListView.addEntry(entry); } } - saveVault(true); + saveAndBackupVault(); } } private void onAddEntryResult(Intent data) { if (_loaded) { UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID"); - VaultEntry entry = _vault.getEntryByUUID(entryUUID); + VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID); _entryListView.addEntry(entry, true); } } @@ -311,7 +286,7 @@ private void onEditEntryResult(Intent data) { if (data.getBooleanExtra("delete", false)) { _entryListView.removeEntry(entryUUID); } else { - VaultEntry entry = _vault.getEntryByUUID(entryUUID); + VaultEntry entry = _vaultManager.getVault().getEntryByUUID(entryUUID); _entryListView.replaceEntry(entryUUID, entry); } } @@ -352,19 +327,18 @@ private void decodeQrCodeImage(Uri inputFile) { } private void updateSortCategoryMenu() { - SortCategory category = getPreferences().getCurrentSortCategory(); + SortCategory category = _prefs.getCurrentSortCategory(); _menu.findItem(category.getMenuItem()).setChecked(true); } - private void onDoIntroResult() { - _vault = _app.getVaultManager(); + private void onIntroResult() { loadEntries(); checkTimeSyncSetting(); } private void checkTimeSyncSetting() { boolean autoTime = Settings.Global.getInt(getContentResolver(), Settings.Global.AUTO_TIME, 1) == 1; - if (!autoTime && getPreferences().isTimeSyncWarningEnabled()) { + if (!autoTime && _prefs.isTimeSyncWarningEnabled()) { Dialogs.showTimeSyncWarningDialog(this, (dialog, which) -> { Intent intent = new Intent(Settings.ACTION_DATE_SETTINGS); startActivity(intent); @@ -373,7 +347,6 @@ private void checkTimeSyncSetting() { } private void onDecryptResult() { - _vault = _app.getVaultManager(); loadEntries(); checkTimeSyncSetting(); } @@ -396,7 +369,7 @@ private void startScanImageActivity() { Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_picture)); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { fileIntent }); - AegisActivity.Helper.startExtActivityForResult(this, chooserIntent, CODE_SCAN_IMAGE); + _vaultManager.startActivityForResult(this, chooserIntent, CODE_SCAN_IMAGE); } private void startPreferencesActivity() { @@ -413,7 +386,7 @@ private void startPreferencesActivity(Class fragm private void doShortcutActions() { Intent intent = getIntent(); String action = intent.getStringExtra("action"); - if (action == null || _app.isVaultLocked()) { + if (action == null || !_vaultManager.isVaultLoaded()) { return; } @@ -427,7 +400,7 @@ private void doShortcutActions() { } private void handleDeeplink() { - if (_app.isVaultLocked()) { + if (!_vaultManager.isVaultLoaded()) { return; } @@ -453,7 +426,7 @@ private void handleDeeplink() { } private void handleSharedImage() { - if (_app.isVaultLocked()) { + if (!_vaultManager.isVaultLoaded()) { return; } @@ -473,40 +446,28 @@ private void handleSharedImage() { protected void onResume() { super.onResume(); - if (_vault == null) { - // start the intro if the vault file doesn't exist - if (!_isDoingIntro && !VaultManager.fileExists(this)) { - if (getPreferences().isIntroDone()) { - Toast.makeText(this, getString(R.string.vault_not_found), Toast.LENGTH_SHORT).show(); - } - Intent intro = new Intent(this, IntroActivity.class); - startActivityForResult(intro, CODE_DO_INTRO); - _isDoingIntro = true; - return; + if (_vaultManager.isVaultInitNeeded()) { + if (_prefs.isIntroDone()) { + Toast.makeText(this, getString(R.string.vault_not_found), Toast.LENGTH_SHORT).show(); } + Intent intro = new Intent(this, IntroActivity.class); + startActivityForResult(intro, CODE_DO_INTRO); + return; + } - // read the vault from disk - // if this fails, show the error to the user and close the app - try { - VaultFile vaultFile = _app.loadVaultFile(); - if (!vaultFile.isEncrypted()) { - _vault = _app.initVaultManager(vaultFile, null); - } - } catch (VaultManagerException e) { - e.printStackTrace(); - Dialogs.showErrorDialog(this, R.string.vault_load_error, e, (dialog1, which) -> finish()); - return; - } + if (!_vaultManager.isVaultLoaded() && !_vaultManager.isVaultFileLoaded()) { + Dialogs.showErrorDialog(this, R.string.vault_load_error, _vaultManager.getVaultFileError(), (dialog1, which) -> finish()); + return; } - if (_app.isVaultLocked()) { + if (!_vaultManager.isVaultLoaded()) { startAuthActivity(false); } else if (_loaded) { // update the list of groups in the entry list view so that the chip gets updated - _entryListView.setGroups(_vault.getGroups()); + _entryListView.setGroups(_vaultManager.getVault().getGroups()); - // update the usage counts in case they are edited outside of the entrylistview - _entryListView.setUsageCounts(getPreferences().getUsageCounts()); + // update the usage counts in case they are edited outside of the EntryListView + _entryListView.setUsageCounts(_prefs.getUsageCounts()); // refresh all codes to prevent showing old ones _entryListView.refresh(false); @@ -533,8 +494,8 @@ public void onBackPressed() { return; } - if (_app.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)) { - _app.lock(false); + if (_vaultManager.isAutoLockEnabled(Preferences.AUTO_LOCK_ON_BACK_BUTTON)) { + _vaultManager.lock(false); return; } @@ -543,11 +504,11 @@ public void onBackPressed() { private void deleteEntries(List entries) { for (VaultEntry entry: entries) { - VaultEntry oldEntry = _vault.removeEntry(entry); + VaultEntry oldEntry = _vaultManager.getVault().removeEntry(entry); _entryListView.removeEntry(oldEntry); } - saveVault(true); + saveAndBackupVault(); } @Override @@ -556,7 +517,7 @@ public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); updateLockIcon(); if (_loaded) { - _entryListView.setGroups(_vault.getGroups()); + _entryListView.setGroups(_vaultManager.getVault().getGroups()); updateSortCategoryMenu(); } @@ -565,7 +526,7 @@ public boolean onCreateOptionsMenu(Menu menu) { _searchView = (SearchView) searchViewMenuItem.getActionView(); _searchView.setQueryHint(getString(R.string.search)); - if (getPreferences().getFocusSearchEnabled() && !_isRecreated) { + if (_prefs.getFocusSearchEnabled() && !_isRecreated) { _searchView.setIconified(false); _searchView.setFocusable(true); _searchView.requestFocus(); @@ -612,7 +573,7 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } case R.id.action_lock: - _app.lock(true); + _vaultManager.lock(true); return true; default: if (item.getGroupId() == R.id.action_sort_category) { @@ -642,7 +603,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } _entryListView.setSortCategory(sortCategory, true); - getPreferences().setCurrentSortCategory(sortCategory); + _prefs.setCurrentSortCategory(sortCategory); } return super.onOptionsItemSelected(item); } @@ -655,34 +616,31 @@ private void collapseSearchView() { private void loadEntries() { if (!_loaded) { - _entryListView.setUsageCounts(getPreferences().getUsageCounts()); - _entryListView.addEntries(_vault.getEntries()); + _entryListView.setUsageCounts(_prefs.getUsageCounts()); + _entryListView.addEntries(_vaultManager.getVault().getEntries()); _entryListView.runEntriesAnimation(); _loaded = true; } } private void startAuthActivity(boolean inhibitBioPrompt) { - if (!_isAuthenticating) { - Intent intent = new Intent(this, AuthActivity.class); - intent.putExtra("inhibitBioPrompt", inhibitBioPrompt); - startActivityForResult(intent, CODE_DECRYPT); - _isAuthenticating = true; - } + Intent intent = new Intent(this, AuthActivity.class); + intent.putExtra("inhibitBioPrompt", inhibitBioPrompt); + startActivityForResult(intent, CODE_DECRYPT); } private void updateLockIcon() { // hide the lock icon if the vault is not unlocked - if (_menu != null && !_app.isVaultLocked()) { + if (_menu != null && _vaultManager.isVaultLoaded()) { MenuItem item = _menu.findItem(R.id.action_lock); - item.setVisible(_vault.isEncryptionEnabled()); + item.setVisible(_vaultManager.getVault().isEncryptionEnabled()); } } private void updateBackupErrorBar() { String error = null; - if (_app.getPreferences().isBackupsEnabled()) { - error = _app.getPreferences().getBackupsError(); + if (_prefs.isBackupsEnabled()) { + error = _prefs.getBackupsError(); } _btnBackupError.setVisibility(error == null ? View.GONE : View.VISIBLE); @@ -725,22 +683,22 @@ public void onLongEntryClick(VaultEntry entry) { _selectedEntries.add(entry); _entryListView.setActionModeState(true, entry); - _actionMode = this.startSupportActionMode(_actionModeCallbacks); + _actionMode = startSupportActionMode(_actionModeCallbacks); } @Override public void onEntryMove(VaultEntry entry1, VaultEntry entry2) { - _vault.swapEntries(entry1, entry2); + _vaultManager.getVault().swapEntries(entry1, entry2); } @Override public void onEntryDrop(VaultEntry entry) { - saveVault(false); + saveVault(); } @Override public void onEntryChange(VaultEntry entry) { - saveVault(true); + saveAndBackupVault(); } public void onEntryCopy(VaultEntry entry) { @@ -757,7 +715,7 @@ public void onScroll(int dx, int dy) { @Override public void onSaveGroupFilter(List groupFilter) { - getPreferences().setGroupFilter(groupFilter); + _prefs.setGroupFilter(groupFilter); } @Override @@ -772,7 +730,6 @@ public void onLocked(boolean userInitiated) { _entryListView.clearEntries(); _loaded = false; - if (userInitiated) { startAuthActivity(true); } else { @@ -832,8 +789,9 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { for (VaultEntry entry : _selectedEntries) { if (entry.getGroup() != null) { - if (!_vault.getGroups().contains(entry.getGroup())) { - _entryListView.setGroups(_vault.getGroups()); + TreeSet groups = _vaultManager.getVault().getGroups(); + if (!groups.contains(entry.getGroup())) { + _entryListView.setGroups(groups); break; } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java index 95d5deb5ed..8453e385f4 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/PanicResponderActivity.java @@ -5,10 +5,9 @@ import android.widget.Toast; import com.beemdevelopment.aegis.BuildConfig; -import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.crypto.pins.GuardianProjectFDroidRSA2048; -import com.beemdevelopment.aegis.vault.VaultManager; +import com.beemdevelopment.aegis.vault.VaultRepository; import info.guardianproject.GuardianProjectRSA4096; import info.guardianproject.trustedintents.TrustedIntents; @@ -19,9 +18,8 @@ public class PanicResponderActivity extends AegisActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Preferences prefs = getPreferences(); - if (!prefs.isPanicTriggerEnabled()) { + if (!_prefs.isPanicTriggerEnabled()) { Toast.makeText(this, R.string.panic_trigger_ignore_toast, Toast.LENGTH_SHORT).show(); finish(); return; @@ -39,8 +37,8 @@ protected void onCreate(Bundle savedInstanceState) { } if (intent != null && PANIC_TRIGGER_ACTION.equals(intent.getAction())) { - getApp().lock(false); - VaultManager.deleteFile(this); + VaultRepository.deleteFile(this); + _vaultManager.lock(false); finishApp(); return; } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java index 2300dcc534..60068adb95 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/PreferencesActivity.java @@ -8,8 +8,8 @@ import androidx.preference.PreferenceFragmentCompat; import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.ui.fragments.MainPreferencesFragment; -import com.beemdevelopment.aegis.ui.fragments.PreferencesFragment; +import com.beemdevelopment.aegis.ui.fragments.preferences.MainPreferencesFragment; +import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment; public class PreferencesActivity extends AegisActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { @@ -18,6 +18,9 @@ public class PreferencesActivity extends AegisActivity implements @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } setContentView(R.layout.activity_preferences); setSupportActionBar(findViewById(R.id.toolbar)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java index 3de6e87e31..0b5b463715 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/ScannerActivity.java @@ -51,6 +51,9 @@ public class ScannerActivity extends AegisActivity implements QrCodeAnalyzer.Lis @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } setContentView(R.layout.activity_scanner); setSupportActionBar(findViewById(R.id.toolbar)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java index f44d55e012..aaab4eb851 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java @@ -39,6 +39,9 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } setContentView(R.layout.activity_slots); setSupportActionBar(findViewById(R.id.toolbar)); _edited = false; diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java index cf11d1e716..21af20210d 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/TransferEntriesActivity.java @@ -17,7 +17,6 @@ import com.beemdevelopment.aegis.Theme; import com.beemdevelopment.aegis.otp.GoogleAuthInfo; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.vault.VaultManager; import com.google.zxing.BarcodeFormat; import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; @@ -34,16 +33,16 @@ public class TransferEntriesActivity extends AegisActivity { private TextView _entriesCount; private Button _nextButton; private Button _previousButton; - - private VaultManager _vault; private int _currentEntryCount = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + if (abortIfOrphan(savedInstanceState)) { + return; + } setContentView(R.layout.activity_share_entry); setSupportActionBar(findViewById(R.id.toolbar)); - _vault = getApp().getVaultManager(); _qrImage = findViewById(R.id.ivQrCode); _issuer = findViewById(R.id.tvIssuer); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/AppearancePreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/AppearancePreferencesFragment.java similarity index 87% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/AppearancePreferencesFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/AppearancePreferencesFragment.java index dde4edcf6e..095845c083 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/AppearancePreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/AppearancePreferencesFragment.java @@ -1,4 +1,4 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import android.app.Activity; import android.content.Intent; @@ -8,12 +8,11 @@ import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; -import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.Theme; import com.beemdevelopment.aegis.ViewMode; -import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.GroupManagerActivity; +import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.vault.VaultEntry; import java.util.ArrayList; @@ -27,12 +26,11 @@ public class AppearancePreferencesFragment extends PreferencesFragment { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); addPreferencesFromResource(R.xml.preferences_appearance); - Preferences prefs = getPreferences(); _groupsPreference = findPreference("pref_groups"); _groupsPreference.setOnPreferenceClickListener(preference -> { Intent intent = new Intent(getActivity(), GroupManagerActivity.class); - intent.putExtra("groups", new ArrayList<>(getVault().getGroups())); + intent.putExtra("groups", new ArrayList<>(_vaultManager.getVault().getGroups())); startActivityForResult(intent, CODE_GROUPS); return true; }); @@ -42,23 +40,23 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) .setTitle(R.string.preference_reset_usage_count) .setMessage(R.string.preference_reset_usage_count_dialog) - .setPositiveButton(android.R.string.yes, (dialog, which) -> getPreferences().clearUsageCount()) + .setPositiveButton(android.R.string.yes, (dialog, which) -> _prefs.clearUsageCount()) .setNegativeButton(android.R.string.no, null) .create()); return true; }); - int currentTheme = prefs.getCurrentTheme().ordinal(); + int currentTheme = _prefs.getCurrentTheme().ordinal(); Preference darkModePreference = findPreference("pref_dark_mode"); darkModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.theme_titles)[currentTheme])); darkModePreference.setOnPreferenceClickListener(preference -> { - int currentTheme1 = prefs.getCurrentTheme().ordinal(); + int currentTheme1 = _prefs.getCurrentTheme().ordinal(); Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) .setTitle(R.string.choose_theme) .setSingleChoiceItems(R.array.theme_titles, currentTheme1, (dialog, which) -> { int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - prefs.setCurrentTheme(Theme.fromInteger(i)); + _prefs.setCurrentTheme(Theme.fromInteger(i)); dialog.dismiss(); @@ -83,17 +81,17 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { langPreference.setVisible(false); } - int currentViewMode = prefs.getCurrentViewMode().ordinal(); + int currentViewMode = _prefs.getCurrentViewMode().ordinal(); Preference viewModePreference = findPreference("pref_view_mode"); viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[currentViewMode])); viewModePreference.setOnPreferenceClickListener(preference -> { - int currentViewMode1 = prefs.getCurrentViewMode().ordinal(); + int currentViewMode1 = _prefs.getCurrentViewMode().ordinal(); Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) .setTitle(R.string.choose_view_mode) .setSingleChoiceItems(R.array.view_mode_titles, currentViewMode1, (dialog, which) -> { int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); - prefs.setCurrentViewMode(ViewMode.fromInteger(i)); + _prefs.setCurrentViewMode(ViewMode.fromInteger(i)); viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getResources().getStringArray(R.array.view_mode_titles)[i])); getResult().putExtra("needsRefresh", true); dialog.dismiss(); @@ -133,12 +131,12 @@ private void onGroupManagerResult(int resultCode, Intent data) { HashSet groups = new HashSet<>(data.getStringArrayListExtra("groups")); - for (VaultEntry entry : getVault().getEntries()) { + for (VaultEntry entry : _vaultManager.getVault().getEntries()) { if (!groups.contains(entry.getGroup())) { entry.setGroup(null); } } - saveVault(); + saveAndBackupVault(); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/BackupsPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BackupsPreferencesFragment.java similarity index 77% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/BackupsPreferencesFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BackupsPreferencesFragment.java index 5504092ae8..46c60c57f5 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/BackupsPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BackupsPreferencesFragment.java @@ -1,4 +1,4 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import android.app.Activity; import android.content.Intent; @@ -9,11 +9,9 @@ import androidx.preference.Preference; import androidx.preference.SwitchPreferenceCompat; -import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.ui.AegisActivity; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; public class BackupsPreferencesFragment extends PreferencesFragment { private SwitchPreferenceCompat _androidBackupsPreference; @@ -32,14 +30,13 @@ public void onResume() { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); addPreferencesFromResource(R.xml.preferences_backups); - Preferences prefs = getPreferences(); _backupsPreference = findPreference("pref_backups"); _backupsPreference.setOnPreferenceChangeListener((preference, newValue) -> { if ((boolean) newValue) { selectBackupsLocation(); } else { - prefs.setIsBackupsEnabled(false); + _prefs.setIsBackupsEnabled(false); updateBackupPreference(); } @@ -48,13 +45,13 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { _androidBackupsPreference = findPreference("pref_android_backups"); _androidBackupsPreference.setOnPreferenceChangeListener((preference, newValue) -> { - prefs.setIsAndroidBackupsEnabled((boolean) newValue); + _prefs.setIsAndroidBackupsEnabled((boolean) newValue); updateBackupPreference(); - getVault().androidBackupDataChanged(); + _vaultManager.scheduleAndroidBackup(); return false; }); - Uri backupLocation = prefs.getBackupsLocation(); + Uri backupLocation = _prefs.getBackupsLocation(); _backupsLocationPreference = findPreference("pref_backups_location"); if (backupLocation != null) { _backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(backupLocation.toString()))); @@ -66,11 +63,11 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { _backupsTriggerPreference = findPreference("pref_backups_trigger"); _backupsTriggerPreference.setOnPreferenceClickListener(preference -> { - if (prefs.isBackupsEnabled()) { + if (_prefs.isBackupsEnabled()) { try { - getVault().backup(); + _vaultManager.scheduleBackup(); Toast.makeText(getActivity(), R.string.backup_successful, Toast.LENGTH_LONG).show(); - } catch (VaultManagerException e) { + } catch (VaultRepositoryException e) { e.printStackTrace(); Dialogs.showErrorDialog(getContext(), R.string.backup_error, e); } @@ -79,12 +76,12 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { }); _backupsVersionsPreference = findPreference("pref_backups_versions"); - _backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, prefs.getBackupsVersionCount(), prefs.getBackupsVersionCount())); + _backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, _prefs.getBackupsVersionCount(), _prefs.getBackupsVersionCount())); _backupsVersionsPreference.setOnPreferenceClickListener(preference -> { Dialogs.showBackupVersionsPickerDialog(getActivity(), number -> { number = number * 5 + 5; - prefs.setBackupsVersionCount(number); - _backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, prefs.getBackupsVersionCount(), prefs.getBackupsVersionCount())); + _prefs.setBackupsVersionCount(number); + _backupsVersionsPreference.setSummary(getResources().getQuantityString(R.plurals.pref_backups_versions_summary, _prefs.getBackupsVersionCount(), _prefs.getBackupsVersionCount())); }); return false; }); @@ -106,18 +103,17 @@ private void onSelectBackupsLocationResult(int resultCode, Intent data) { int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION; getContext().getContentResolver().takePersistableUriPermission(data.getData(), flags); - Preferences prefs = getPreferences(); - prefs.setBackupsLocation(uri); - prefs.setIsBackupsEnabled(true); - prefs.setBackupsError(null); + _prefs.setBackupsLocation(uri); + _prefs.setIsBackupsEnabled(true); + _prefs.setBackupsError(null); _backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(uri.toString()))); updateBackupPreference(); } private void updateBackupPreference() { - boolean encrypted = getVault().isEncryptionEnabled(); - boolean androidBackupEnabled = getPreferences().isAndroidBackupsEnabled() && encrypted; - boolean backupEnabled = getPreferences().isBackupsEnabled() && encrypted; + boolean encrypted = _vaultManager.getVault().isEncryptionEnabled(); + boolean androidBackupEnabled = _prefs.isAndroidBackupsEnabled() && encrypted; + boolean backupEnabled = _prefs.isBackupsEnabled() && encrypted; _androidBackupsPreference.setChecked(androidBackupEnabled); _androidBackupsPreference.setEnabled(encrypted); _backupsPreference.setChecked(backupEnabled); @@ -134,6 +130,6 @@ private void selectBackupsLocation() { | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - AegisActivity.Helper.startExtActivityForResult(this, intent, CODE_BACKUPS); + _vaultManager.startActivityForResult(this, intent, CODE_BACKUPS); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/BehaviorPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BehaviorPreferencesFragment.java similarity index 89% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/BehaviorPreferencesFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BehaviorPreferencesFragment.java index e48f8e2f5b..9f68b033b7 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/BehaviorPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/BehaviorPreferencesFragment.java @@ -1,15 +1,12 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import android.os.Bundle; import androidx.preference.Preference; -import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; public class BehaviorPreferencesFragment extends PreferencesFragment { - - private Preferences _prefs; private Preference _entryPausePreference; @Override @@ -17,8 +14,6 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); addPreferencesFromResource(R.xml.preferences_behavior); - _prefs = getPreferences(); - Preference copyOnTapPreference = findPreference("pref_copy_on_tap"); copyOnTapPreference.setOnPreferenceChangeListener((preference, newValue) -> { getResult().putExtra("needsRefresh", true); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/IconPacksManagerFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/IconPacksManagerFragment.java similarity index 94% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/IconPacksManagerFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/IconPacksManagerFragment.java index ff6ed57e87..13e52379da 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/IconPacksManagerFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/IconPacksManagerFragment.java @@ -1,4 +1,4 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import android.app.Activity; import android.content.Intent; @@ -15,27 +15,35 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.beemdevelopment.aegis.AegisApplication; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.helpers.FabScrollHelper; import com.beemdevelopment.aegis.icons.IconPack; import com.beemdevelopment.aegis.icons.IconPackException; import com.beemdevelopment.aegis.icons.IconPackExistsException; import com.beemdevelopment.aegis.icons.IconPackManager; -import com.beemdevelopment.aegis.ui.AegisActivity; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.tasks.ImportIconPackTask; import com.beemdevelopment.aegis.ui.views.IconPackAdapter; +import com.beemdevelopment.aegis.vault.VaultManager; import com.google.android.material.floatingactionbutton.FloatingActionButton; +import javax.inject.Inject; + +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint public class IconPacksManagerFragment extends Fragment implements IconPackAdapter.Listener { private static final int CODE_IMPORT = 0; - private IconPackAdapter _adapter; - private IconPackManager _iconPackManager; + @Inject + IconPackManager _iconPackManager; + + @Inject + VaultManager _vaultManager; private View _iconPacksView; private RecyclerView _iconPacksRecyclerView; + private IconPackAdapter _adapter; private LinearLayout _noIconPacksView; private FabScrollHelper _fabScrollHelper; @@ -45,8 +53,6 @@ public IconPacksManagerFragment() { @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - _iconPackManager = ((AegisApplication) getContext().getApplicationContext()).getIconPackManager(); - FloatingActionButton fab = view.findViewById(R.id.fab); fab.setOnClickListener(v -> startImportIconPack()); _fabScrollHelper = new FabScrollHelper(fab); @@ -145,7 +151,7 @@ private boolean removeIconPack(IconPack pack) { private void startImportIconPack() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("application/zip"); - AegisActivity.Helper.startExtActivityForResult(this, intent, CODE_IMPORT); + _vaultManager.startActivityForResult(this, intent, CODE_IMPORT); } private void updateEmptyState() { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/ImportExportPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/ImportExportPreferencesFragment.java similarity index 90% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/ImportExportPreferencesFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/ImportExportPreferencesFragment.java index 5660b30395..8e488d4e85 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/ImportExportPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/ImportExportPreferencesFragment.java @@ -1,4 +1,4 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import android.app.Activity; import android.content.Intent; @@ -22,15 +22,14 @@ import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.helpers.DropdownHelper; import com.beemdevelopment.aegis.importers.DatabaseImporter; -import com.beemdevelopment.aegis.ui.AegisActivity; import com.beemdevelopment.aegis.ui.ImportEntriesActivity; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.tasks.ExportTask; import com.beemdevelopment.aegis.ui.tasks.ImportFileTask; import com.beemdevelopment.aegis.vault.VaultBackupManager; import com.beemdevelopment.aegis.vault.VaultFileCredentials; -import com.beemdevelopment.aegis.vault.VaultManager; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.vault.VaultRepository; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; import com.beemdevelopment.aegis.vault.slots.Slot; import com.beemdevelopment.aegis.vault.slots.SlotException; @@ -61,7 +60,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); - AegisActivity.Helper.startExtActivityForResult(this, intent, CODE_IMPORT_SELECT); + _vaultManager.startActivityForResult(this, intent, CODE_IMPORT_SELECT); }); return true; }); @@ -184,7 +183,7 @@ private void startExport() { .addCategory(Intent.CATEGORY_OPENABLE) .setType(getExportMimeType(requestCode)) .putExtra(Intent.EXTRA_TITLE, fileInfo.toString()); - AegisActivity.Helper.startExtActivityForResult(this, intent, requestCode); + _vaultManager.startActivityForResult(this, intent, requestCode); }); btnNeutral.setOnClickListener(v -> { @@ -209,7 +208,7 @@ private void startExport() { startExportVault(requestCode, cb -> { try (OutputStream stream = new FileOutputStream(file)) { cb.exportVault(stream); - } catch (IOException | VaultManagerException e) { + } catch (IOException | VaultRepositoryException e) { e.printStackTrace(); Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e); return; @@ -221,7 +220,7 @@ private void startExport() { .setType(getExportMimeType(requestCode)) .putExtra(Intent.EXTRA_STREAM, uri); Intent chooser = Intent.createChooser(intent, getString(R.string.pref_export_summary)); - AegisActivity.Helper.startExtActivity(this, chooser); + _vaultManager.startActivity(this, chooser); }); }); }); @@ -239,11 +238,11 @@ private static int getExportRequestCode(int spinnerPos, boolean encrypt) { private static VaultBackupManager.FileInfo getExportFileInfo(int spinnerPos, boolean encrypt) { if (spinnerPos == 0) { - String filename = encrypt ? VaultManager.FILENAME_PREFIX_EXPORT : VaultManager.FILENAME_PREFIX_EXPORT_PLAIN; + String filename = encrypt ? VaultRepository.FILENAME_PREFIX_EXPORT : VaultRepository.FILENAME_PREFIX_EXPORT_PLAIN; return new VaultBackupManager.FileInfo(filename); } - return new VaultBackupManager.FileInfo(VaultManager.FILENAME_PREFIX_EXPORT_URI, "txt"); + return new VaultBackupManager.FileInfo(VaultRepository.FILENAME_PREFIX_EXPORT_URI, "txt"); } private static String getExportMimeType(int requestCode) { @@ -262,8 +261,8 @@ private File getExportCacheDir() throws IOException { private void startExportVault(int requestCode, StartExportCallback cb) { switch (requestCode) { case CODE_EXPORT: - if (getVault().isEncryptionEnabled()) { - cb.exportVault(stream -> getVault().export(stream)); + if (_vaultManager.getVault().isEncryptionEnabled()) { + cb.exportVault(stream -> _vaultManager.getVault().export(stream)); } else { Dialogs.showSetPasswordDialog(getActivity(), new Dialogs.SlotListener() { @Override @@ -278,7 +277,7 @@ public void onSlotResult(Slot slot, Cipher cipher) { return; } - cb.exportVault(stream -> getVault().export(stream, creds)); + cb.exportVault(stream -> _vaultManager.getVault().export(stream, creds)); } @Override @@ -289,10 +288,10 @@ public void onException(Exception e) { } break; case CODE_EXPORT_PLAIN: - cb.exportVault((stream) -> getVault().export(stream, null)); + cb.exportVault((stream) -> _vaultManager.getVault().export(stream, null)); break; case CODE_EXPORT_GOOGLE_URI: - cb.exportVault((stream) -> getVault().exportGoogleUris(stream)); + cb.exportVault((stream) -> _vaultManager.getVault().exportGoogleUris(stream)); break; } } @@ -307,12 +306,12 @@ private void onExportResult(int requestCode, int resultCode, Intent data) { File file; OutputStream outStream = null; try { - file = File.createTempFile(VaultManager.FILENAME_PREFIX_EXPORT + "-", ".json", getExportCacheDir()); + file = File.createTempFile(VaultRepository.FILENAME_PREFIX_EXPORT + "-", ".json", getExportCacheDir()); outStream = new FileOutputStream(file); cb.exportVault(outStream); new ExportTask(getContext(), new ExportResultListener()).execute(getLifecycle(), new ExportTask.Params(file, uri)); - } catch (VaultManagerException | IOException e) { + } catch (VaultRepositoryException | IOException e) { e.printStackTrace(); Dialogs.showErrorDialog(getContext(), R.string.exporting_vault_error, e); } finally { @@ -350,7 +349,7 @@ public void onTaskFinished(Exception e) { } private interface FinishExportCallback { - void exportVault(OutputStream stream) throws IOException, VaultManagerException; + void exportVault(OutputStream stream) throws IOException, VaultRepositoryException; } private interface StartExportCallback { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/MainPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/MainPreferencesFragment.java similarity index 85% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/MainPreferencesFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/MainPreferencesFragment.java index be183f3b1d..3172fd8579 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/MainPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/MainPreferencesFragment.java @@ -1,4 +1,4 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import android.os.Bundle; diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/PreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/PreferencesFragment.java similarity index 70% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/PreferencesFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/PreferencesFragment.java index 0d89348b5e..0bf036f998 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/PreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/PreferencesFragment.java @@ -1,4 +1,4 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import android.app.Activity; import android.content.Intent; @@ -7,13 +7,17 @@ import androidx.annotation.CallSuper; import androidx.preference.PreferenceFragmentCompat; -import com.beemdevelopment.aegis.AegisApplication; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.vault.VaultManager; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; +import javax.inject.Inject; + +import dagger.hilt.android.AndroidEntryPoint; + +@AndroidEntryPoint public abstract class PreferencesFragment extends PreferenceFragmentCompat { // activity request codes public static final int CODE_IMPORT_SELECT = 0; @@ -25,18 +29,17 @@ public abstract class PreferencesFragment extends PreferenceFragmentCompat { public static final int CODE_EXPORT_GOOGLE_URI = 7; public static final int CODE_BACKUPS = 8; - private AegisApplication _app; private Intent _result; - private Preferences _prefs; - private VaultManager _vault; + + @Inject + Preferences _prefs; + + @Inject + VaultManager _vaultManager; @Override @CallSuper public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - _app = (AegisApplication) getActivity().getApplication(); - _prefs = _app.getPreferences(); - _vault = _app.getVaultManager(); - setResult(new Intent()); } @@ -62,22 +65,10 @@ public void setResult(Intent result) { getActivity().setResult(Activity.RESULT_OK, _result); } - protected AegisApplication getApp() { - return _app; - } - - protected Preferences getPreferences() { - return _prefs; - } - - protected VaultManager getVault() { - return _vault; - } - - protected boolean saveVault() { + protected boolean saveAndBackupVault() { try { - _vault.save(true); - } catch (VaultManagerException e) { + _vaultManager.saveAndBackup(); + } catch (VaultRepositoryException e) { e.printStackTrace(); Dialogs.showErrorDialog(getContext(), R.string.saving_error, e); return false; diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/SecurityPreferencesFragment.java similarity index 86% rename from app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/SecurityPreferencesFragment.java index 523a1792fc..6afffb2173 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/SecurityPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/SecurityPreferencesFragment.java @@ -1,4 +1,4 @@ -package com.beemdevelopment.aegis.ui.fragments; +package com.beemdevelopment.aegis.ui.fragments.preferences; import static android.text.TextUtils.isDigitsOnly; @@ -23,13 +23,12 @@ import com.beemdevelopment.aegis.crypto.KeyStoreHandleException; import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer; import com.beemdevelopment.aegis.helpers.BiometricsHelper; -import com.beemdevelopment.aegis.services.NotificationService; import com.beemdevelopment.aegis.ui.SlotManagerActivity; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.preferences.SwitchPreference; import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; import com.beemdevelopment.aegis.vault.VaultFileCredentials; -import com.beemdevelopment.aegis.vault.VaultManagerException; +import com.beemdevelopment.aegis.vault.VaultRepositoryException; import com.beemdevelopment.aegis.vault.slots.BiometricSlot; import com.beemdevelopment.aegis.vault.slots.PasswordSlot; import com.beemdevelopment.aegis.vault.slots.Slot; @@ -80,10 +79,10 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { }); Preference tapToRevealTimePreference = findPreference("pref_tap_to_reveal_time"); - tapToRevealTimePreference.setSummary(getPreferences().getTapToRevealTime() + " seconds"); + tapToRevealTimePreference.setSummary(_prefs.getTapToRevealTime() + " seconds"); tapToRevealTimePreference.setOnPreferenceClickListener(preference -> { Dialogs.showNumberPickerDialog(getActivity(), number -> { - getPreferences().setTapToRevealTime(number); + _prefs.setTapToRevealTime(number); tapToRevealTimePreference.setSummary(number + " seconds"); getResult().putExtra("needsRefresh", true); }); @@ -92,7 +91,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { _encryptionPreference = findPreference("pref_encryption"); _encryptionPreference.setOnPreferenceChangeListener((preference, newValue) -> { - if (!getVault().isEncryptionEnabled()) { + if (!_vaultManager.getVault().isEncryptionEnabled()) { Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener()); } else { Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity()) @@ -100,24 +99,15 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { .setMessage(getText(R.string.disable_encryption_description)) .setPositiveButton(android.R.string.yes, (dialog, which) -> { try { - getVault().disableEncryption(); - } catch (VaultManagerException e) { + _vaultManager.disableEncryption(); + } catch (VaultRepositoryException e) { e.printStackTrace(); Dialogs.showErrorDialog(getContext(), R.string.disable_encryption_error, e); return; } - // clear the KeyStore - try { - KeyStoreHandle handle = new KeyStoreHandle(); - handle.clear(); - } catch (KeyStoreHandleException e) { - e.printStackTrace(); - } - - getActivity().stopService(new Intent(getActivity(), NotificationService.class)); - getPreferences().setIsBackupsEnabled(false); - getPreferences().setIsAndroidBackupsEnabled(false); + _prefs.setIsBackupsEnabled(false); + _prefs.setIsAndroidBackupsEnabled(false); updateEncryptionPreferences(); }) .setNegativeButton(android.R.string.no, null) @@ -129,7 +119,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { _biometricsPreference = findPreference("pref_biometrics"); _biometricsPreference.setOnPreferenceChangeListener((preference, newValue) -> { - VaultFileCredentials creds = getVault().getCredentials(); + VaultFileCredentials creds = _vaultManager.getVault().getCredentials(); SlotList slots = creds.getSlots(); if (!slots.has(BiometricSlot.class)) { @@ -145,7 +135,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { // remove the biometric slot BiometricSlot slot = slots.find(BiometricSlot.class); slots.remove(slot); - getVault().setCredentials(creds); + _vaultManager.getVault().setCredentials(creds); // remove the KeyStore key try { @@ -155,7 +145,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { e.printStackTrace(); } - saveVault(); + saveAndBackupVault(); updateEncryptionPreferences(); } @@ -171,7 +161,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { _slotsPreference = findPreference("pref_slots"); _slotsPreference.setOnPreferenceClickListener(preference -> { Intent intent = new Intent(getActivity(), SlotManagerActivity.class); - intent.putExtra("creds", getVault().getCredentials()); + intent.putExtra("creds", _vaultManager.getVault().getCredentials()); startActivityForResult(intent, CODE_SLOTS); return true; }); @@ -184,7 +174,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { Dialogs.showPasswordInputDialog(getActivity(), R.string.set_password_confirm, R.string.pin_keyboard_description, password -> { if (isDigitsOnly(new String(password))) { - List slots = getVault().getCredentials().getSlots().findAll(PasswordSlot.class); + List slots = _vaultManager.getVault().getCredentials().getSlots().findAll(PasswordSlot.class); PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password); PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(getActivity(), new PasswordConfirmationListener()); task.execute(getLifecycle(), params); @@ -210,7 +200,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { final String[] textItems = getResources().getStringArray(R.array.pref_auto_lock_types); final boolean[] checkedItems = new boolean[items.length]; for (int i = 0; i < items.length; i++) { - checkedItems[i] = getPreferences().isAutoLockTypeEnabled(items[i]); + checkedItems[i] = _prefs.isAutoLockTypeEnabled(items[i]); } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) @@ -224,7 +214,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { } } - getPreferences().setAutoLockMask(autoLock); + _prefs.setAutoLockMask(autoLock); _autoLockPreference.setSummary(getAutoLockSummary()); }) .setNegativeButton(android.R.string.cancel, null); @@ -236,7 +226,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { _passwordReminderPreference = findPreference("pref_password_reminder_freq"); _passwordReminderPreference.setSummary(getPasswordReminderSummary()); _passwordReminderPreference.setOnPreferenceClickListener((preference) -> { - final PassReminderFreq currFreq = getPreferences().getPasswordReminderFrequency(); + final PassReminderFreq currFreq = _prefs.getPasswordReminderFrequency(); final PassReminderFreq[] items = PassReminderFreq.values(); final String[] textItems = Arrays.stream(items) .map(f -> getString(f.getStringRes())) @@ -247,7 +237,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { .setSingleChoiceItems(textItems, currFreq.ordinal(), (dialog, which) -> { int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition(); PassReminderFreq freq = PassReminderFreq.fromInteger(i); - getPreferences().setPasswordReminderFrequency(freq); + _prefs.setPasswordReminderFrequency(freq); _passwordReminderPreference.setSummary(getPasswordReminderSummary()); dialog.dismiss(); }) @@ -270,13 +260,13 @@ private void onSlotManagerResult(int resultCode, Intent data) { } VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds"); - getVault().setCredentials(creds); - saveVault(); + _vaultManager.getVault().setCredentials(creds); + saveAndBackupVault(); updateEncryptionPreferences(); } private void updateEncryptionPreferences() { - boolean encrypted = getVault().isEncryptionEnabled(); + boolean encrypted = _vaultManager.getVault().isEncryptionEnabled(); _encryptionPreference.setChecked(encrypted, true); _setPasswordPreference.setVisible(encrypted); _biometricsPreference.setVisible(encrypted); @@ -285,7 +275,7 @@ private void updateEncryptionPreferences() { _pinKeyboardPreference.setVisible(encrypted); if (encrypted) { - SlotList slots = getVault().getCredentials().getSlots(); + SlotList slots = _vaultManager.getVault().getCredentials().getSlots(); boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1; boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1; boolean showSlots = BuildConfig.DEBUG || multiPassword || multiBio; @@ -305,7 +295,7 @@ private void updateEncryptionPreferences() { } private String getPasswordReminderSummary() { - PassReminderFreq freq = getPreferences().getPasswordReminderFrequency(); + PassReminderFreq freq = _prefs.getPasswordReminderFrequency(); if (freq == PassReminderFreq.NEVER) { return getString(R.string.pref_password_reminder_summary_disabled); } @@ -320,7 +310,7 @@ private String getAutoLockSummary() { StringBuilder builder = new StringBuilder(); for (int i = 0; i < settings.length; i++) { - if (getPreferences().isAutoLockTypeEnabled(settings[i])) { + if (_prefs.isAutoLockTypeEnabled(settings[i])) { if (builder.length() != 0) { builder.append(", "); } @@ -339,7 +329,7 @@ private String getAutoLockSummary() { private class SetPasswordListener implements Dialogs.SlotListener { @Override public void onSlotResult(Slot slot, Cipher cipher) { - VaultFileCredentials creds = getVault().getCredentials(); + VaultFileCredentials creds = _vaultManager.getVault().getCredentials(); SlotList slots = creds.getSlots(); try { @@ -359,10 +349,10 @@ public void onSlotResult(Slot slot, Cipher cipher) { return; } - getVault().setCredentials(creds); - saveVault(); + _vaultManager.getVault().setCredentials(creds); + saveAndBackupVault(); - if (getPreferences().isPinKeyboardEnabled()) { + if (_prefs.isPinKeyboardEnabled()) { _pinKeyboardPreference.setChecked(false); Toast.makeText(getContext(), R.string.pin_keyboard_disabled, Toast.LENGTH_SHORT).show(); } @@ -379,7 +369,7 @@ public void onException(Exception e) { private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener { @Override public void onInitializeSlot(BiometricSlot slot, Cipher cipher) { - VaultFileCredentials creds = getVault().getCredentials(); + VaultFileCredentials creds = _vaultManager.getVault().getCredentials(); try { slot.setKey(creds.getKey(), cipher); } catch (SlotException e) { @@ -388,9 +378,9 @@ public void onInitializeSlot(BiometricSlot slot, Cipher cipher) { return; } creds.getSlots().add(slot); - getVault().setCredentials(creds); + _vaultManager.getVault().setCredentials(creds); - saveVault(); + saveAndBackupVault(); updateEncryptionPreferences(); } @@ -410,13 +400,12 @@ public void onSlotResult(Slot slot, Cipher cipher) { try { slot.setKey(creds.getKey(), cipher); creds.getSlots().add(slot); - getVault().enableEncryption(creds); - } catch (VaultManagerException | SlotException e) { + _vaultManager.enableEncryption(creds); + } catch (VaultRepositoryException | SlotException e) { onException(e); return; } - getActivity().startService(new Intent(getActivity(), NotificationService.class)); _pinKeyboardPreference.setChecked(false); updateEncryptionPreferences(); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java index 7179f647f1..c1a631f61c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java @@ -2,7 +2,6 @@ import android.annotation.SuppressLint; import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -412,7 +411,7 @@ private void initializeGroupChip() { chipGroup.removeAllViews(); for (String group : _groups) { - Chip chip = (Chip) this.getLayoutInflater().inflate(R.layout.chip_material, null, false); + Chip chip = (Chip) getLayoutInflater().inflate(R.layout.chip_material, null, false); chip.setText(group); chip.setCheckable(true); chip.setChecked(_groupFilter != null && _groupFilter.contains(group)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultBackupManager.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultBackupManager.java index bf020f7d3a..4a7ed0e7b6 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultBackupManager.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultBackupManager.java @@ -48,24 +48,19 @@ public VaultBackupManager(Context context) { _executor = Executors.newSingleThreadExecutor(); } - public void destroy() { - Log.i(TAG, "Shutting down backup manager thread"); - _executor.shutdown(); - } - public void scheduleBackup(File tempFile, Uri dirUri, int versionsToKeep) { _executor.execute(() -> { try { createBackup(tempFile, dirUri, versionsToKeep); _prefs.setBackupsError(null); - } catch (VaultManagerException e) { + } catch (VaultRepositoryException e) { e.printStackTrace(); _prefs.setBackupsError(e); } }); } - private void createBackup(File tempFile, Uri dirUri, int versionsToKeep) throws VaultManagerException { + private void createBackup(File tempFile, Uri dirUri, int versionsToKeep) throws VaultRepositoryException { FileInfo fileInfo = new FileInfo(FILENAME_PREFIX); DocumentFile dir = DocumentFile.fromTreeUri(_context, dirUri); @@ -73,28 +68,28 @@ private void createBackup(File tempFile, Uri dirUri, int versionsToKeep) throws Log.i(TAG, String.format("Creating backup at %s: %s", Uri.decode(dir.getUri().toString()), fileInfo.toString())); if (!hasPermissionsAt(dirUri)) { - throw new VaultManagerException("No persisted URI permissions"); + throw new VaultRepositoryException("No persisted URI permissions"); } // If we create a file with a name that already exists, SAF will append a number // to the filename and write to that instead. We can't overwrite existing files, so // just avoid that altogether by checking beforehand. if (dir.findFile(fileInfo.toString()) != null) { - throw new VaultManagerException("Backup file already exists"); + throw new VaultRepositoryException("Backup file already exists"); } DocumentFile file = dir.createFile("application/json", fileInfo.toString()); if (file == null) { - throw new VaultManagerException("createFile returned null"); + throw new VaultRepositoryException("createFile returned null"); } try (FileInputStream inStream = new FileInputStream(tempFile); OutputStream outStream = _context.getContentResolver().openOutputStream(file.getUri())) { IOUtils.copy(inStream, outStream); } catch (IOException e) { - throw new VaultManagerException(e); + throw new VaultRepositoryException(e); } - } catch (VaultManagerException e) { + } catch (VaultRepositoryException e) { Log.e(TAG, String.format("Unable to create backup: %s", e.toString())); throw e; } finally { @@ -252,7 +247,7 @@ public Date parse(@NonNull String text, ParsePosition pos) { int posIndex = pos.getIndex(); Date d = super.parse(text, pos); if (!isLenient() && d != null) { - String format = this.format(d); + String format = format(d); if (posIndex + format.length() != text.length() || !text.endsWith(format)) { d = null; // Not exact match diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java index 1b68a4327d..7d92bda39b 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java @@ -1,207 +1,187 @@ package com.beemdevelopment.aegis.vault; +import android.app.Activity; import android.app.backup.BackupManager; +import android.content.ActivityNotFoundException; import android.content.Context; +import android.content.Intent; -import androidx.core.util.AtomicFile; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; import com.beemdevelopment.aegis.Preferences; -import com.beemdevelopment.aegis.otp.GoogleAuthInfo; -import com.beemdevelopment.aegis.util.IOUtils; +import com.beemdevelopment.aegis.R; +import com.beemdevelopment.aegis.crypto.KeyStoreHandle; +import com.beemdevelopment.aegis.crypto.KeyStoreHandleException; +import com.beemdevelopment.aegis.services.NotificationService; +import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import org.json.JSONObject; - -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileOutputStream; +import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.text.Collator; -import java.util.Collection; -import java.util.TreeSet; -import java.util.UUID; +import java.util.ArrayList; +import java.util.List; public class VaultManager { - public static final String FILENAME = "aegis.json"; - public static final String FILENAME_PREFIX_EXPORT = "aegis-export"; - public static final String FILENAME_PREFIX_EXPORT_PLAIN = "aegis-export-plain"; - public static final String FILENAME_PREFIX_EXPORT_URI = "aegis-export-uri"; + private final Context _context; + private final Preferences _prefs; + + private VaultFile _vaultFile; + private VaultRepositoryException _vaultFileError; + private VaultRepository _repo; - private Vault _vault; - private VaultFileCredentials _creds; + private final VaultBackupManager _backups; + private final BackupManager _androidBackups; - private Context _context; - private Preferences _prefs; - private VaultBackupManager _backups; - private BackupManager _androidBackups; + private final List _lockListeners; + private boolean _blockAutoLock; - public VaultManager(Context context, Vault vault, VaultFileCredentials creds) { + public VaultManager(@NonNull Context context) { _context = context; - _prefs = new Preferences(context); - _backups = new VaultBackupManager(context); + _prefs = new Preferences(_context); + _backups = new VaultBackupManager(_context); _androidBackups = new BackupManager(context); - _vault = vault; - _creds = creds; - } - - public VaultManager(Context context, Vault vault) { - this(context, vault, null); + _lockListeners = new ArrayList<>(); + loadVaultFile(); } - public static AtomicFile getAtomicFile(Context context) { - return new AtomicFile(new File(context.getFilesDir(), FILENAME)); - } - - public static boolean fileExists(Context context) { - File file = getAtomicFile(context).getBaseFile(); - return file.exists() && file.isFile(); - } + private void loadVaultFile() { + try { + _vaultFile = VaultRepository.readVaultFile(_context); + } catch (VaultRepositoryException e) { + e.printStackTrace(); + if (!(e.getCause() instanceof FileNotFoundException)) { + _vaultFileError = e; + } + } - public static void deleteFile(Context context) { - getAtomicFile(context).delete(); + if (_vaultFile != null && !_vaultFile.isEncrypted()) { + try { + load(_vaultFile, null); + } catch (VaultRepositoryException e) { + e.printStackTrace(); + _vaultFile = null; + _vaultFileError = e; + } + } } - public static VaultFile readVaultFile(Context context) throws VaultManagerException { - AtomicFile file = getAtomicFile(context); - - try { - byte[] fileBytes = file.readFully(); - return VaultFile.fromBytes(fileBytes); - } catch (IOException | VaultFileException e) { - throw new VaultManagerException(e); + /** + * Initializes the vault repository with a new empty vault and the given creds. It can + * only be called if isVaultLoaded() returns false. + * + * Calling this method removes the manager's internal reference to the raw vault file (if it had one). + */ + @NonNull + public VaultRepository init(@Nullable VaultFileCredentials creds) throws VaultRepositoryException { + if (isVaultLoaded()) { + throw new IllegalStateException("Vault manager is already initialized"); } - } - public static void writeToFile(Context context, InputStream inStream) throws IOException { - AtomicFile file = VaultManager.getAtomicFile(context); + _vaultFile = null; + _vaultFileError = null; + _repo = new VaultRepository(_context, new Vault(), creds); + save(); - FileOutputStream outStream = null; - try { - outStream = file.startWrite(); - IOUtils.copy(inStream, outStream); - file.finishWrite(outStream); - } catch (IOException e) { - if (outStream != null) { - file.failWrite(outStream); - } - throw e; + if (getVault().isEncryptionEnabled()) { + startNotificationService(); } + + return getVault(); } - public static VaultManager init(Context context, VaultFile file, VaultFileCredentials creds) throws VaultManagerException { - if (file.isEncrypted() && creds == null) { - throw new IllegalArgumentException("The VaultFile is encrypted but the given VaultFileCredentials is null"); + /** + * Initializes the vault repository by decrypting the given vaultFile with the given + * creds. It can only be called if isVaultLoaded() returns false. + * + * Calling this method removes the manager's internal reference to the raw vault file (if it had one). + */ + @NonNull + public VaultRepository load(@NonNull VaultFile vaultFile, @Nullable VaultFileCredentials creds) throws VaultRepositoryException { + if (isVaultLoaded()) { + throw new IllegalStateException("Vault manager is already initialized"); } - Vault vault; - try { - JSONObject obj; - if (!file.isEncrypted()) { - obj = file.getContent(); - } else { - obj = file.getContent(creds); - } + _vaultFile = null; + _vaultFileError = null; + _repo = VaultRepository.fromFile(_context, vaultFile, creds); - vault = Vault.fromJson(obj); - } catch (VaultException | VaultFileException e) { - throw new VaultManagerException(e); + if (getVault().isEncryptionEnabled()) { + startNotificationService(); } - return new VaultManager(context, vault, creds); + return getVault(); } - public static void save(Context context, VaultFile vaultFile) throws VaultManagerException { - try { - byte[] bytes = vaultFile.toBytes(); - writeToFile(context, new ByteArrayInputStream(bytes)); - } catch (IOException e) { - throw new VaultManagerException(e); + @NonNull + public VaultRepository unlock(@NonNull VaultFileCredentials creds) throws VaultRepositoryException { + VaultRepository repo = load(getVaultFile(), creds); + startNotificationService(); + return repo; + } + + /** + * Locks the vault and the app. + * @param userInitiated whether or not the user initiated the lock in MainActivity. + */ + public void lock(boolean userInitiated) { + _repo = null; + + for (LockListener listener : _lockListeners) { + listener.onLocked(userInitiated); } + + stopNotificationService(); + loadVaultFile(); } - public void destroy() { - _backups.destroy(); + public void enableEncryption(VaultFileCredentials creds) throws VaultRepositoryException { + getVault().setCredentials(creds); + saveAndBackup(); + startNotificationService(); } - public void save(boolean backup) throws VaultManagerException { + public void disableEncryption() throws VaultRepositoryException { + getVault().setCredentials(null); + save(); + + // remove any keys that are stored in the KeyStore try { - JSONObject obj = _vault.toJson(); + KeyStoreHandle handle = new KeyStoreHandle(); + handle.clear(); + } catch (KeyStoreHandleException e) { + // this cleanup operation is not strictly necessary, so we ignore any exceptions here + e.printStackTrace(); + } - VaultFile file = new VaultFile(); - if (isEncryptionEnabled()) { - file.setContent(obj, _creds); - } else { - file.setContent(obj); - } + stopNotificationService(); + } - save(_context, file); - } catch (VaultFileException e) { - throw new VaultManagerException(e); - } + public void save() throws VaultRepositoryException { + getVault().save(); + } + + public void saveAndBackup() throws VaultRepositoryException { + save(); - if (backup) { + if (getVault().isEncryptionEnabled()) { if (_prefs.isBackupsEnabled()) { try { - backup(); + scheduleBackup(); _prefs.setBackupsError(null); - } catch (VaultManagerException e) { + } catch (VaultRepositoryException e) { _prefs.setBackupsError(e); } } if (_prefs.isAndroidBackupsEnabled()) { - androidBackupDataChanged(); - } - } - } - - /** - * Exports the vault bt serializing it and writing it to the given OutputStream. If encryption - * is enabled, the vault will be encrypted automatically. - */ - public void export(OutputStream stream) throws VaultManagerException { - export(stream, getCredentials()); - } - - /** - * Exports the vault by serializing it and writing it to the given OutputStream. If creds is - * not null, it will be used to encrypt the vault first. - */ - public void export(OutputStream stream, VaultFileCredentials creds) throws VaultManagerException { - try { - VaultFile vaultFile = new VaultFile(); - if (creds != null) { - vaultFile.setContent(_vault.toJson(), creds); - } else { - vaultFile.setContent(_vault.toJson()); + scheduleAndroidBackup(); } - - byte[] bytes = vaultFile.toBytes(); - stream.write(bytes); - } catch (IOException | VaultFileException e) { - throw new VaultManagerException(e); - } - } - - /** - * Exports the vault by serializing the list of entries to a newline-separated list of - * Google Authenticator URI's and writing it to the given OutputStream. - */ - public void exportGoogleUris(OutputStream outStream) throws VaultManagerException { - try (PrintStream stream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name())) { - for (VaultEntry entry : getEntries()) { - GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer()); - stream.println(info.getUri().toString()); - } - } catch (IOException e) { - throw new VaultManagerException(e); } } - public void backup() throws VaultManagerException { + public void scheduleBackup() throws VaultRepositoryException { try { File dir = new File(_context.getCacheDir(), "backup"); if (!dir.exists() && !dir.mkdir()) { @@ -209,83 +189,154 @@ public void backup() throws VaultManagerException { } File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir); - try (InputStream inStream = getAtomicFile(_context).openRead(); - OutputStream outStream = new FileOutputStream(tempFile)) { - IOUtils.copy(inStream, outStream); - } - + getVault().backupTo(tempFile); _backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount()); } catch (IOException e) { - throw new VaultManagerException(e); + throw new VaultRepositoryException(e); } } - public void androidBackupDataChanged() { + public void scheduleAndroidBackup() { _androidBackups.dataChanged(); } - public void addEntry(VaultEntry entry) { - _vault.getEntries().add(entry); + public boolean isAutoLockEnabled(int autoLockType) { + return _prefs.isAutoLockTypeEnabled(autoLockType) + && isVaultLoaded() + && getVault().isEncryptionEnabled(); + } + + public void registerLockListener(LockListener listener) { + _lockListeners.add(listener); } - public VaultEntry getEntryByUUID(UUID uuid) { - return _vault.getEntries().getByUUID(uuid); + public void unregisterLockListener(LockListener listener) { + _lockListeners.remove(listener); } - public VaultEntry removeEntry(VaultEntry entry) { - return _vault.getEntries().remove(entry); + /** + * Sets whether to block automatic lock on minimization. This should only be called + * by activities before invoking an intent that shows a DocumentsUI, because that + * action leads AppLifecycleObserver to believe that the app has been minimized. + */ + public void setBlockAutoLock(boolean block) { + _blockAutoLock = block; } - public void wipeEntries() { - _vault.getEntries().wipe(); + /** + * Reports whether automatic lock on minimization is currently blocked. + */ + public boolean isAutoLockBlocked() { + return _blockAutoLock; } - public VaultEntry replaceEntry(VaultEntry entry) { - return _vault.getEntries().replace(entry); + public boolean isVaultLoaded() { + return _repo != null; } - public void swapEntries(VaultEntry entry1, VaultEntry entry2) { - _vault.getEntries().swap(entry1, entry2); + public boolean isVaultFileLoaded() { + return _vaultFile != null; } - public boolean isEntryDuplicate(VaultEntry entry) { - return _vault.getEntries().has(entry); + public boolean isVaultInitNeeded() { + return !isVaultLoaded() && !isVaultFileLoaded() && getVaultFileError() == null; } - public Collection getEntries() { - return _vault.getEntries().getValues(); + @NonNull + public VaultRepository getVault() { + if (!isVaultLoaded()) { + throw new IllegalStateException("Vault manager is not initialized"); + } + + return _repo; } - public TreeSet getGroups() { - TreeSet groups = new TreeSet<>(Collator.getInstance()); - for (VaultEntry entry : getEntries()) { - String group = entry.getGroup(); - if (group != null) { - groups.add(group); + @NonNull + public VaultFile getVaultFile() { + if (_vaultFile == null) { + throw new IllegalStateException("Vault file is not in memory"); + } + + return _vaultFile; + } + + @Nullable + public VaultRepositoryException getVaultFileError() { + return _vaultFileError; + } + + /** + * Starts an external activity, temporarily blocks automatic lock of Aegis and + * shows an error dialog if the target activity is not found. + */ + public void startActivityForResult(Activity activity, Intent intent, int requestCode) { + setBlockAutoLock(true); + + try { + activity.startActivityForResult(intent, requestCode, null); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + + if (isDocsAction(intent.getAction())) { + Dialogs.showErrorDialog(activity, R.string.documentsui_error, e); + } else { + throw e; + } + } + } + + /** + * Starts an external activity, temporarily blocks automatic lock of Aegis and + * shows an error dialog if the target activity is not found. + */ + public void startActivity(Fragment fragment, Intent intent) { + startActivityForResult(fragment, intent, -1); + } + + /** + * Starts an external activity, temporarily blocks automatic lock of Aegis and + * shows an error dialog if the target activity is not found. + */ + public void startActivityForResult(Fragment fragment, Intent intent, int requestCode) { + setBlockAutoLock(true); + + try { + fragment.startActivityForResult(intent, requestCode, null); + } catch (ActivityNotFoundException e) { + e.printStackTrace(); + + if (isDocsAction(intent.getAction())) { + Dialogs.showErrorDialog(fragment.getContext(), R.string.documentsui_error, e); + } else { + throw e; } } - return groups; } - public VaultFileCredentials getCredentials() { - return _creds; + private void startNotificationService() { + _context.startService(getNotificationServiceIntent()); } - public void setCredentials(VaultFileCredentials creds) { - _creds = creds; + private void stopNotificationService() { + _context.stopService(getNotificationServiceIntent()); } - public boolean isEncryptionEnabled() { - return _creds != null; + private Intent getNotificationServiceIntent() { + return new Intent(_context, NotificationService.class); } - public void enableEncryption(VaultFileCredentials creds) throws VaultManagerException { - _creds = creds; - save(true); + private static boolean isDocsAction(@Nullable String action) { + return action != null && (action.equals(Intent.ACTION_GET_CONTENT) + || action.equals(Intent.ACTION_CREATE_DOCUMENT) + || action.equals(Intent.ACTION_OPEN_DOCUMENT) + || action.equals(Intent.ACTION_OPEN_DOCUMENT_TREE)); } - public void disableEncryption() throws VaultManagerException { - _creds = null; - save(true); + public interface LockListener { + /** + * Called when the vault lock status changes + * @param userInitiated whether or not the user initiated the lock in MainActivity. + */ + void onLocked(boolean userInitiated); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManagerException.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManagerException.java deleted file mode 100644 index 3ac44b507b..0000000000 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManagerException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.beemdevelopment.aegis.vault; - -public class VaultManagerException extends Exception { - public VaultManagerException(Throwable cause) { - super(cause); - } - - public VaultManagerException(String message) { - super(message); - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java new file mode 100644 index 0000000000..aa85780341 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java @@ -0,0 +1,236 @@ +package com.beemdevelopment.aegis.vault; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.util.AtomicFile; + +import com.beemdevelopment.aegis.otp.GoogleAuthInfo; +import com.beemdevelopment.aegis.util.IOUtils; + +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.text.Collator; +import java.util.Collection; +import java.util.TreeSet; +import java.util.UUID; + +public class VaultRepository { + public static final String FILENAME = "aegis.json"; + public static final String FILENAME_PREFIX_EXPORT = "aegis-export"; + public static final String FILENAME_PREFIX_EXPORT_PLAIN = "aegis-export-plain"; + public static final String FILENAME_PREFIX_EXPORT_URI = "aegis-export-uri"; + + @NonNull + private final Vault _vault; + + @Nullable + private VaultFileCredentials _creds; + + @NonNull + private final Context _context; + + public VaultRepository(@NonNull Context context, @NonNull Vault vault, @Nullable VaultFileCredentials creds) { + _context = context; + _vault = vault; + _creds = creds; + } + + private static AtomicFile getAtomicFile(Context context) { + return new AtomicFile(new File(context.getFilesDir(), FILENAME)); + } + + public static boolean fileExists(Context context) { + File file = getAtomicFile(context).getBaseFile(); + return file.exists() && file.isFile(); + } + + public static void deleteFile(Context context) { + getAtomicFile(context).delete(); + } + + public static VaultFile readVaultFile(Context context) throws VaultRepositoryException { + AtomicFile file = getAtomicFile(context); + + try { + byte[] fileBytes = file.readFully(); + return VaultFile.fromBytes(fileBytes); + } catch (IOException | VaultFileException e) { + throw new VaultRepositoryException(e); + } + } + + public static void writeToFile(Context context, InputStream inStream) throws IOException { + AtomicFile file = VaultRepository.getAtomicFile(context); + + FileOutputStream outStream = null; + try { + outStream = file.startWrite(); + IOUtils.copy(inStream, outStream); + file.finishWrite(outStream); + } catch (IOException e) { + if (outStream != null) { + file.failWrite(outStream); + } + throw e; + } + } + + public static VaultRepository fromFile(Context context, VaultFile file, VaultFileCredentials creds) throws VaultRepositoryException { + if (file.isEncrypted() && creds == null) { + throw new IllegalArgumentException("The VaultFile is encrypted but the given VaultFileCredentials is null"); + } + + Vault vault; + try { + JSONObject obj; + if (!file.isEncrypted()) { + obj = file.getContent(); + } else { + obj = file.getContent(creds); + } + + vault = Vault.fromJson(obj); + } catch (VaultException | VaultFileException e) { + throw new VaultRepositoryException(e); + } + + return new VaultRepository(context, vault, creds); + } + + void save() throws VaultRepositoryException { + try { + JSONObject obj = _vault.toJson(); + + VaultFile file = new VaultFile(); + if (isEncryptionEnabled()) { + file.setContent(obj, _creds); + } else { + file.setContent(obj); + } + + try { + byte[] bytes = file.toBytes(); + writeToFile(_context, new ByteArrayInputStream(bytes)); + } catch (IOException e) { + throw new VaultRepositoryException(e); + } + } catch (VaultFileException e) { + throw new VaultRepositoryException(e); + } + } + + /** + * Exports the vault bt serializing it and writing it to the given OutputStream. If encryption + * is enabled, the vault will be encrypted automatically. + */ + public void export(OutputStream stream) throws VaultRepositoryException { + export(stream, getCredentials()); + } + + /** + * Exports the vault by serializing it and writing it to the given OutputStream. If creds is + * not null, it will be used to encrypt the vault first. + */ + public void export(OutputStream stream, VaultFileCredentials creds) throws VaultRepositoryException { + try { + VaultFile vaultFile = new VaultFile(); + if (creds != null) { + vaultFile.setContent(_vault.toJson(), creds); + } else { + vaultFile.setContent(_vault.toJson()); + } + + byte[] bytes = vaultFile.toBytes(); + stream.write(bytes); + } catch (IOException | VaultFileException e) { + throw new VaultRepositoryException(e); + } + } + + /** + * Exports the vault by serializing the list of entries to a newline-separated list of + * Google Authenticator URI's and writing it to the given OutputStream. + */ + public void exportGoogleUris(OutputStream outStream) throws VaultRepositoryException { + try (PrintStream stream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name())) { + for (VaultEntry entry : getEntries()) { + GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer()); + stream.println(info.getUri().toString()); + } + } catch (IOException e) { + throw new VaultRepositoryException(e); + } + } + + public void backupTo(File destFile) throws IOException { + try (InputStream inStream = getAtomicFile(_context).openRead(); + OutputStream outStream = new FileOutputStream(destFile)) { + IOUtils.copy(inStream, outStream); + } + } + + public void addEntry(VaultEntry entry) { + _vault.getEntries().add(entry); + } + + public VaultEntry getEntryByUUID(UUID uuid) { + return _vault.getEntries().getByUUID(uuid); + } + + public VaultEntry removeEntry(VaultEntry entry) { + return _vault.getEntries().remove(entry); + } + + public void wipeEntries() { + _vault.getEntries().wipe(); + } + + public VaultEntry replaceEntry(VaultEntry entry) { + return _vault.getEntries().replace(entry); + } + + public void swapEntries(VaultEntry entry1, VaultEntry entry2) { + _vault.getEntries().swap(entry1, entry2); + } + + public boolean isEntryDuplicate(VaultEntry entry) { + return _vault.getEntries().has(entry); + } + + public Collection getEntries() { + return _vault.getEntries().getValues(); + } + + public TreeSet getGroups() { + TreeSet groups = new TreeSet<>(Collator.getInstance()); + for (VaultEntry entry : getEntries()) { + String group = entry.getGroup(); + if (group != null) { + groups.add(group); + } + } + return groups; + } + + public VaultFileCredentials getCredentials() { + return _creds; + } + + public void setCredentials(VaultFileCredentials creds) { + _creds = creds; + } + + public boolean isEncryptionEnabled() { + return _creds != null; + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepositoryException.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepositoryException.java new file mode 100644 index 0000000000..f3e8bff6f1 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepositoryException.java @@ -0,0 +1,11 @@ +package com.beemdevelopment.aegis.vault; + +public class VaultRepositoryException extends Exception { + public VaultRepositoryException(Throwable cause) { + super(cause); + } + + public VaultRepositoryException(String message) { + super(message); + } +} diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3e463b8184..aee053b3f6 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -5,37 +5,37 @@ android:title="@string/action_settings"> diff --git a/build.gradle b/build.gradle index 8ddcc852b3..8b020737c6 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:7.0.4' + classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.18' // NOTE: Do not place your application dependencies here; they belong From baa3cac5a413b6756f8060ca2d55f4c2a1b05e28 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 6 Mar 2022 13:33:57 +0100 Subject: [PATCH 030/389] Remove slot manager activity We don't really use this anymore, and it was only available for debug builds anyway --- .../aegis/ui/SlotManagerActivity.java | 236 ------------------ .../SecurityPreferencesFragment.java | 35 --- app/src/main/res/layout/activity_slots.xml | 124 --------- app/src/main/res/values-ar-rSA/strings.xml | 2 - app/src/main/res/values-bg-rBG/strings.xml | 2 - app/src/main/res/values-cs-rCZ/strings.xml | 2 - app/src/main/res/values-da-rDK/strings.xml | 2 - app/src/main/res/values-de-rDE/strings.xml | 2 - app/src/main/res/values-el-rGR/strings.xml | 2 - app/src/main/res/values-es-rES/strings.xml | 2 - app/src/main/res/values-eu-rES/strings.xml | 2 - app/src/main/res/values-fa-rIR/strings.xml | 2 - app/src/main/res/values-fi-rFI/strings.xml | 2 - app/src/main/res/values-fr-rFR/strings.xml | 2 - app/src/main/res/values-hi-rIN/strings.xml | 2 - app/src/main/res/values-hu-rHU/strings.xml | 2 - app/src/main/res/values-in-rID/strings.xml | 2 - app/src/main/res/values-it-rIT/strings.xml | 2 - app/src/main/res/values-ja-rJP/strings.xml | 2 - app/src/main/res/values-kn-rIN/strings.xml | 1 - app/src/main/res/values-lv-rLV/strings.xml | 2 - app/src/main/res/values-nl-rNL/strings.xml | 2 - app/src/main/res/values-pl-rPL/strings.xml | 2 - app/src/main/res/values-pt-rBR/strings.xml | 2 - app/src/main/res/values-pt-rPT/strings.xml | 2 - app/src/main/res/values-ro-rRO/strings.xml | 2 - app/src/main/res/values-ru-rRU/strings.xml | 2 - app/src/main/res/values-sk-rSK/strings.xml | 1 - app/src/main/res/values-sv-rSE/strings.xml | 2 - app/src/main/res/values-tr-rTR/strings.xml | 2 - app/src/main/res/values-uk-rUA/strings.xml | 2 - app/src/main/res/values-vi-rVN/strings.xml | 2 - app/src/main/res/values-zh-rCN/strings.xml | 2 - app/src/main/res/values-zh-rTW/strings.xml | 2 - app/src/main/res/values/strings.xml | 2 - app/src/main/res/xml/preferences_security.xml | 6 - 36 files changed, 463 deletions(-) delete mode 100644 app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java delete mode 100644 app/src/main/res/layout/activity_slots.xml diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java deleted file mode 100644 index aaab4eb851..0000000000 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/SlotManagerActivity.java +++ /dev/null @@ -1,236 +0,0 @@ -package com.beemdevelopment.aegis.ui; - -import android.content.Intent; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AlertDialog; -import androidx.biometric.BiometricPrompt; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; - -import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.crypto.KeyStoreHandle; -import com.beemdevelopment.aegis.crypto.KeyStoreHandleException; -import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.vault.VaultFileCredentials; -import com.beemdevelopment.aegis.vault.slots.BiometricSlot; -import com.beemdevelopment.aegis.vault.slots.PasswordSlot; -import com.beemdevelopment.aegis.vault.slots.Slot; -import com.beemdevelopment.aegis.vault.slots.SlotException; -import com.beemdevelopment.aegis.vault.slots.SlotList; -import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer; -import com.beemdevelopment.aegis.helpers.BiometricsHelper; -import com.beemdevelopment.aegis.ui.views.SlotAdapter; - -import javax.crypto.Cipher; - -public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener { - private VaultFileCredentials _creds; - private SlotAdapter _adapter; - - private boolean _edited; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (abortIfOrphan(savedInstanceState)) { - return; - } - setContentView(R.layout.activity_slots); - setSupportActionBar(findViewById(R.id.toolbar)); - _edited = false; - - ActionBar bar = getSupportActionBar(); - bar.setHomeAsUpIndicator(R.drawable.ic_close); - bar.setDisplayHomeAsUpEnabled(true); - - findViewById(R.id.button_add_biometric).setOnClickListener(view -> { - if (BiometricsHelper.isAvailable(this)) { - BiometricSlotInitializer initializer = new BiometricSlotInitializer(SlotManagerActivity.this, new RegisterBiometricsListener()); - BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder() - .setTitle(getString(R.string.add_biometric_slot)) - .setNegativeButtonText(getString(android.R.string.cancel)) - .build(); - initializer.authenticate(info); - } - }); - - findViewById(R.id.button_add_password).setOnClickListener(view -> { - Dialogs.showSetPasswordDialog(this, new PasswordListener()); - }); - - // set up the recycler view - _adapter = new SlotAdapter(this); - RecyclerView slotsView = findViewById(R.id.list_slots); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - slotsView.setLayoutManager(layoutManager); - slotsView.setAdapter(_adapter); - slotsView.setNestedScrollingEnabled(false); - - // load the slots and masterKey - _creds = (VaultFileCredentials) getIntent().getSerializableExtra("creds"); - for (Slot slot : _creds.getSlots()) { - _adapter.addSlot(slot); - } - - updateBiometricsButton(); - } - - private void updateBiometricsButton() { - // only show the biometrics option if we can get an instance of the biometrics manager - // and if none of the slots in the collection has a matching alias in the keystore - int visibility = View.VISIBLE; - if (BiometricsHelper.isAvailable(this)) { - try { - KeyStoreHandle keyStore = new KeyStoreHandle(); - for (BiometricSlot slot : _creds.getSlots().findAll(BiometricSlot.class)) { - if (keyStore.containsKey(slot.getUUID().toString())) { - visibility = View.GONE; - break; - } - } - } catch (KeyStoreHandleException e) { - visibility = View.GONE; - } - } else { - visibility = View.GONE; - } - findViewById(R.id.button_add_biometric).setVisibility(visibility); - } - - private void onSave() { - Intent intent = new Intent(); - intent.putExtra("creds", _creds); - setResult(RESULT_OK, intent); - finish(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - onBackPressed(); - break; - case R.id.action_save: - onSave(); - break; - default: - return super.onOptionsItemSelected(item); - } - - return true; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_slots, menu); - return true; - } - - @Override - public void onBackPressed() { - if (!_edited) { - super.onBackPressed(); - return; - } - - Dialogs.showDiscardDialog(this, - (dialog, which) -> onSave(), - (dialog, which) -> super.onBackPressed() - ); - } - - @Override - public void onEditSlot(Slot slot) { - /*EditText textName = new EditText(this); - textName.setHint("Name"); - - new AlertDialog.Builder(this) - .setTitle("Edit slot name") - .setView(textName) - .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> { - String name = textName.getText().toString(); - _edited = true; - }) - .setNegativeButton(android.R.string.cancel, null) - .show();*/ - } - - @Override - public void onRemoveSlot(Slot slot) { - SlotList slots = _creds.getSlots(); - if (slot instanceof PasswordSlot && slots.findAll(PasswordSlot.class).size() <= 1) { - Toast.makeText(this, R.string.password_slot_error, Toast.LENGTH_SHORT).show(); - return; - } - - Dialogs.showSecureDialog(new AlertDialog.Builder(this) - .setTitle(R.string.remove_slot) - .setMessage(R.string.remove_slot_description) - .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { - slots.remove(slot); - _adapter.removeSlot(slot); - _edited = true; - updateBiometricsButton(); - }) - .setNegativeButton(android.R.string.no, null) - .create()); - } - - private void addSlot(Slot slot) { - _creds.getSlots().add(slot); - _adapter.addSlot(slot); - _edited = true; - updateBiometricsButton(); - } - - private void showSlotError(String error) { - Toast.makeText(SlotManagerActivity.this, getString(R.string.adding_new_slot_error) + error, Toast.LENGTH_SHORT).show(); - } - - private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener { - - @Override - public void onInitializeSlot(BiometricSlot slot, Cipher cipher) { - try { - slot.setKey(_creds.getKey(), cipher); - addSlot(slot); - } catch (SlotException e) { - onSlotInitializationFailed(0, e.toString()); - } - } - - @Override - public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) { - if (!BiometricsHelper.isCanceled(errorCode)) { - showSlotError(errString.toString()); - } - } - } - - private class PasswordListener implements Dialogs.SlotListener { - - @Override - public void onSlotResult(Slot slot, Cipher cipher) { - try { - slot.setKey(_creds.getKey(), cipher); - } catch (SlotException e) { - onException(e); - return; - } - - addSlot(slot); - } - - @Override - public void onException(Exception e) { - showSlotError(e.toString()); - } - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/SecurityPreferencesFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/SecurityPreferencesFragment.java index 6afffb2173..c86555a099 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/SecurityPreferencesFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/SecurityPreferencesFragment.java @@ -2,8 +2,6 @@ import static android.text.TextUtils.isDigitsOnly; -import android.app.Activity; -import android.content.Intent; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; @@ -15,7 +13,6 @@ import androidx.preference.Preference; import androidx.preference.SwitchPreferenceCompat; -import com.beemdevelopment.aegis.BuildConfig; import com.beemdevelopment.aegis.PassReminderFreq; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; @@ -23,7 +20,6 @@ import com.beemdevelopment.aegis.crypto.KeyStoreHandleException; import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer; import com.beemdevelopment.aegis.helpers.BiometricsHelper; -import com.beemdevelopment.aegis.ui.SlotManagerActivity; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.preferences.SwitchPreference; import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask; @@ -45,7 +41,6 @@ public class SecurityPreferencesFragment extends PreferencesFragment { private SwitchPreference _biometricsPreference; private Preference _autoLockPreference; private Preference _setPasswordPreference; - private Preference _slotsPreference; private Preference _passwordReminderPreference; private SwitchPreferenceCompat _pinKeyboardPreference; @@ -158,14 +153,6 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { return false; }); - _slotsPreference = findPreference("pref_slots"); - _slotsPreference.setOnPreferenceClickListener(preference -> { - Intent intent = new Intent(getActivity(), SlotManagerActivity.class); - intent.putExtra("creds", _vaultManager.getVault().getCredentials()); - startActivityForResult(intent, CODE_SLOTS); - return true; - }); - _pinKeyboardPreference = findPreference("pref_pin_keyboard"); _pinKeyboardPreference.setOnPreferenceChangeListener((preference, newValue) -> { if (!(boolean) newValue) { @@ -247,30 +234,11 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { }); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (data != null && requestCode == CODE_SLOTS) { - onSlotManagerResult(resultCode, data); - } - } - - private void onSlotManagerResult(int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK) { - return; - } - - VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds"); - _vaultManager.getVault().setCredentials(creds); - saveAndBackupVault(); - updateEncryptionPreferences(); - } - private void updateEncryptionPreferences() { boolean encrypted = _vaultManager.getVault().isEncryptionEnabled(); _encryptionPreference.setChecked(encrypted, true); _setPasswordPreference.setVisible(encrypted); _biometricsPreference.setVisible(encrypted); - _slotsPreference.setEnabled(encrypted); _autoLockPreference.setVisible(encrypted); _pinKeyboardPreference.setVisible(encrypted); @@ -278,18 +246,15 @@ private void updateEncryptionPreferences() { SlotList slots = _vaultManager.getVault().getCredentials().getSlots(); boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1; boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1; - boolean showSlots = BuildConfig.DEBUG || multiPassword || multiBio; boolean canUseBio = BiometricsHelper.isAvailable(getContext()); _setPasswordPreference.setEnabled(!multiPassword); _biometricsPreference.setEnabled(canUseBio && !multiBio); _biometricsPreference.setChecked(slots.has(BiometricSlot.class), true); - _slotsPreference.setVisible(showSlots); _passwordReminderPreference.setVisible(slots.has(BiometricSlot.class)); } else { _setPasswordPreference.setEnabled(false); _biometricsPreference.setEnabled(false); _biometricsPreference.setChecked(false, true); - _slotsPreference.setVisible(false); _passwordReminderPreference.setVisible(false); } } diff --git a/app/src/main/res/layout/activity_slots.xml b/app/src/main/res/layout/activity_slots.xml deleted file mode 100644 index a2dbd93057..0000000000 --- a/app/src/main/res/layout/activity_slots.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values-ar-rSA/strings.xml b/app/src/main/res/values-ar-rSA/strings.xml index 61ab12fb3a..66135394b6 100644 --- a/app/src/main/res/values-ar-rSA/strings.xml +++ b/app/src/main/res/values-ar-rSA/strings.xml @@ -33,8 +33,6 @@ قم بالتفعيل لإظهار اسم الحساب بجانب المصدِّر المهلة قفل المخزن تلقائيًا بعد %1$s ثواني من عدم الاستعمال - خانات المفاتيح - إدارة لائحة المفاتيح التي يمكنها فك تشفير المخزن استيراد من ملف استيراد الرموز من ملف نسخ تلقائي للخزينة diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index 76976109fd..d563a089d6 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -34,8 +34,6 @@ Активирайте това, за да се показва името на акаунта до издателя Изчакване Автоматично заключване на трезора след %1$s секунди бездействие - Ключови слотове - Управление на списъка с ключове, които могат да дешифрират трезора Импорт от файл Импортиране на токени от файл Участвайте в системата за архивиране на Android diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index 9159760138..c29e915ca2 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -40,8 +40,6 @@ Zobrazit název účtu vedle poskytovatele Časový limit Automaticky zamknout trezor po %1$s sekundách nečinnosti - Sloty klíčů - Správa seznamu klíčů, které mohou dešifrovat trezor Importovat ze souboru Importovat tokeny ze souboru Účast v zálohovacím systému Androidu diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 2da36218cd..64faf2eb39 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -40,8 +40,6 @@ Aktiver dette for at vise kontonavnet ved siden af udstederen Timeout Lås automatisk Boksen efter %1$s sekunders inaktivitet - Nøglepladser - Håndtér listen over nøgler, som kan dekryptere Boksen Import til fil Importér tokens fra en fil Deltag i Androids sikkerhedskopieringssystem diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 3725aa1c72..59f78dc310 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -40,8 +40,6 @@ Aktiviere dies, um den Kontonamen neben dem Herausgeber anzuzeigen Zeitüberschreitung Sperrt die Datenbank automatisch nach %1$s Sekunden Inaktivität - Schlüsselplätze - Verwalte die Liste der Schlüssel, die die Datenbank entschlüsseln können Aus Datei importieren Token aus einer Datei importieren Einbindung in das Sicherungssystem von Android diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index 63e7a8146e..03bd26ff80 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -40,8 +40,6 @@ Ενεργοποίηση προβολής ονόματος λογαριασμού δίπλα στον εκδότη Χρονικό όριο Αυτόματο κλείδωμα κρύπτης μετά από %1$s δευτερόλεπτα αδράνειας - Υποδοχή κλειδιών - Διαχείριση λίστας κλειδιών που μπορούν να αποκρυπτογραφήσουν την κρύπτη Εισαγωγή από αρχείο Εισαγωγή αναγνωριστικών από αρχείο Συμμετοχή στο σύστημα αντιγράφων ασφαλείας του Android diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 4c0c222033..6be057d06f 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -40,8 +40,6 @@ Active esto para mostrar el nombre de la cuenta junto al emisor Tiempo de espera Bloquear automáticamente la bóveda después de %1$s segundos de inactividad - Claves - Administrar la lista de claves que puede descifrar la bóveda Importar desde un archivo Importar tokens desde un archivo Participar en el sistema de copia de seguridad de Android diff --git a/app/src/main/res/values-eu-rES/strings.xml b/app/src/main/res/values-eu-rES/strings.xml index 71f8f74544..d3175e0721 100644 --- a/app/src/main/res/values-eu-rES/strings.xml +++ b/app/src/main/res/values-eu-rES/strings.xml @@ -40,8 +40,6 @@ Aktibatu hau kontu-izena igorlearen ondoan bistaratzeko Denbora-muga Blokeatu automatikoki biltegia %1$s segundo jarduerarik gabe egon ondoren - Arteka giltzarriak - Kudeatu biltegia deszifratu dezaketen gakoen zerrenda Inportatu fitxategi batetik Inportatu marka-ikurrak fitxategi batetik Androiden segurtasun kopien sisteman parte hartu diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index ee2158f7c4..5b4ae1c193 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -34,8 +34,6 @@ برای نمایش نام حساب کاربری کنار صادر کننده فعال کنید اتمام مهلت بعد از %1$s ثانیه عدم فعالیت مخزن را قفل کن - محل کلید‌ها - مدیریت کلیدهایی که میتوانند مخزن را رمزگشایی کنند وارد کردن از فایل وارد کردن توکن ها از فایل شرکت کردن در سیستم پشتیبانگیری اندروید diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index 8f6e071afe..a25eb73ae4 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -39,8 +39,6 @@ Ota käyttöön nähdäksesi tilin nimen sen myöntäjän rinnalla Aikakatkaisu Lukitse holvi automaattisesti %1$s sekunnin käyttämättömyyden jälkeen - Avainpaikat - Hallitse listaa avaimista, jotka voivat purkaa holvin salauksen Tuo tiedostosta Tuo tunnukset tiedostosta Käytä Androidin omaa varmuuskopiointijärjestelmää diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 0e66fa7a46..1b337f3f09 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -40,8 +40,6 @@ Activer pour afficher le nom de compte près de l\'émetteur Délai Verrouiller automatiquement le coffre-fort après %1$s secondes d\'inactivité - Emplacements de clé - Gérer la liste des clés qui peuvent déchiffrer le coffre-fort Importer depuis un fichier Importer des jetons depuis un fichier Participer au système de sauvegarde d\'Android diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index ef35e19f8a..fecbb85ef1 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -20,8 +20,6 @@ जारीकर्ता के बगल में खाता नाम दिखाने के लिए इसे सक्षम करें समय सीमा समाप्ति निष्क्रियता के %1$s सेकंड के बाद वॉल्ट को स्वचालित रूप से लॉक करें - कुंजी स्लॉट - कुंजीओं की सूची प्रबंधित करें जो वॉल्ट को डिक्रिप्ट कर सकती हैं फ़ाइल से आयात करें टोकन को फ़ाइल से आयात करें बदलाव होने पर एक्सटर्नल स्टोरेज में स्वतः ही वॉल्ट का बैकअप बनाएँ। यह सिर्फ एन्क्रिप्टेड वॉल्ट को सपोर्ट करता है। diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index ec8f69f253..81cf8c34e5 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -18,8 +18,6 @@ Kapcsolja be a fiók nevének megjelenítéséhez a kibocsátó mellett Időtúllépés A széf automatikus zárolása %1$s másodperc tétlenség után - Feloldási kulcsok - Azon kulcsok kezelése, melyek feloldhatják a széfet Importálás fájlból Tokenek importálása fájlból Biztonsági mentést készít a széfről a külső tárolón. Ez csak titkosított széfek esetén támogatott. diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml index 00fadab3c5..21a279509f 100644 --- a/app/src/main/res/values-in-rID/strings.xml +++ b/app/src/main/res/values-in-rID/strings.xml @@ -40,8 +40,6 @@ Aktifkan ini untuk menampilkan nama akun di samping penerbit Waktu habis Kunci brankas secara otomatis setelah %1$s detik tidak aktif - Slot kunci - Kelola daftar kunci yang dapat mendekripsi brankas Impor berkas Impor token dari berkas Partisipasi di sistem pencadangan Android diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index 03e9a46ba5..c6181747bb 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -40,8 +40,6 @@ Abilita questa opzione per mostrare il nome dell\'account vicino all\'emittente Timeout Blocca automaticamente la cassaforte dopo %1$s secondi di inattività - Slot chiavi - Gestisci la lista delle chiavi che possono decriptare il database Importa da file Importa token da file Partecipa al sistema di backup di Android diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index fe640ead13..b38d2f6231 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -40,8 +40,6 @@ 有効にすると発行者の隣にアカウント名を表示します タイムアウト 操作を%1$s秒間行わない場合、自動的にロックします - キースロット - Vaultを復号できる鍵を管理します ファイルからインポート ファイルからトークンをインポート Androidのバックアップシステムに含める diff --git a/app/src/main/res/values-kn-rIN/strings.xml b/app/src/main/res/values-kn-rIN/strings.xml index c23645073e..33011cfd26 100644 --- a/app/src/main/res/values-kn-rIN/strings.xml +++ b/app/src/main/res/values-kn-rIN/strings.xml @@ -15,7 +15,6 @@ ಖಾತೆಯ ಹೆಸರನ್ನು ತೋರಿಸು ಖಾತೆಯ ಹೆಸರನ್ನು ನೀಡುವವರ ಪಕ್ಕ ತೋರಿಸುವುದಕ್ಕೆ ಇದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸು ಕಾಲಾವಧಿ - ಬೀಗದ ಕೈಯ್ಯಿನ ಸ್ಲಾಟುಗಳು ಫೈಲಿಂದ ಆಮದಿಸು ಅಪ್ಲಿಕೇಶನಿಂದ ಆಮದಿಸು ರಫ್ತು ಮಾಡು diff --git a/app/src/main/res/values-lv-rLV/strings.xml b/app/src/main/res/values-lv-rLV/strings.xml index aecef5bb50..3d78f758aa 100644 --- a/app/src/main/res/values-lv-rLV/strings.xml +++ b/app/src/main/res/values-lv-rLV/strings.xml @@ -40,8 +40,6 @@ Iespējot, lai rādītu konta nosaukumu blakus izsniedzējam Noildze Aizslēgt glabātavu pēc %1$s sekundes(žu) bezdarbības - Atslēgas vieta - Pārvaldīt atslēgu, ar kurām var atšifrēt glabātavu, sarakstu Izgūt no datnes Ievietot kodus no datnes Ņemt dalību Android dublēšanas sistēmā diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index a064def220..1c4502b24b 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -40,8 +40,6 @@ Schakel in om de accountnaam naast de uitgever te tonen Time-out Automatisch kluis vergrendelen na %1$s seconden zonder activiteit - Vergrendelingssleutels - Beheer de lijst van vergrendelingssleutels die de kluis kunnen decrypten Importeren vanuit een bestand Importeer tokens vanuit een bestand Neem deel aan het back-upsysteem van Android diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index 4a832ce746..c22b7766d8 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -37,8 +37,6 @@ Włącz tę opcję, aby pokazywać nazwę konta obok wydawcy Limit czasu Automatycznie zablokuj sejf po %1$s sekundach bezczynności - Sloty klucza - Zarządzaj listą kluczy, które odszyfrowują sejf Importuj z pliku Importuj tokeny z aplikacji Utwórz kopię zapasową w systemie Android diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ffa4345ded..9feb5de973 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -40,8 +40,6 @@ Habilite essa opção para exibir o nome da conta ao lado do nome do serviço Tempo de espera Bloqueia automaticamente o cofre depois de %1$s segundos de inatividade - Slots de chaves - Gerencia a lista de chaves que podem descriptografar o cofre Importar de arquivo Importa tokens de um arquivo Participar do sistema de backup do Android diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index b420d7887c..f0e3b34a21 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -20,8 +20,6 @@ Ative isto para mostrar o nome da conta ao lado do emissor Tempo esgotado Bloquear automaticamente o cofre após %1$s segundos de inatividade - Slots de chaves - Gerenciar a lista de chaves que podem descriptografar o cofre Importar arquivo Importar tokens de um arquivo Cria automaticamente backups do cofre no armazenamento externo quando alterações forem feitas. Isso só é suportado para cofres criptografados. diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index 18f18e998d..5eeb3ba505 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -40,8 +40,6 @@ Activează pentru a afișa numele contului lângă emitent Inactivitate Blochează automat seiful după %1$s secunde de inactivitate - Sloturi cheie - Gestionați lista de chei care pot decripta seiful Importă din fișier Importă token-uri dintr-un fișier Participă la copia de rezervă a sistemului Android diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index edbc1c4d8a..3539336443 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -40,8 +40,6 @@ Включите, чтобы показывать название учётной записи рядом с эмитентом Задержка Автоматически блокировать хранилище после %1$s секунд бездействия - Ключевые слоты - Управление списком ключей, которые могут расшифровать хранилище Импорт из файла Импорт ключей из файла Система резервного копирования Android diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index 8a11b73620..83ffc9a1ab 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -29,7 +29,6 @@ Povolením tejto možnosti zobrazíte názov účtu vedľa vydavateľa Časový limit Automaticky uzamknúť trezor po %1$s sekundách nečinnosti - Spravujte zoznam kľúčov, pomocou ktorých je možné dešifrovať trezor Importovať zo súboru Importovať tokeny zo súboru Po vykonaní zmien automaticky vytvárať zálohy trezoru na externom úložisku. Toto je podporované iba pre šifrované trezory. diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 552455c538..3d73d8a3ea 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -40,8 +40,6 @@ Aktivera denna för att visa kontonamnet bredvid utfärdaren Tidsgräns Lås valvet automatiskt efter %1$s sekunders inaktivitet - Nyckelplatser - Hantera listan över nycklar som kan dekryptera valvet Importera från fil Importera polletter från en fil Delta i Androids säkerhetskopiering diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index 6b696cc188..ee03c276d9 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -40,8 +40,6 @@ Hesap adını sağlayıcının yanında göstermek için bunu etkinleştirin Zaman aşımı Kasayı %1$s saniyelik hareketsizliğin ardından otomatik kilitle - Anahtarlık - Kasayı açabilecek anahtarların listesini yönet Dosyadan içeriye aktar Veritabanını dosyadan içeriye aktar Android yedekleme sistemine dahil ol diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index 98b9b12f8c..d87717f2ce 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -34,8 +34,6 @@ Увімкніть, щоб показувати обліковий запис поруч з емітентом Тайм-аут Автоматично блокувати сховище через %1$s секунд неактивності - Ключові слоти - Керуйте списком ключів, які можуть розшифрувати сховище Імпорт з файлу Імпортувати токени з файлу Брати участь у системі резервного копіювання Android diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml index a0211aee0f..a9bf33c485 100644 --- a/app/src/main/res/values-vi-rVN/strings.xml +++ b/app/src/main/res/values-vi-rVN/strings.xml @@ -40,8 +40,6 @@ Bật tuỳ chọn này để hiện tên tài khoản bên cạnh người cấp Thời gian chờ Tự động khoá kho sau %1$s giây không có hoạt động - Ô mã khoá - Quản lý danh sách các mã khoá có thể giải mã kho Nhập từ tệp Nhập các token từ một tệp Tham gia vào hệ thống sao lưu của Android diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 681fb50a59..3a25fab578 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -40,8 +40,6 @@ 在应用名称旁边显示账户名称 超时 在无操作 %1$s 秒后自动锁定数据库 - 键槽 - 管理能够解密数据库的密钥列表 从文件导入 从文件导入令牌 参与Android的备份系统 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 3a7dbe4f24..18f38a9cd0 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -30,8 +30,6 @@ 在服務商名稱旁邊顯示帳戶名稱 越時 在無操作 %1$s 秒後自動鎖定保險箱 - 密匙槽 - 管理能夠解密保險箱的密鑰列表 從檔案匯入 從檔案匯入憑證 使用 Android 的備份系統 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2a889bde03..be6b7cd428 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,8 +45,6 @@ Enable this to show the account name next to the issuer Timeout Automatically lock the vault after %1$s seconds of inactivity - Key slots - Manage the list of keys that can decrypt the vault Import from file Import tokens from a file Android cloud backups diff --git a/app/src/main/res/xml/preferences_security.xml b/app/src/main/res/xml/preferences_security.xml index ea98ce26d2..37e68fc0d5 100644 --- a/app/src/main/res/xml/preferences_security.xml +++ b/app/src/main/res/xml/preferences_security.xml @@ -32,12 +32,6 @@ android:summary="@string/pref_password_reminder_summary" android:dependency="pref_biometrics" app:iconSpaceReserved="false"/> - - Date: Sun, 6 Mar 2022 13:49:40 +0100 Subject: [PATCH 031/389] Remove unused string resources --- app/src/main/res/values-ar-rSA/strings.xml | 21 -------------------- app/src/main/res/values-bg-rBG/strings.xml | 21 -------------------- app/src/main/res/values-cs-rCZ/strings.xml | 21 -------------------- app/src/main/res/values-da-rDK/strings.xml | 22 --------------------- app/src/main/res/values-de-rDE/strings.xml | 22 --------------------- app/src/main/res/values-el-rGR/strings.xml | 22 --------------------- app/src/main/res/values-es-rES/strings.xml | 22 --------------------- app/src/main/res/values-eu-rES/strings.xml | 22 --------------------- app/src/main/res/values-fa-rIR/strings.xml | 21 -------------------- app/src/main/res/values-fi-rFI/strings.xml | 22 --------------------- app/src/main/res/values-fr-rFR/strings.xml | 22 --------------------- app/src/main/res/values-hi-rIN/strings.xml | 21 -------------------- app/src/main/res/values-hu-rHU/strings.xml | 21 -------------------- app/src/main/res/values-in-rID/strings.xml | 22 --------------------- app/src/main/res/values-it-rIT/strings.xml | 22 --------------------- app/src/main/res/values-ja-rJP/strings.xml | 22 --------------------- app/src/main/res/values-kn-rIN/strings.xml | 16 --------------- app/src/main/res/values-lt-rLT/strings.xml | 14 ------------- app/src/main/res/values-lv-rLV/strings.xml | 22 --------------------- app/src/main/res/values-nl-rNL/strings.xml | 22 --------------------- app/src/main/res/values-pl-rPL/strings.xml | 22 --------------------- app/src/main/res/values-pt-rBR/strings.xml | 22 --------------------- app/src/main/res/values-pt-rPT/strings.xml | 21 -------------------- app/src/main/res/values-ro-rRO/strings.xml | 22 --------------------- app/src/main/res/values-ru-rRU/strings.xml | 22 --------------------- app/src/main/res/values-sk-rSK/strings.xml | 13 ------------ app/src/main/res/values-sv-rSE/strings.xml | 12 ----------- app/src/main/res/values-tr-rTR/strings.xml | 22 --------------------- app/src/main/res/values-uk-rUA/strings.xml | 21 -------------------- app/src/main/res/values-vi-rVN/strings.xml | 22 --------------------- app/src/main/res/values-w820dp/dimens.xml | 1 - app/src/main/res/values-zh-rCN/strings.xml | 22 --------------------- app/src/main/res/values-zh-rTW/strings.xml | 21 -------------------- app/src/main/res/values/strings.xml | 23 ---------------------- 34 files changed, 686 deletions(-) diff --git a/app/src/main/res/values-ar-rSA/strings.xml b/app/src/main/res/values-ar-rSA/strings.xml index 66135394b6..76124ef3ca 100644 --- a/app/src/main/res/values-ar-rSA/strings.xml +++ b/app/src/main/res/values-ar-rSA/strings.xml @@ -2,7 +2,6 @@ الإعدادات عن - استيراد حذف نقل تعديل الأيقونة @@ -11,7 +10,6 @@ حفظ المصدِّر مقترح - التفضيلات تطبيق عناصر أندرويد @@ -31,8 +29,6 @@ عرض الكود في مجموعات من رقمين بدلًا من مجموعات من ثلاث أرقام اظهر اسم الحساب قم بالتفعيل لإظهار اسم الحساب بجانب المصدِّر - المهلة - قفل المخزن تلقائيًا بعد %1$s ثواني من عدم الاستعمال استيراد من ملف استيراد الرموز من ملف نسخ تلقائي للخزينة @@ -81,8 +77,6 @@ تحذير: إذا نسيت كلمة المرور، فلن تستطيع الوصول للرموز نهائيًا. لا توجد طريقة لاستعادتهم بدون كلمة المرور. البيومترية بالإضافة إلى كلمة المرور، يمكن استعمال البيومترية المسجلة على هذا الجهاز، مثل البصمة أو وجهك، لفك قفل المخزن - كلمة المرور - أدخل كلمة مرورك افتح قفل لمخزن افتح قفل\nالمخزن @@ -100,7 +94,6 @@ نعم لا فتح القفل - البيومترية متقدم العداد الأرقام @@ -108,14 +101,9 @@ امسح كود QR امسح الصورة أدخل يدويًا - أضف البيومترية - أضف خانة بيومترية تعيين فتح القفل البيومتري - أضف كلمة مرور - المخزن آمن فقط كأمان أضعف أسرارك. إذا قمت بتغيير إعدادات المصادقة البيومترية على جهازك، ستحتاج إلى إعادة تفعيل فقح القفل البيومتري داخل Aegis. نسخ تعديل - حذف كلمة المرور تأكيد كلمة المرور عرض كلمة المرور @@ -134,8 +122,6 @@ حذف المدخلات تجاهل التغييرات؟ لم يتم حفظ تغييراتك - مجلد - انقر للتحديد حدث خطأ أثناء حفظ الملف الشخصي مرحبًا Aegis تطبيق مصادقة ثنائية مجاني، وآمن، ومفتوح المصدر @@ -180,15 +166,9 @@ حدث خطأ أثناء محاولة قراءة كود QR خام يتم فتح قفل المخزن - يتم فتح قفل المخزن (يتم التصليح) - يجب أن يكون لديك خانة كلمة مرور واحدة على الأقل (هذه الأجهزة) - إزالة الخانة - هل ترغب حقًا في حذف هذه الخانة؟ إزالة المجموعة هل ترغب حقًا في إزالة هذه المجموعة؟ ستتحول المدخلات في هذه المجموعة تلقائيًا إلى "غير مجمّعة" - حدث خطأ أثناء محاولة إضافة خانة جديدة: - تعذّر اعادة تعيين مقياس مدة الرسوم المتحركة. أشرطة التقدم ستكون مخفية. التفاصيل قفل الاسم @@ -199,7 +179,6 @@ الحساب (ي إلى أ) مخصص مجموعة جديدة - أدخل اسم للمجموعة اسم المجموعة تعديل المجموعات أدر واحذف مجموعاتك هنا diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index d563a089d6..6d3bad57f9 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -2,7 +2,6 @@ Настройки Относно - Импортиране Изтриване Прехвърляне Редактиране на икона @@ -10,7 +9,6 @@ Отхвърляне Запазване Издател - Предпочитания Приложение Записи Андроид @@ -32,8 +30,6 @@ Показване на кода в 2-цифрено групиране вместо в 3-цифрено групиране Показване на профилното име Активирайте това, за да се показва името на акаунта до издателя - Изчакване - Автоматично заключване на трезора след %1$s секунди бездействие Импорт от файл Импортиране на токени от файл Участвайте в системата за архивиране на Android @@ -88,8 +84,6 @@ Предупреждение: Ако забравите паролата си, ще загубите трайно достъп до вашите токени. Няма начин да ги възстановите без паролата. Биометрични данни В допълнение към парола, биометрични данни, регистрирани на това устройство, като пръстов отпечатък или лицето Ви, могат да се използват за отключване на трезора. - Парола - Въведете паролата си Отключете трезора Отключете\nтрезора @@ -109,7 +103,6 @@ Да Не Отключи - Биометрични данни Разширени Брояч Цифри @@ -117,14 +110,9 @@ Сканирайте QR кода Сканирайте изображение Въведете ръчно - Добавете биометрични данни - Добавете биометричен слот Настройте биометрично отключване - Добавете парола - Трезорът е толкова сигурен, колкото най-слабата ви тайна. Ако промените настройките за биометрично удостоверяване на вашето устройство, ще трябва да активирате отново биометричното отключване в Aegis. Копирай Промени - Изтрий Парола Потвърди паролата Покажи парола @@ -146,8 +134,6 @@ Отхвърляне на промените? Промените Ви не са записани - Папка - Натиснете, за да изберете Грешка при запазването на профила Добре дошли Aegis е безплатно, сигурно и с отворен код 2FA приложение @@ -199,15 +185,9 @@ Aegis не е съвместим с частния 2FA алгоритъм на Microsoft. Моля, не забравяйте да изберете „Надстройка на приложение без известия“, когато конфигурирате 2FA в Office 365. Необработен Отключване на трезора - Отключване на трезора (ремонтиране) - Трябва да имате поне един слот за парола (това устройство) - Отстранете слота - Наистина ли искате да премахнете този слот? Премахни група Наистина ли искате да премахнете тази група? Записите в тази група автоматично ще преминат към „Без група“. - Възникна грешка при опит за добавяне на нов слот: - Не може да се нулира скалата за продължителност на аниматора. Лентите за напредък ще бъдат невидими. Подробности Заключи Име @@ -219,7 +199,6 @@ Акаунт (Я до А) По избор Нова група… - Въведете име на групата Група Име на групата Редактиране на групи diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml index c29e915ca2..65f105f49c 100644 --- a/app/src/main/res/values-cs-rCZ/strings.xml +++ b/app/src/main/res/values-cs-rCZ/strings.xml @@ -2,7 +2,6 @@ Nastavení O aplikaci - Importovat Odstranit Přenést Upravit ikonu @@ -14,7 +13,6 @@ Poskytovatel Navrhnuté Počet použití - Předvolby Aplikace Položky Android @@ -38,8 +36,6 @@ Seskupit číslice kódu po dvou číslicích namísto po třech číslicích Zobrazit název účtu Zobrazit název účtu vedle poskytovatele - Časový limit - Automaticky zamknout trezor po %1$s sekundách nečinnosti Importovat ze souboru Importovat tokeny ze souboru Účast v zálohovacím systému Androidu @@ -96,8 +92,6 @@ Upozornění: Pokud zapomenete své heslo, trvale ztratíte přístup ke svým tokenům. Bez hesla neexistuje způsob, jak je obnovit. Biometrie Kromě hesla půjde k odemčení trezoru použít také biometrické prvky registrované v tomto zařízení jako je otisk prstu nebo tvář. - Heslo - Zadejte své heslo Odemknout trezor Odemknout\ntrezor @@ -117,7 +111,6 @@ Ano Ne Odemknout - Biometrie Pokročilé Počítadlo Číslice @@ -125,14 +118,9 @@ Naskenovat QR kód Skenovat obrázek Zadat ručně - Přidat biometriku - Přidat biometrický slot Nastavení biometrického odemčení - Přidat heslo - Trezor je stejně bezpečný jako jeho nejslabší místo. Změníte-li nastavení biometrického ověření ve vašem zařízení, budete muset v Aegis znovu aktivovat biometrické odemknutí. Kopírovat Upravit - Odstranit Heslo Potvrdit heslo Zobrazit heslo @@ -157,8 +145,6 @@ Zahodit změny? Změny nebyly uloženy - Složka - Vyberte klepnutím Chyba při ukládání profilu Vítejte Aegis je bezpečná, bezplatná a open source 2FA aplikace @@ -210,15 +196,9 @@ Při pokusu o přečtení QR kódu došlo k chybě Prosté Odemykání trezoru - Odemykání trezoru (opravuji) - Je potřeba alespoň jeden slot hesla (toto zařízení) - Odstranit slot - Opravdu chcete tento slot odstranit? Odstranit skupinu Opravdu chcete odstranit tuto skupinu? Záznamy z ní budou automaticky přemístěny do „Žádná skupina“. - Při pokusu o přidání nového slotu došlo k chybě: - Měřítko trvání animace nelze resetovat. Ukazatele průběhu nebudou viditelné. Podrobnosti Uzamknout Název @@ -229,7 +209,6 @@ Účet (Z → A) Vlastní Nová skupina… - Zadejte název skupiny Název skupiny Upravit skupiny Správa a odstranění skupin diff --git a/app/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml index 64faf2eb39..b4339460d9 100644 --- a/app/src/main/res/values-da-rDK/strings.xml +++ b/app/src/main/res/values-da-rDK/strings.xml @@ -2,7 +2,6 @@ Indstillinger Om - Importér Slett Overfør Redigér ikon @@ -14,7 +13,6 @@ Udsteder Foreslået Brugstælling - Præferencer App Poster Android @@ -38,8 +36,6 @@ Vis kode i 2-cifret i stedet for 3-cifret gruppering Vis kontonavn Aktiver dette for at vise kontonavnet ved siden af udstederen - Timeout - Lås automatisk Boksen efter %1$s sekunders inaktivitet Import til fil Importér tokens fra en fil Deltag i Androids sikkerhedskopieringssystem @@ -94,8 +90,6 @@ Advarsel: Hvis du glemmer din adgangskode, vil du miste adgang til dine tokens permanent. Der er ingen mulighed for at gendanne dem uden adgangskoden. Biometri Ud over en adgangskode kan biometri, der er registreret på denne enhed såsom et fingeraftryk eller dit ansigt, bruges til at låse boksen op. - Adgangskode - Indtast din adgangskode Lås op for boksen Lås\nboksen op @@ -115,7 +109,6 @@ Ja Nej Lås op - Biometri Avanceret Tæller Cifre @@ -123,14 +116,9 @@ Scan QR-kode Scan billede Indtast manuelt - Tilføj biometri - Tilføj biometrisk plads Konfigurer biometrisk oplåsning - Tilføj adgangskode - Boksen er kun så sikker som din mindste hemmelighed. Hvis du ændre den biometriske verifikationsindstilling på din enhed, skal du genaktivere biometrisk oplåsning igen inde i Aegis. Kopier Rediger - Slet Adgangskode Bekræft adgangskode Vis adgangskode @@ -153,8 +141,6 @@ Kassér ændringer? Din ændringer er ikke blevet gemt - Mappe - Tryk for at vælge Fejl under lagring af profil Velkommen Aegis er en gratis, sikker og open source 2FA app @@ -186,7 +172,6 @@ %d ikon %d ikoner - Ikonpakke Tilpasset Tilladelse nægtet Nyt format (v0.6.3 eller nyere) @@ -215,17 +200,11 @@ Aegis er ikke kompatibel med Microsofts proprietære 2FA-algoritme. Sørg for at vælge \"Opsæt applikation uden notifikationer\", når du konfigurerer 2FA i Office 365. Raw Låser boksen op - Låser boksen op (reparation) - Du skal have mindst én adgangskodeplads (denne enhed) - Fjern plads - Er du sikker på du vil fjerne denne plads? Fjern gruppe Er du sikker på, at du vil fjerne denne gruppe? Poster i denne gruppe vil automatisk skifte til \'Ingen gruppe\'. Fjern ikonpakke Sikker på, at du vil fjerne denne ikonpakke? Poster, som bruger ikoner fra denne pakke, påvirkes ikke. - Der opstod en fejl under forsøg på at tilføje en ny plads: - Kan ikke nulstille animator varighedsskala. Fremskridtsbjælker vil være usynlige. Detaljer Lås Navn @@ -238,7 +217,6 @@ Brugstælling Tilpasset Ny gruppe… - Indtast navn på gruppe Gruppe Gruppenavn Redigér grupper diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 59f78dc310..9dc096bc8a 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -2,7 +2,6 @@ Einstellungen Über - Importieren Löschen Übertragen Symbol bearbeiten @@ -14,7 +13,6 @@ Herausgeber Empfohlen Verwendungszähler - Einstellungen App Einträge Android @@ -38,8 +36,6 @@ Code in Zweier- statt Dreiergruppen anzeigen Kontonamen anzeigen Aktiviere dies, um den Kontonamen neben dem Herausgeber anzuzeigen - Zeitüberschreitung - Sperrt die Datenbank automatisch nach %1$s Sekunden Inaktivität Aus Datei importieren Token aus einer Datei importieren Einbindung in das Sicherungssystem von Android @@ -94,8 +90,6 @@ Warnung: Wenn du dein Passwort vergisst, verlierst du dauerhaft den Zugriff auf deine Token. Es gibt keine Möglichkeit, diese ohne das Passwort wiederherzustellen. Biometrie Zusätzlich zu einem Passwort können biometrische Daten, die auf diesem Gerät registriert sind, wie ein Fingerabdruck oder dein Gesicht, verwendet werden, um die Datenbank zu entsperren. - Passwort - Gib dein Passwort ein Datenbank entsperren Datenbank\nentsperren @@ -115,7 +109,6 @@ Ja Nein Entsperren - Biometrie Fortgeschritten Zähler Ziffern @@ -123,14 +116,9 @@ QR-Code scannen Bild scannen Manuell eingeben - Biometrie hinzufügen - Biometrischen Slot hinzufügen Biometrisches Entsperren einrichten - Passwort hinzufügen - Die Datenbank ist nur so sicher wie dein schwächstes Geheimnis. Wenn du die biometrischen Authentifizierungseinstellungen deines Geräts änderst, musst du biometrisches Entsperren in Aegis reaktivieren. Kopieren Bearbeiten - Löschen Passwort Passwort bestätigen Passwort anzeigen @@ -153,8 +141,6 @@ Änderungen verwerfen? Deine Änderungen wurden noch nicht gespeichert - Ordner - Zum Auswählen tippen Fehler beim Speichern des Profils Willkommen Aegis ist eine kostenlose, sichere und quelloffene 2FA-App @@ -186,7 +172,6 @@ %d Symbol %d Symbole - Symbolpaket Benutzerdefiniert Zugriff verweigert Neues Format (v0.6.3 oder neuer) @@ -215,17 +200,11 @@ Aegis ist nicht mit dem proprietären 2FA-Algorithmus von Microsoft kompatibel. Bitte stelle sicher, dass du bei der Konfiguration von 2FA in Office 365 \"Anwendung ohne Benachrichtigungen einrichten\" auswählst. Raw Entsperren der Datenbank - Entsperren der Datenbank (reparieren) - Du musst mindestens einen Passwort-Slot haben (Dieses Gerät) - Slot entfernen - Bist du sicher, dass du diesen Slot entfernen willst? Gruppe entfernen Bist du sicher, dass du diese Gruppe entfernen möchtest? Einträge in dieser Gruppe wechseln automatisch zu \'Keine Gruppe\'. Symbolpaket entfernen Bist du sicher, dass du dieses Symbolpaket entfernen möchtest? Einträge, die Symbole aus diesem Paket verwenden, sind davon nicht betroffen. - Beim Versuch, einen neuen Slot hinzuzufügen, ist ein Fehler aufgetreten: - Die Skala der Animationsdauer kann nicht zurückgesetzt werden. Fortschrittsbalken werden unsichtbar sein. Details Sperren Name @@ -238,7 +217,6 @@ Verwendungszähler Benutzerdefiniert Neue Gruppe … - Gib einen Gruppennamen ein Gruppe Gruppenname Gruppen bearbeiten diff --git a/app/src/main/res/values-el-rGR/strings.xml b/app/src/main/res/values-el-rGR/strings.xml index 03bd26ff80..4bfa152cc8 100644 --- a/app/src/main/res/values-el-rGR/strings.xml +++ b/app/src/main/res/values-el-rGR/strings.xml @@ -2,7 +2,6 @@ Ρυθμίσεις Σχετικά - Εισαγωγή Διαγραφή Μεταφορά Επεξεργασία εικονιδίου @@ -14,7 +13,6 @@ Εκδότης Προτεινόμενο Αριθμός χρήσεων - Προτιμήσεις Εφαρμογή Καταχωρήσεις Android @@ -38,8 +36,6 @@ Εμφάνιση κωδικού σε διψήφια ομαδοποίηση αντί για τριψήφια ομαδοποίηση Εμφάνιση του ονόματος λογαριασμού Ενεργοποίηση προβολής ονόματος λογαριασμού δίπλα στον εκδότη - Χρονικό όριο - Αυτόματο κλείδωμα κρύπτης μετά από %1$s δευτερόλεπτα αδράνειας Εισαγωγή από αρχείο Εισαγωγή αναγνωριστικών από αρχείο Συμμετοχή στο σύστημα αντιγράφων ασφαλείας του Android @@ -94,8 +90,6 @@ Προειδοποίηση: Εάν ξεχάσετε τον κωδικό πρόσβασης σας, θα χάσετε μόνιμα πρόσβαση στα αναγνωριστικά σας. Δεν υπάρχει τρόπος να τα ανακτήσετε χωρίς τον κωδικό πρόσβασης. Βιομετρικά Εκτός από έναν κωδικό πρόσβασης, τα βιομετρικά στοιχεία που έχουν καταχωρηθεί σε αυτήν τη συσκευή, όπως ένα δακτυλικό αποτύπωμα ή το πρόσωπό σας, μπορούν να χρησιμοποιηθούν για να ξεκλειδώσετε την κρύπτη σας. - Κωδικός πρόσβασης - Εισάγετε τον κωδικό σας Ξεκλείδωμα της κρύπτης Ξεκλείδωμα\nτης κρύπτης @@ -115,7 +109,6 @@ Ναι Όχι Ξεκλείδωμα - Βιομετρική Σύνθετες Μετρητής Ψηφία @@ -123,14 +116,9 @@ Σάρωση κωδικού QR Σάρωση εικόνας Χειροκίνητη εισαγωγή - Προσθήκη βιομετρίας - Προσθήκη βιομετρικής υποδοχής Ρύθμιση βιομετρικού ξεκλειδώματος - Προσθήκη κωδικού πρόσβασης - Η κρύπτη σας είναι τόσο ασφαλής όσο με το πιο αδύναμο μυστικό σας. Εάν αλλάξετε τις ρυθμίσεις βιομετρικού ελέγχου ταυτότητας της συσκευής σας, θα χρειαστεί να ενεργοποιήσετε ξανά το βιομετρικό ξεκλείδωμα μέσα από το Aegis. Αντιγραφή Επεξεργασία - Διαγραφή Κωδικός πρόσβασης Επιβεβαίωση κωδικού Προβολή κωδικού @@ -153,8 +141,6 @@ Απόρριψη αλλαγών; Οι αλλαγές σας δεν έχουν αποθηκευτεί - Φάκελος - Πατήστε για να επιλέξετε Σφάλμα κατά την αποθήκευση προφίλ Καλώς ήρθατε Το Aegis είναι μια δωρεάν, ασφαλής και ανοικτού κώδικα εφαρμογή 2FA @@ -186,7 +172,6 @@ %d εικονίδιο %d εικονίδια - Πακέτο εικονιδίων Προσαρμοσμένο Η άδεια απορρίφθηκε Νέα μορφή (v0.6.3 ή νεότερη) @@ -215,17 +200,11 @@ Το Aegis δεν είναι συμβατό με τον ιδιόκτητο αλγόριθμο 2FA της Microsoft. Βεβαιωθείτε να επιλέξετε \"Ρύθμιση εφαρμογής χωρίς ειδοποιήσεις\" κατά τη διαμόρφωση του 2FA στο Office 365. Ακατέργαστη Ξεκλείδωμα κρύπτης - Ξεκλείδωμα κρύπτης (επισκευή) - Πρέπει να έχετε τουλάχιστον μία υποδοχή κωδικού (αυτή η συσκευή) - Κατάργηση υποδοχής - Είστε βέβαιοι ότι θέλετε να καταργήσετε αυτή την υποδοχή; Κατάργηση ομάδας Είστε βέβαιοι ότι θέλετε να καταργήσετε αυτήν την ομάδα; Οι καταχωρίσεις σε αυτήν θα μεταβούν αυτόματα στο \"Χωρίς ομάδα\". Αφαίρεση πακέτου εικονιδίων Είστε βέβαιοι ότι θέλετε να καταργήσετε αυτό το πακέτο εικονιδίων? Καταχωρήσεις που χρησιμοποιούν εικονίδια από αυτό το πακέτο δεν θα επηρεαστούν. - Παρουσιάστηκε σφάλμα κατά την προσπάθεια προσθήκης νέας υποδοχής: - Δεν είναι δυνατή η επαναφορά της κλίμακας. Οι γραμμές προόδου θα είναι αόρατες. Λεπτομέρειες Κλείδωμα Όνομα @@ -238,7 +217,6 @@ Αριθμός χρήσεων Προσαρμογή Νέα ομάδα… - Εισαγωγή ονόματος ομάδας Ομάδα Όνομα ομάδας Επεξεργασία ομάδων diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 6be057d06f..5c90f17831 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -2,7 +2,6 @@ Configuración Acerca de - Importar Eliminar Transferir Editar icono @@ -14,7 +13,6 @@ Emisor Suguerido Contador de uso - Preferencias App Entradas Android @@ -38,8 +36,6 @@ Muestra los códigos en agrupaciones de 2 dígitos en lugar de 3 dígitos Mostrar el nombre de la cuenta Active esto para mostrar el nombre de la cuenta junto al emisor - Tiempo de espera - Bloquear automáticamente la bóveda después de %1$s segundos de inactividad Importar desde un archivo Importar tokens desde un archivo Participar en el sistema de copia de seguridad de Android @@ -94,8 +90,6 @@ Advertencia: Si olvida su contraseña, perderá permanentemente el acceso a sus tokens. No hay forma de recuperarlos sin la contraseña. Biometría Además de una contraseña, la biometría registrada en este dispositivo, como una huella digital o su cara, pueden ser usados para desbloquear la bóveda. - Contraseña - Introduzca su contraseña Desbloquear la bóveda Desbloquear\nla bóveda @@ -115,7 +109,6 @@ Si No Desbloquear - Biometría Avanzado Contador Dígitos @@ -123,14 +116,9 @@ Escanear código QR Escanear imagen Introducir manualmente - Añadir biometría - Añadir clave biométrica Configurar desbloqueo biométrico - Añadir contraseña - La bóveda es tan segura como su código secreto más débil. Si cambia la configuración de autenticación biométrica de su dispositivo, tendrá que reactivar el desbloqueo biométrico dentro de Aegis. Copiar Editar - Eliminar Contraseña Confirmar contraseña Mostrar contraseña @@ -153,8 +141,6 @@ ¿Descartar cambios? Sus cambios no se han guardado - Carpeta - Pulsar para seleccionar Error al guardar el perfil Bienvenido Aegis es una aplicación 2FA gratuita, segura y de código abierto @@ -186,7 +172,6 @@ %d icono %d iconos - Paquete de iconos Personalizado Permiso denegado Formato nuevo (v0.6.3 o superior) @@ -215,17 +200,11 @@ Aegis no es compatible con el algoritmo 2FA propietario de Microsoft. Por favor, asegúrese de seleccionar \"Configurar aplicación sin notificaciones\" al configurar 2FA en Office 365. Raw Desbloqueando la bóveda - Desbloqueando la bóveda (reparando) - Debe tener al menos una contraseña (este dispositivo) - Eliminar clave - ¿Está seguro de que desea eliminar esta clave? Eliminar grupo ¿Está seguro de que desea eliminar este grupo? Las entradas se moverán automáticamente a \'Sin grupo\'. Eliminar paquete de iconos ¿Está seguro de que desea eliminar este paquete de iconos? Las entradas que usen iconos de este paquete no se verán afectadas. - Se ha producido un error tratando de añadir una nueva clave: - No se puede restablecer la escala de duración de la animación. Las barras de progreso serán invisibles. Detalles Bloquear Cuenta @@ -238,7 +217,6 @@ Contador de uso Personalizado Nuevo grupo… - Introduzca un nombre de grupo Grupo Nombre del grupo Editar grupos diff --git a/app/src/main/res/values-eu-rES/strings.xml b/app/src/main/res/values-eu-rES/strings.xml index d3175e0721..8af7a6b194 100644 --- a/app/src/main/res/values-eu-rES/strings.xml +++ b/app/src/main/res/values-eu-rES/strings.xml @@ -2,7 +2,6 @@ Ezarpenak Honi buruz - Inportatu Ezabatu Transferitu Editatu abiarazlea @@ -14,7 +13,6 @@ Igorlea Iradokitakoak Erabilera kopurua - Hobespenak Aplikazioa Sarrerak Android @@ -38,8 +36,6 @@ Erakutsi kode-digituak binaka taldekatuta hirunaka taldekatuta erakutsi ordez Erakutsi kontuaren izena Aktibatu hau kontu-izena igorlearen ondoan bistaratzeko - Denbora-muga - Blokeatu automatikoki biltegia %1$s segundo jarduerarik gabe egon ondoren Inportatu fitxategi batetik Inportatu marka-ikurrak fitxategi batetik Androiden segurtasun kopien sisteman parte hartu @@ -94,8 +90,6 @@ Adi: pasahitza ahazten baduzu, zure tokenen sarbidea galduko duzu betirako. Ezin dira berreskuratu pasahitzik gabe. Biometriak Pasahitzaz gain, gailu honetan erregistratutako biometriak, hala nola hatz-marka edo zure aurpegia, biltegia desblokeatzeko erabil daitezke. - Pasahitza - Sartu zure pasahitza Desblokeatu biltegia Desblokeatu\nbiltegia @@ -115,7 +109,6 @@ Bai Ez Desblokeatu - Biometriak Aurreratua Kontagailua Digituak @@ -123,14 +116,9 @@ Eskaneatu QR kodea Eskaneatu irudia Sartu eskuz - Gehitu biometria - Gehitu arteka biometrikoa Desblokeatze biometrikoa konfiguratu - Gehitu pasahitza - Biltegia bere sekreturik ahulena bezain segurua da. Zure gailuaren autentifikazio biometrikoaren konfigurazioa aldatzen baduzu, desblokeo biometrikoa berriro aktibatu beharko duzu Aegisen barruan. Kopiatu Editatu - Ezabatu Pasahitza Berretsi pasahitza Erakutsi pasahitza @@ -153,8 +141,6 @@ Baztertu aldaketak? Zure aldaketak ez dira gorde - Direktorioa - Ukitu aukeratzeko Errorea profila gordetzerakoan Ongi Etorri Aegis doakoa, segurua eta software librea den 2FA aplikazioa da @@ -186,7 +172,6 @@ Ikono %d %d ikono - Ikono multzoa Pertsonalizatua Baimena ukatuta Formatu berria (v0.6.3 edo berriagoa) @@ -215,17 +200,11 @@ Aegis ez da bateragarria Microsoften 2FA algoritmo jabedunarekin. Office 365en 2FA konfiguratzen duzunean aukeratu \"Aplikazioa jakinarazpen gabe konfiguratu\" aukeratu ezazu (ingelesez: \"Setup application without notifications\"). Gordina Biltegia desblokeatzen - Biltegia desblokeatzen (konpontzen) - Arteka-pasahitz bat behintzat izan behar duzu (gailu hau) - Ezabatu arteka - Ziur zaude arteka hau ezabatu nahi duzula? Ezabatu taldea Ziur zaude talde hau ezabatu nahi duzula? Talde honetako sarrerak automatikoki aldatuko dira \'Talderik ez\' aukerara. Ezabatu ikono multzoa Ziur zaude ikono multzo hau ezabatzeaz? Ikono multzo honetako ikonoak darabiltzaten sarreretan ez da aldaketarik egongo. - Errorea gertatu da arteka berria gehitzen saiatzean: - Ezin da animazioaren iraupenaren eskala berrezarri. Aurrerapen-barrak ikusezinak izango dira. Xehetasunak Blokeatu Izena @@ -238,7 +217,6 @@ Erabilera kopurua Pertsonalizatua Talde berria… - Sartu talde izena Taldea Taldearen izena Editatu taldeak diff --git a/app/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml index 5b4ae1c193..3dbebe979b 100644 --- a/app/src/main/res/values-fa-rIR/strings.xml +++ b/app/src/main/res/values-fa-rIR/strings.xml @@ -2,7 +2,6 @@ تنظیمات درباره‌ - وارد کردن حذف انتقال ویرایش ایکون @@ -10,7 +9,6 @@ لغو ذخیره صادر کننده - تنظیمات نرم افزار مطالب اندروید @@ -32,8 +30,6 @@ جدا کردن رقم ها به صورت دو به دو، بجای جدا کردن به صورت سه به سه نمایش نام حساب کاربری برای نمایش نام حساب کاربری کنار صادر کننده فعال کنید - اتمام مهلت - بعد از %1$s ثانیه عدم فعالیت مخزن را قفل کن وارد کردن از فایل وارد کردن توکن ها از فایل شرکت کردن در سیستم پشتیبانگیری اندروید @@ -87,8 +83,6 @@ هشدار: پس از فراموشی کلمه عبور هیچ راهی برای بازگرداندن توکن ها نیست. بیومتریک علاوه بر کلمه عبور، یک روش احراز هویت بیومتریک مانند اثر انگشت یا تشخیص چهره تعریف شده است که میتوان برای بازگشایی مخزن استفاده کرد. - کلمه عبور - کلمه عبور خود را وارد کنید بازکردن مخزن باز کردن\nمخزن @@ -107,7 +101,6 @@ بله خیر بازکردن - بیومتریک پیشرفته شمارنده ارقام @@ -115,14 +108,9 @@ اسکن بارکد دوبعدی اسکن تصویر وارد کردن دستی - افزودن احراز هویت بیومتریک - افزودن احراز هویت بیومتریک تعریف رمزگذاری بیومتریک - افزودن کلمه عبور - مخزن به اندازه ضعیف ترین رمز شما ایمن است. اگر تغییری در تنظیمات بیومتریک انجام داده اید، لطفا برای فعال سازی مجدد به تنظیمات برنامه مراجعه کنید. کپی ویرایش - حذف کلمه عبور تایید کلمه عبور نمایش کلمه عبور @@ -143,8 +131,6 @@ لغو تغییرات؟ تغییرات اعمال شده ذخیره نشد - پوشه - برای انتخاب لمس کنید خطا در ذخیره حساب کاربری خوش آمدید Aegis یک نرم افزار امن، رایگان و متن باز برای ورود دو مرحله ای می‌باشد @@ -196,15 +182,9 @@ آگیس با الگوریتم اختصاصی 2FA مایکروسافت سازگاری ندارد. لطفا اطمینان حاصل فرمایید که گزینه \"تنظیم برنامه بدون اعلانات\" در تنظیمات 2FA آفیس 365 انتخاب شده باشد. خام رمزگشایی مزخن - رمزگشایی مخزن (در حال تعمیر) - شما حداقل باید یک محل کلمه عبور داشته باشید. (این دستگاه) - حذف اسلات - آیا از حذف این اسلات اطمینان دارید؟ حذف گروه آیا مطمئن به حذف این گروه هستید؟ تمام آیتم های داخل این گروه تبدیل به \"بدون گروه\" می‌شوند. - هنگام ایجاد اسلات خطایی رخ داد: - عدم توانایی در تنظیم انیمیشن. نمایش درصد تکمیل مخفی می‌شود. جزئیات قفل نام @@ -215,7 +195,6 @@ نام حساب (Z - A) سفارشی گروه جدید… - نام گروه را وارد کنید گروه نام گروه ویرایش گروه diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml index a25eb73ae4..83cb1479f4 100644 --- a/app/src/main/res/values-fi-rFI/strings.xml +++ b/app/src/main/res/values-fi-rFI/strings.xml @@ -2,7 +2,6 @@ Asetukset Tietoja - Tuo Poista Siirrä Muokkaa kuvaketta @@ -14,7 +13,6 @@ Myöntäjä Suositellut Käyttömäärä - Asetukset Sovellus Kohteet Android @@ -37,8 +35,6 @@ Näytä koodi kahden numeron ryhmissä, kolminumeroisen ryhmittelyn sijasta Näytä tilin nimi Ota käyttöön nähdäksesi tilin nimen sen myöntäjän rinnalla - Aikakatkaisu - Lukitse holvi automaattisesti %1$s sekunnin käyttämättömyyden jälkeen Tuo tiedostosta Tuo tunnukset tiedostosta Käytä Androidin omaa varmuuskopiointijärjestelmää @@ -93,8 +89,6 @@ Varoitus: Jos unohdat salasanasi, menetät pääsyn tunnuksiisi pysyvästi. Niitä ei voida palauttaa ilman salasanaa. Biometriikka Holvin voi avata sekä salasanalla että laitteeseen tallennettujen biometristen tietojen, kuten sormenjälkesi tai kasvojesi avulla. - Salasana - Syötä salasanasi Avaa holvi Avaa\nholvi @@ -113,7 +107,6 @@ Kyllä Ei Avaa lukitus - Biometriikka Edistyneet Laskuri Numeroa @@ -121,14 +114,9 @@ Skannaa QR-koodi Skannaa kuva Syötä manuaalisesti - Lisää biometriikka - Lisää biometrinen paikka Määritä biometrinen avaaminen - Lisää salasana - Holvi on vain yhtä turvallinen kuin heikoin salaisuutesi. Jos muutat laitteen biometrisen todennuksen asetuksia, sinun on aktivoitava biometrinen avaaminen uudelleen Aegisissa. Kopioi Muokkaa - Poista Salasana Vahvista salasana Näytä salasana @@ -151,8 +139,6 @@ Hylätäänkö muutokset? Muutoksiasi ei ole tallennettu - Kansio - Napauta valitaksesi Virhe profiilin tallennuksessa Tervetuloa Aegis on ilmainen, turvallinen ja avoimen lähdekoodin 2FA-sovellus @@ -180,7 +166,6 @@ %d kuvake %d kuvaketta - Kuvakepaketti Mukautettu Lupa evätty Uusi tallennusmuoto (v0.6.3 tai uudempi) @@ -209,16 +194,10 @@ Aegis ei ole yhteensopiva Microsoftin oman 2FA-algoritmin kanssa. Varmista, että valitset \"Määritä sovellus ilman ilmoituksia\", kun määrität Office 365:n kaksivaiheista tunnistautumista. Raw Holvia avataan - Holvia avataan (korjataan) - Sinulla pitää olla ainakin yksi salasanapaikka (tämä laite) - Poista paikka - Haluatko varmasti poistaa tämän paikan? Poista ryhmä Haluatko varmasti poistaa tämän ryhmän? Tässä ryhmässä olevat kohteet merkitään automaattisesti ryhmättömiksi. Poista kuvakepaketti - Uuden paikan lisäämisessä tapahtui virhe: - Animaattorin kestoasteikkoa ei voitu nollata. Edistymispalkit ovat näkymättömiä. Tiedot Lukitse Nimi @@ -231,7 +210,6 @@ Käyttömäärä Mukautettu Uusi ryhmä… - Syötä ryhmän nimi Ryhmä Ryhmän nimi Muokkaa ryhmiä diff --git a/app/src/main/res/values-fr-rFR/strings.xml b/app/src/main/res/values-fr-rFR/strings.xml index 1b337f3f09..8434dbbc2c 100644 --- a/app/src/main/res/values-fr-rFR/strings.xml +++ b/app/src/main/res/values-fr-rFR/strings.xml @@ -2,7 +2,6 @@ Paramètres À propos - Importer Supprimer Transfert Modifier l\'icône @@ -14,7 +13,6 @@ Émetteur Suggéré Nombre d\'utilisations - Préférences Application Entrées Android @@ -38,8 +36,6 @@ Afficher le code avec un groupement de 2 chiffres au lieu de 3 Afficher le nom du compte Activer pour afficher le nom de compte près de l\'émetteur - Délai - Verrouiller automatiquement le coffre-fort après %1$s secondes d\'inactivité Importer depuis un fichier Importer des jetons depuis un fichier Participer au système de sauvegarde d\'Android @@ -94,8 +90,6 @@ Attention : Si vous oubliez votre mot de passe, vous perdrez définitivement l\'accès à vos jetons. Il n\'y a aucun moyen de les récupérer sans le mot de passe. Biométrie En plus d\'un mot de passe, les données biométriques enregistrées sur cet appareil, comme une empreinte digitale ou votre visage, peuvent être utilisées pour déverrouiller le coffre-fort. - Mot de passe - Saisissez votre mot de passe Déverrouiller le coffre-fort Déverrouiller\nle coffre-fort @@ -115,7 +109,6 @@ Oui Non Déverrouiller - Biométrie Avancé Compteur Chiffres @@ -123,14 +116,9 @@ Scanner code QR Scanner image Saisir manuellement - Ajouter données biométriques - Ajouter un emplacement de données biométriques Configurer le déverrouillage biométrique - Ajouter un mot de passe - Le coffre-fort est aussi sécurisé que votre secret le plus faible. Si vous modifiez les paramètres d\'authentification biométrique de votre appareil, vous devrez réactiver le déverrouillage biométrique dans Aegis. Copier Modifier - Supprimer Mot de passe Confirmer mot de passe Afficher le mot de passe @@ -153,8 +141,6 @@ Annuler les modifications ? Vos modifications n\'ont pas été enregistrées - Dossier - Appuyer pour sélectionner Erreur lors de l\'enregistrement du profil Bienvenue Aegis est une application d\'A2F gratuite, sécurisée et open source @@ -186,7 +172,6 @@ %d icône %d icônes - Pack d\'icônes Personnalisé Autorisation refusée Nouveau format (v0.6.3 ou plus récent) @@ -215,17 +200,11 @@ Aegis n\'est pas compatible avec l\'algorithme A2F propriétaire de Microsoft. Veuillez vous assurer de sélectionner \"Configurer l\'application sans notification\" lors de la configuration de la 2FA dans Office 365. Brut Déverrouillage du coffre-fort - Déverrouillage du coffre-fort (réparation) - Vous devez avoir au moins un emplacement de mot de passe (ces appareils) - Supprimer emplacement - Êtes-vous sûr de vouloir supprimer cet emplacement ? Supprimer groupe Êtes-vous sûr de vouloir supprimer ce groupe ? Les entrées dans ce groupe seront basculées automatiquement dans \"Aucun groupe\". Supprimer le pack d\'icônes Êtes-vous sûr de vouloir supprimer ce pack d\'icônes ? Les entrées qui utilisent les icônes de ce pack ne seront pas affectées. - Une erreur est survenue en essayant d\'ajouter un nouvel emplacement : - Impossible de réinitialiser l\'échelle de durée d\'animation. Les barres de progression seront invisibles. Détails Verrouiller Nom @@ -238,7 +217,6 @@ Nombre d\'utilisations Personnalisé Nouveau groupe… - Saisissez un nom de groupe Groupe Nom de groupe Modifier les groupes diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index fecbb85ef1..d46b5e617c 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -2,7 +2,6 @@ सेटिंग हमारे बारे में - आयात हटाएँ स्थानांतरण आइकन संपादित करें @@ -10,7 +9,6 @@ रद्द करें सहेजें ज़ारीकर्ता - वरीयताएँ थीम देखने का प्रकार भाषा @@ -18,8 +16,6 @@ कोड को 3-संख्या समूह की जगह 2-संख्या समूह में दिखाएँ खाते का नाम दिखाएँ जारीकर्ता के बगल में खाता नाम दिखाने के लिए इसे सक्षम करें - समय सीमा समाप्ति - निष्क्रियता के %1$s सेकंड के बाद वॉल्ट को स्वचालित रूप से लॉक करें फ़ाइल से आयात करें टोकन को फ़ाइल से आयात करें बदलाव होने पर एक्सटर्नल स्टोरेज में स्वतः ही वॉल्ट का बैकअप बनाएँ। यह सिर्फ एन्क्रिप्टेड वॉल्ट को सपोर्ट करता है। @@ -63,8 +59,6 @@ चेतावनी: यदि आप अपना पासवर्ड भूल जाते हैं, तो आप स्थायी रूप से अपने टोकनों तक पहुंच खो देंगे। पासवर्ड के बिना उन्हें पुनर्प्राप्त करने का कोई तरीका नहीं है। बॉयोमेट्रिक्स एक पासवर्ड के अलावा, इस डिवाइस पर पंजीकृत बायोमेट्रिक्स, एक फिंगरप्रिंट या आपके चेहरे को वॉल्ट को अनलॉक करने के लिए इस्तेमाल किया जा सकता है। - पासवर्ड - आपका पासवर्ड डालें वॉल्ट अनलॉक करें वॉल्ट\nअनलॉक करें @@ -79,7 +73,6 @@ हां नहीं अनलॉक - बॉयोमेट्रिक्स उन्नत काउंटर अंक @@ -87,14 +80,9 @@ क्यूआर कोड स्कैन करें छवि स्कैन करें हाथों से दर्ज़ करें - बॉयोमेट्रिक जोड़ें - बॉयोमेट्रिक स्लॉट जोड़ें बॉयोमेट्रिक अनलॉक सेट करें - पासवर्ड जोड़ें - वॉल्ट केवल आपके सबसे कमजोर रहस्य की तरह ही सुरक्षित है। यदि आप अपने डिवाइस की बायोमेट्रिक प्रमाणीकरण सेटिंग्स बदलते हैं, तो आपको Aegis के भीतर बायोमेट्रिक अनलॉक को फिर से सक्रिय करना होगा। कॉपी करें संपादित करें - हटाएँ पासवर्ड पासवर्ड की पुष्टि करें पासवर्ड दिखाएँ @@ -114,8 +102,6 @@ परिवर्तन निरस्त करे? आपके परिवर्तन सहेजे नहीं गए हैं - फोल्डर - चयन करने के लिए टैप करें प्रोफाइल सहेजने में त्रुटि स्वागत है Aegis एक मुफ्त, सुरक्षित और ओपन सोर्स 2FA ऐप है @@ -163,15 +149,9 @@ क्यूआर कोड पढ़ने की कोशिश करते समय एक त्रुटि हुई रॉ वॉल्ट अनलॉक हो रहा है - वॉल्ट अनलॉक हो रहा है (मरम्मत हो रही) - आपके पास कम से कम एक पासवर्ड स्लॉट होना चाहिए (यह डिवाइस) - स्लॉट हटाये - क्या आप वाकई इस स्लॉट को हटाना चाहते हैं? समूह हटाये क्या आप वाकई इस समूह को हटाना चाहते हैं? इस समूह में प्रविष्टियाँ स्वतः ही \'नो ग्रुप\' में बदल जाएँगी। - नया स्लॉट जोड़ने का प्रयास करते समय एक त्रुटि हुई: - एनिमेटर अवधि पैमाने को रीसेट करने में असमर्थ। प्रगति पट्टी अदृश्य हो जाएगी। विवरण लॉक करें नाम @@ -182,7 +162,6 @@ खाता (Z से A) कस्टम नया समूह... - समूह का नाम दर्ज़ करें समूह का नाम समूहों को संपादित करें यहां अपने समूहों को प्रबंधित करें और हटाएँ diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml index 81cf8c34e5..7a727b8cd8 100644 --- a/app/src/main/res/values-hu-rHU/strings.xml +++ b/app/src/main/res/values-hu-rHU/strings.xml @@ -2,13 +2,11 @@ Beállítások Névjegy - Importálás Törlés Alapértelmezett ikon visszaállítása Elvetés Mentés Kibocsátó - Beállítások Téma Nézetmód Nyelv @@ -16,8 +14,6 @@ Kódok megjelenítése két számjegyes csoportosításban a három számjegyes helyett A fióknév megjelenítése Kapcsolja be a fiók nevének megjelenítéséhez a kibocsátó mellett - Időtúllépés - A széf automatikus zárolása %1$s másodperc tétlenség után Importálás fájlból Tokenek importálása fájlból Biztonsági mentést készít a széfről a külső tárolón. Ez csak titkosított széfek esetén támogatott. @@ -51,8 +47,6 @@ Figyelmeztetés: Ha elfelejti a jelszavát, akkor végleg elveszti a hozzáférését a tokenekhez. Jelszó nélkül sehogy sem fogja tudni visszaállítani azokat. Biometrikus A jelszó mellett a biometrikus adatai is regisztrálva lesznek az eszközön, mint például az ujjlenyomata vagy az arca, melyet a széf feloldásához használhat. - Jelszó - Adja meg a jelszavát A széf feloldása Adja meg a jelszavát @@ -64,7 +58,6 @@ Igen Nem Feloldás - Biometrikus adatok Speciális Számláló Számjegy @@ -72,14 +65,9 @@ QR-kód leolvasása Kép beolvasása Kézi megadás - Biometrikus adatok hozzáadása - Biometrikus zár hozzáadása Feloldás beállítása biometrikus adatokkal - Jelszó hozzáadása - A zár olyan biztonságos, mint a leggyengébb titok. Ha módosítja az eszköze biometrikus hitelesítési beállításait, akkor újra kell aktiválnia a biometrikus feloldást az Aegisben. Másolás Szerkesztés - Törlés Jelszó Jelszó megerősítése Jelszó megjelenítése @@ -95,8 +83,6 @@ Bejegyzések törlése Elveti a változásokat? A változásai nem lettek mentve - Mappa - Koppintson a kiválasztáshoz Hiba a profil mentésekor Üdvözöljük Az Aegis egy szabad, biztonságos és nyílt forráskódú két faktoros hitelesítő alkalmazás @@ -133,14 +119,8 @@ Hiba történt a QR-kód leolvasásakor Nyers A széf feloldása - A széf feloldása (javítás) - Legalább egy jelszavas zárnak kell lennie - Zár eltávolítása - Biztos, hogy eltávolítja ezt a zárat? Csoport eltávolítása Biztos, hogy eltávolítja ezt a csoportot? A csoport bejegyzései automatikusan „Csoport nélküli” elemek lesznek. - Hiba történt az új zár hozzáadásakor: - Az animátor hossza nem állítható vissza. A folyamatsávok nem fognak látszódni. Részletek Zárolás Név @@ -151,7 +131,6 @@ Fiók (Z-től A-ig) Egyéni Új csoport… - Adja meg a csoport nevét Csoportnév Csoportok szerkesztése Itt kezelheti és törölheti a csoportokat diff --git a/app/src/main/res/values-in-rID/strings.xml b/app/src/main/res/values-in-rID/strings.xml index 21a279509f..01a398ba3b 100644 --- a/app/src/main/res/values-in-rID/strings.xml +++ b/app/src/main/res/values-in-rID/strings.xml @@ -2,7 +2,6 @@ Pengaturan Tentang - Impor Hapus Kirim Mengedit ikon @@ -14,7 +13,6 @@ Penerbit Disarankan Jumlah penggunaan - Preferensi Aplikasi Entri Android @@ -38,8 +36,6 @@ Tampilkan kode dalam pengelompokan 2 digit, bukan pengelompokan 3 digit Tampilkan nama akun Aktifkan ini untuk menampilkan nama akun di samping penerbit - Waktu habis - Kunci brankas secara otomatis setelah %1$s detik tidak aktif Impor berkas Impor token dari berkas Partisipasi di sistem pencadangan Android @@ -93,8 +89,6 @@ Peringatan: Jika Anda lupa kata sandi, Anda akan kehilangan akses ke token Anda secara permanen. Tidak ada cara untuk memulihkan nya tanpa kata sandi. Biometrik Selain kata sandi, biometrik yang terdaftar di perangkat ini, seperti sidik jari atau wajah Anda, dapat digunakan untuk membuka brankas. - Kata Sandi - Masukkan kata sandi Anda Buka brankas Buka\nbrankas @@ -114,7 +108,6 @@ Ya Tidak Buka - Biometrik Lanjutan Penghitung Digit @@ -122,14 +115,9 @@ Pindai kode QR Pindai gambar Masukkan secara manual - Tambahkan biometrik - Tambahkan slot biometrik Atur buka kunci biometrik - Tambahkan kata sandi - Brankas ini hanya mengamankan sebagian rahasia Anda. Jika Anda mengubah pengaturan otentikasi biometrik perangkat Anda, Anda perlu mengaktifkan kembali buka kunci biometrik di dalam Aegis. Salin Ubah - Hapus Kata Sandi Konfirmasi Kata Sandi Tampilkan Kata Sandi @@ -151,8 +139,6 @@ Batalkan perubahan? Perubahan Anda belum disimpan - Direktori - Sentuh untuk memilih Terjadi kesalahan saat menyimpan profil Selamat Datang Aegis adalah aplikasi 2FA gratis, aman, dan kode sumber terbuka @@ -183,7 +169,6 @@ %d ikon - Paket Ikon Kustom Izin ditolak Format baru (v0.6.3 atau terbaru) @@ -211,17 +196,11 @@ Aegis tidak sesuai dengan algoritma 2FA milik Microsoft. Tolong untuk memastikan telah memilih \"Atur aplikasi tanpa notifikasi\" ketika mengonfigurasi 2FA di Office 365. Raw Buka brankas - Membuka brankas (memperbaiki) - Setidaknya Anda harus memiliki satu slot kata sandi (perangkat ini) - Hapus slot - Apakah Anda yakin ingin menghapus slot ini? Hapus kelompok Apakah Anda yakin ingin menghapus kelompok ini? Catatan dalam kelompok ini akan secara otomatis beralih ke \'Tidak ada kelompok\'. Hapus Paket Ikon Apakah Anda yakin ingin menghapus paket ikon ini? Entri yang menggunakan ikon dari paket ini tidak akan terpengaruh. - Terjadi kesalahan saat menambahkan slot baru: - Tidak dapat mengatur ulang skala durasi animasi. Progress bar tidak akan terlihat. Rincian Kunci Nama @@ -234,7 +213,6 @@ Jumlah penggunaan Kustom Kelompok baru… - Masukkan nama kelompok Kelompok Nama kelompok Ubah kelompok diff --git a/app/src/main/res/values-it-rIT/strings.xml b/app/src/main/res/values-it-rIT/strings.xml index c6181747bb..22976849eb 100644 --- a/app/src/main/res/values-it-rIT/strings.xml +++ b/app/src/main/res/values-it-rIT/strings.xml @@ -2,7 +2,6 @@ Impostazioni Informazioni - Importa Elimina Trasferisci Modifica icona @@ -14,7 +13,6 @@ Emittente Suggeriti Numero di utilizzi - Impostazioni App Voci Android @@ -38,8 +36,6 @@ Mostra il codice diviso ogni 2 cifre invece che ogni 3 cifre Mostra nome account Abilita questa opzione per mostrare il nome dell\'account vicino all\'emittente - Timeout - Blocca automaticamente la cassaforte dopo %1$s secondi di inattività Importa da file Importa token da file Partecipa al sistema di backup di Android @@ -94,8 +90,6 @@ Attenzione: Se dimentichi la password perderai definitivamente l\'accesso ai token e non avrai modo di recuperarli. Biometrica Oltre a una password, i dati biometrici registrati su questo dispositivo, come un\'impronta digitale o il tuo volto, possono essere utilizzati per sbloccare la cassaforte. - Password - Inserisci la tua password Sblocca la cassaforte Sblocca\nla cassaforte @@ -115,7 +109,6 @@ Si No Sblocca - Biometrica Avanzate Contatore Cifre @@ -123,14 +116,9 @@ Scansiona codice QR Scansiona immagine Inserisci manualmente - Aggiungi dati biometrici - Aggiungi dati biometrici Imposta sblocco biometrico - Aggiungi password - La cassaforte è sicura quanto il tuo segreto più debole. Se cambi le impostazioni di autenticazione biometrica del tuo dispositivo, dovrai riattivare lo sblocco biometrico all\'interno di Aegis. Copia Modifica - Elimina Password Conferma password Mostra password @@ -153,8 +141,6 @@ Annullare le modifiche? Le modifiche non sono state salvate - Cartella - Tocca per selezionare Errore nel salvataggio del profilo Benvenuto Aegis è un\'app per l\'autenticazione a due fattori gratuita, sicura e open source @@ -186,7 +172,6 @@ %d icona %d icone - Pacchetto di icone Personalizzato Autorizzazione negata Nuovo formato (v0.6.3 o più recente) @@ -215,17 +200,11 @@ Aegis non è compatibile con l\'algoritmo di autenticazione a due fattori proprietario di Microsoft. Assicurati di selezionare \"Configura un\'applicazione senza notifiche\" quando configuri l\'autenticazione a due fattori in Office 365. Raw Sblocco della cassaforte - Sblocco della cassaforte (riparazione) - Devi avere almeno uno slot password (questo dispositivo) - Rimuovi slot - Sei sicuro di voler rimuovere questo slot? Rimuovi gruppo Sei sicuro di voler rimuovere questo gruppo? Gli elementi in questo gruppo verranno automaticamente spostati in \'Nessun gruppo\'. Rimuovi pacchetto di icone Sei sicuro di voler rimuovere questo pacchetto di icone? Le voci che usano le icone di questo pacchetto non saranno modificate. - Errore nell\'aggiunta di un nuovo slot: - Impossibile ripristinare la durata delle animazioni. Le barre di caricamento non saranno visibili. Dettagli Blocca Nome @@ -238,7 +217,6 @@ Numero di utilizzi Personalizza Nuovo gruppo… - Inserisci un nome di gruppo Gruppo Nome gruppo Modifica gruppi diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index b38d2f6231..3de4fa6f92 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -2,7 +2,6 @@ 設定 このプログラムについて - インポート 削除 転送 アイコンを編集 @@ -14,7 +13,6 @@ 発行者 提案 使用回数 - 設定 アプリ エントリー Android @@ -38,8 +36,6 @@ コードを3桁ずつ表示する代わりに2桁ずつ表示 アカウント名を表示 有効にすると発行者の隣にアカウント名を表示します - タイムアウト - 操作を%1$s秒間行わない場合、自動的にロックします ファイルからインポート ファイルからトークンをインポート Androidのバックアップシステムに含める @@ -93,8 +89,6 @@ 警告: パスワードを忘れた場合、トークンへのアクセスが永久に失われます。パスワードなしで復元する方法はありません。 生体認証 パスワードに加えて、指紋や顔など、このデバイスに登録された生体認証を保管庫のロックの解除に使用することができます。 - パスワード - パスワードを入力してください 保管庫のロックを解除 ロックを\n解除 @@ -114,7 +108,6 @@ はい いいえ ロック解除 - 生体認証 高度な設定 カウンター 桁数 @@ -122,14 +115,9 @@ QRコードをスキャン 画像をスキャン 手動で入力 - 生体認証を追加 - 生体認証スロットを追加 生体認証ロック解除の設定 - パスワードを追加する - 保管庫は、最も繊細な秘密情報です。デバイスの生体認証設定を変更した場合、Aegis内で生体認証ロック解除を再設定する必要があります。 コピー 編集 - 削除 パスワード パスワードの確認 パスワードを表示 @@ -151,8 +139,6 @@ 変更を破棄しますか? 変更は保存されていません - フォルダー - タップして選択 プロファイルの保存中にエラーが発生しました ようこそ Aegisは、無料で安全なオープンソースの2FAアプリです @@ -183,7 +169,6 @@ %d アイコン - アイコンパック カスタム 権限がありません 新しい形式 (v0.6.3 以降) @@ -211,17 +196,11 @@ AegisはMicrosoft独自の2FAアルゴリズムと互換性がありません。Office 365で2FAを設定する場合は、必ず「通知なしでアプリケーションを設定する」を選択してください。 Raw 保管庫のロックを解除しています - 保管庫のロックを解除しています(修理中) - 少なくとも1つのパスワードスロットが必要です (このデバイス) - スロットを削除 - このスロットを削除してもよろしいですか? グループを削除 このグループを削除してもよろしいですか?このグループのエントリは自動的に「グループなし」に切り替わります。 アイコンパックを削除 このアイコンパックを削除してもよろしいですか?このパックからアイコンを使用するエントリーは影響を受けません。 - 新しいスロットの追加中にエラーが発生しました: - アニメーションの継続時間をリセットすることはできません。プログレスバーは表示されません。 詳細 ロックする 名前 @@ -234,7 +213,6 @@ 使用回数 カスタム 新しいグループ… - グループ名を入力してください グループ グループ名 グループを編集 diff --git a/app/src/main/res/values-kn-rIN/strings.xml b/app/src/main/res/values-kn-rIN/strings.xml index 33011cfd26..6132ffd160 100644 --- a/app/src/main/res/values-kn-rIN/strings.xml +++ b/app/src/main/res/values-kn-rIN/strings.xml @@ -2,19 +2,16 @@ ಸಿದ್ಢತೆಗಳು ಅಪ್ಲಿಕೇಶನ್ ಬಗ್ಗೆ - ಅಮದಿಸು ಅಳಿಸು ಮೂಲ ಐಕಾನನ್ನು ಪುನಃಸ್ಥಾಪಿಸಿ ತಿರಸ್ಕರಿಸು ಉಳಿಸು ನೀಡುವವರು - ಸಿದ್ಢತೆಗಳು ಥೀಮ್ ವೀಕ್ಷಣೆಯ ರೀತಿ ಭಾಷೆ ಖಾತೆಯ ಹೆಸರನ್ನು ತೋರಿಸು ಖಾತೆಯ ಹೆಸರನ್ನು ನೀಡುವವರ ಪಕ್ಕ ತೋರಿಸುವುದಕ್ಕೆ ಇದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸು - ಕಾಲಾವಧಿ ಫೈಲಿಂದ ಆಮದಿಸು ಅಪ್ಲಿಕೇಶನಿಂದ ಆಮದಿಸು ರಫ್ತು ಮಾಡು @@ -30,8 +27,6 @@ ಭದ್ರತೆ ಯಾವುದೂ ಇಲ್ಲ ಗುಪ್ತಪದ - ಗುಪ್ತಪದ - ನಿಮ್ಮ ಗುಪ್ತಪದವನ್ನು ನಮೂದಿಸಿ ದಯವಿಟ್ಟು ಗುಪ್ತಪದವನ್ನು ನಮೂದಿಸಿ ದಯವಿಟ್ಟು ಒಂದು ಗುಂಪಿನ ಹೆಸರವನ್ನು ನಮೂದಿಸಿ @@ -45,10 +40,8 @@ ಕ್ಯೂ ಆರ್ ಸಂಕೇತವನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡು ಚಿತ್ರವನ್ನು ಸ್ಕ್ಯಾನ್ ಮಾಡು ಕೈಯಾರೆ ನಮೂದಿಸಿ - ಗುಪ್ತಪದವನ್ನು ಸೇರಿಸಿ ನಕಲಿಸು ಸಂಪಾದಿಸು - ಅಳಿಸು ಗುಪ್ತಪದ ಗುಪ್ತಪದವನ್ನು ಖಚಿತಗೊಳಿಸು ವೌಲ್ಟಿನ ಬೀಗವನ್ನು ತೆರೆಯಲು ಆಗಲಿಲ್ಲ @@ -60,8 +53,6 @@ ನಿಜವಾಗಲು ಈ ಎಂಟ್ರಿಯನ್ನು ಅಳಿಸಲು ಬಯಸುವಿರಾ? ಬದಲಾವಣೆಗಳನ್ನು ತಿರಸ್ಕರಿಸುವುದಾ? ನಿಮ್ಮ ಬದಲಾವಣೆಗಳು ಉಳಿಸಲಾಗಿಲ್ಲ - ಫೋಲ್ಡರ್ - ಆಯ್ಕೆಮಾಡಲು ಇಲ್ಲಿ ಒತ್ತು ಪ್ರೊಫೈಲನ್ನು ಉಳಿಸಲು ದೋಷವಾಯಿತು ಸ್ವಾಗತ Aegis ಒಂದು ಉಚಿತ, ಸುರಕ್ಷಿತ, ಹಾಗು ಮುಕ್ತ ಸಂಪನ್ಮೂಲ 2FA ಅಪ್ಲಿಕೇಶನ್ @@ -87,14 +78,8 @@ ಕ್ಯೂ ಆರ್ ಸಂಕೇತವನ್ನು ಓದಲು ಪ್ರಯತ್ನಿಸುವಾಗ ಒಂದು ದೋಷ ಸಂಭವಿಸಿದೆ ಕಚ್ಚಾ ವೌಲ್ಟಿನ ಬೀಗವನ್ನು ತೆರೆಯಲಾಗುತ್ತಿದೆ - ವೌಲ್ಟಿನ ಬೀಗವನ್ನು ತೆರೆಯಲಾಗುತ್ತಿದೆ (ಸರಿಪಡಿಸುತ್ತಿದೆ) - ನೀನು ಕನಿಷ್ಠ ಒಂದು ಗುಪ್ತಪದದ ಸ್ಲಾಟನ್ನು ಹೊಂದಿರಬೇಕು - ಸ್ಲಾಟನ್ನು ತೆಗೆದುಹಾಕು - ಈ ಸ್ಲಾಟನ್ನು ತೆಗೆದುಹಾಕಲು ಖಚಿತವಾಗಿದ್ದೀಯಾ? ಗುಂಪನ್ನು ತೆಗೆದುಹಾಕು ನಿಜವಾಗಲು ಈ ಗುಂಪನ್ನು ತೆರೆಯಾಲು ಬಯಸುವಿರಾ? ಈ ಗುಂಪಿನಾಲ್ಲಿರುವ ಎಂಟ್ರಿಗಳು ಸ್ವಯಂಚಾಲಿತವಾಗಿ \'ಗುಂಪಿಲ್ಲ\'ಗೆ ಬದಲಾವಣೆ ಆಗುತ್ತವೆ. - ಹೊಸ ಸ್ಲಾಟನ್ನು ಸೇರಿಸಲು ಪ್ರಯತ್ನಿಸುವಾಗ ಒಂದು ದೋಷ ಸಂಭವಿಸಿದೆ: - ಆನಿಮೇಟರಿನ ಅವಧಿಯ ಅಳತೆಯನ್ನು ಮರುಹೊಂದಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಪ್ರಗತಿ ಬಾರುಗಳು ಅಗೋಚರವಿರುತ್ತವೆ. ವಿವರಗಳು ಬೀಗ ಹಾಕು ಹೆಸರು @@ -105,7 +90,6 @@ ಖಾತೆ (ವರ್ಣಮಾಲೆಯ ಹಿಮ್ಮುಖವಾಗಿಯಾಗಿ) ನಿಮ್ಮ ಇಚ್ಛೆ ಹೊಸ ಗುಂಪು… - ಗುಂಪಿನ ಹೆಸರನ್ನು ನಮೂದಿಸು ಗುಂಪಿನ ಹೆಸರು ಗುಂಪುಗಳನ್ನು ಸಂಪಾದಿಸು ಇಲ್ಲಿ ನಿಮ್ಮ ಗುಂಪುಗಳನ್ನು ನಿರ್ವಹಿಸು ಮತ್ತು ಅಳಿಸು diff --git a/app/src/main/res/values-lt-rLT/strings.xml b/app/src/main/res/values-lt-rLT/strings.xml index 19b80391fa..3c041275d4 100644 --- a/app/src/main/res/values-lt-rLT/strings.xml +++ b/app/src/main/res/values-lt-rLT/strings.xml @@ -2,7 +2,6 @@ Nustatymai Apie - Importuoti Ištrinti Perkelti Taisyti piktogramą @@ -11,7 +10,6 @@ Įrašyti Leidėjas Naudojimų skaičius - Nuostatos Programėlė Įrašai „Android“ @@ -75,8 +73,6 @@ Įspėjimas: Jei pamiršite savo slaptažodį, jūs visam laikui prarasite prieigą prie prieigos raktų. Be slaptažodžio niekaip nebegalėsite jų atkurti. Biometrika Papildomai prie slaptažodžio, gali būti naudojama šiame įrenginyje registruota biometrika, tokia kaip piršto atspaudas ar jūsų veidas. - Slaptažodis - Įveskite slaptažodį Atrakinti slėptuvę Atrakinkite\nslėptuvę @@ -93,17 +89,13 @@ Taip Ne Atrakinti - Biometrika Skaitmenys Paslaptis Skenuoti QR kodą Skenuoti atvaizdą Įvesti rankiniu būdu - Pridėti biometriką - Pridėti slaptažodį Kopijuoti Taisyti - Ištrinti Slaptažodis Rodyti slaptažodį Naujas įrašas @@ -124,7 +116,6 @@ Atmesti pakeitimus? Jūsų pakeitimai neįrašyti - Aplankas Klaida įrašant profilį Sveiki Aegis yra nemokama, saugi ir atvirojo kodo dviejų faktorių tapatybės nustatymo (2FA) programėlė @@ -145,7 +136,6 @@ %d piktogramų %d piktograma - Piktogramų paketas Leidimas atmestas Pasirinkite programėlę, iš kurios norėtumėte importuoti Pasirinkite pageidaujamą apipavidalinimą @@ -166,13 +156,9 @@ Bandant nustatyti slaptažodį, įvyko klaida. Bandant perskaityti QR kodą, įvyko klaida Atrakinama slėptuvė - Atrakinama slėptuvė (taisoma) - Šalinti lizdą - Ar tikrai norite pašalinti šį lizdą? Šalinti grupę Šalinti piktogramų paketą Ar tikrai norite pašalinti šį piktogramų paketą? Tai nepaveiks įrašų, kurie naudoja piktogramas iš šio paketo. - Bandant pridėti naują lizdą, įvyko klaida: Pavadinimas Nėra grupės Leidėjas (A–Ž) diff --git a/app/src/main/res/values-lv-rLV/strings.xml b/app/src/main/res/values-lv-rLV/strings.xml index 3d78f758aa..485e649889 100644 --- a/app/src/main/res/values-lv-rLV/strings.xml +++ b/app/src/main/res/values-lv-rLV/strings.xml @@ -2,7 +2,6 @@ Iestatījumi Par - Ievietot Dzēst Pārsūtīt Labot ikonu @@ -14,7 +13,6 @@ Izsniedzējs Ieteikts Izmantošanas reižu skaits - Uzstādījumi Lietotne Ieraksti Android @@ -38,8 +36,6 @@ Rādīt kodu ar divciparu kopām 3 ciparu kopu vietā Rādīt konta nosaukumu Iespējot, lai rādītu konta nosaukumu blakus izsniedzējam - Noildze - Aizslēgt glabātavu pēc %1$s sekundes(žu) bezdarbības Izgūt no datnes Ievietot kodus no datnes Ņemt dalību Android dublēšanas sistēmā @@ -95,8 +91,6 @@ Brīdinājums: ja tiks aizmirsta parole, neatgriezeniski tiks zaudēta piekļuve kodiem. Bez paroles nav iespējams tos atgūt. Biometrija Papildus parolei glabātavas atslēgšanai var izmantot arī ierīcē pievinotos biometriskos datus, piemēram, pirkstu nospiedumu vai seju. - Parole - Ievadīt paroli Atslēgt glabātavu Atslēgt\nglabātavu @@ -116,7 +110,6 @@ Atslēgt - Biometrija Papildus Skaitītājs Cipari @@ -124,14 +117,9 @@ Nolasīt kvadrātkodu Nolasīt attēlu Ievadīt pašrocīgi - Pievienot biometriju - Pievienot biometrijas vietu Uzstādīt atslēgšanu ar biometriju - Pievienot paroli - Glabātava ir tikai tik droša, cik tās vājākais noslēpums. Ja tiks mainīti ierīces biometriskās pieteikšanās iestatījumi, būs nepieciešams atkārtoti iespējot atslēgšanu ar biometriju Aegis lietotnē. Ievietot starpliktuvē Labot - Dzēst Parole Apstiprināt paroli Rādīt paroli @@ -155,8 +143,6 @@ Atmest izmaiņas? Izmaiņas netika saglabātas - Mape - Piesist, lai atlasītu Kļūda saglabājot profilu Sveiciens! Aegis ir bezmaksas, droša un atvērtā pirmkoda 2FA lietotne @@ -189,7 +175,6 @@ %d ikona %d ikonas - Ikonu pakotne Pielāgots Atļauja liegta Jaunais veidols (v0.6.3 vai jaunāks) @@ -219,17 +204,11 @@ Aegis nav saderīgs ar Microsoft piederošo 2FA algoritmu. Lūgums pārliecināties, ka ir izvēlēts \"Uzstādīt lietotni bez paziņojumiem\", kad Office 365 tiek uzstādīts 2FA. Neapstrādāts Atslēgšana ar biometriju - Notiek glabātavas atslēgšana (labojot) - Ir nepieciešama vismaz viena paroles vieta (šī ierīce) - Noņemt vietu - Vai tiešām izdzēst šo vietu? Noņemt kopu Vai tiešām noņemt šo kopu? Tās ieraksti tiks pārmainīti uz \"Nav kopas\". Noņemt ikonu pakotni Vai tiešām noņemt šo ikonu pakotni? Netiks ietekmēti ieraksti, kuros ir izmantotas pakotnē esošās ikonas. - Atgadījusies kļūda jaunas vietas pievienošanā: - Nav iespējams atiestatīt animatora ilguma mērogu. Izpildes joslas būs neredzamas. Izklāsts Aizslēgt Nosaukums @@ -242,7 +221,6 @@ Izmantošanas reižu skaits Pielāgots Jauna kopa… - Ievadīt kopas nosaukumu Kopa Kopas nosaukums Labot kopas diff --git a/app/src/main/res/values-nl-rNL/strings.xml b/app/src/main/res/values-nl-rNL/strings.xml index 1c4502b24b..4d0f325ff8 100644 --- a/app/src/main/res/values-nl-rNL/strings.xml +++ b/app/src/main/res/values-nl-rNL/strings.xml @@ -2,7 +2,6 @@ Instellingen Over - Importeren Verwijderen Overzetten Icoon aanpassen @@ -14,7 +13,6 @@ Uitgever Voorgesteld Gebruikstelling - Voorkeuren App Items Android @@ -38,8 +36,6 @@ Toon code in 2-cijferige groepering in plaats van 3-cijferige groepering Toon de accountnaam Schakel in om de accountnaam naast de uitgever te tonen - Time-out - Automatisch kluis vergrendelen na %1$s seconden zonder activiteit Importeren vanuit een bestand Importeer tokens vanuit een bestand Neem deel aan het back-upsysteem van Android @@ -94,8 +90,6 @@ Waarschuwing: Als je je wachtwoord vergeet, verlies je permanent toegang tot je tokens. Er is geen manier om deze te herstellen zonder het wachtwoord. Biometrie Naast een wachtwoord kunnen biometrische gegevens die geregistreerd zijn op dit apparaat, zoals een vingerafdruk of je gezicht, gebruikt worden om de kluis te ontgrendelen. - Wachtwoord - Vul je wachtwoord in Ontgrendel de kluis Ontgrendel\nde kluis @@ -115,7 +109,6 @@ Ja Nee Ontgrendel - Biometrie Geavanceerd Teller Cijfers @@ -123,14 +116,9 @@ Scan QR-code Scan afbeelding Handmatig invoeren - Biometrie toevoegen - Biometrische vergrendelingssleutel toevoegen Biometrische ontgrendeling instellen - Wachtwoord toevoegen - De kluis is zo veilig als je zwakste geheim. Als u de biometrische authenticatie-instellingen van uw apparaat wijzigt, moet u de biometrische verificatie binnen Aegis opnieuw activeren. Kopiëren Bewerken - Verwijderen Wachtwoord Bevestig wachtwoord Wachtwoord tonen @@ -153,8 +141,6 @@ Wijzigingen ongedaan maken? Je wijzigingen zijn niet opgeslagen - Map - Aantikken om te selecteren Fout bij opslaan profiel Welkom Aegis is een gratis en veilige open-source 2FA-app. @@ -186,7 +172,6 @@ %d pictogram %d pictogrammen - Pictogrampakket Aangepast Toestemming geweigerd Nieuw formaat (v0.6.3 of nieuwer) @@ -215,17 +200,11 @@ Aegis is niet compatibel met het eigen 2FA-algoritme van Microsoft. Zorg ervoor dat u \"Setup applicatie zonder meldingen\" selecteert bij het configureren van 2FA in Office 365. Onbewerkt Kluis wordt ontgrendeld - Kluis wordt ontgrendeld (repareren) - Je moet minimaal één wachtwoordvergrendeling hebben (dit apparaat) - Vergrendelingssleutel verwijderen - Weet je zeker dat je deze vergrendelingssleutel wilt verwijderen? Groep verwijderen Weet je zeker dat je deze groep wilt verwijderen? Alle items binnen deze groep worden automatisch verplaatst naar \'Geen groep\'. Verwijder pictogrammenpakket Weet je zeker dat je dit pictogrampakket wilt verwijderen? Entries die pictogrammen uit dit pakket gebruiken, worden niet beïnvloed. - Er is een fout opgetreden tijdens het toevoegen van een nieuwe vergrendelingssleutel: - Niet in staat om de schaal van animatieduur te resetten. Voortgangsbalken zullen niet zichtbaar zijn. Details Vergrendel Naam @@ -238,7 +217,6 @@ Gebruikstelling Aangepast Nieuwe groep… - Vul een groepsnaam in Groep Groepsnaam Groepen bewerken diff --git a/app/src/main/res/values-pl-rPL/strings.xml b/app/src/main/res/values-pl-rPL/strings.xml index c22b7766d8..2070fd661e 100644 --- a/app/src/main/res/values-pl-rPL/strings.xml +++ b/app/src/main/res/values-pl-rPL/strings.xml @@ -2,7 +2,6 @@ Ustawienia O aplikacji - Importuj Usuń Przenieś Edytuj ikonę @@ -11,7 +10,6 @@ Zapisz Wydawca Polecane - Ustawienia Aplikacja Wpisy Android @@ -35,8 +33,6 @@ Pokaż kod w grupach 2-cyfrowych zamiast w 3-cyfrowych Pokaż nazwę konta Włącz tę opcję, aby pokazywać nazwę konta obok wydawcy - Limit czasu - Automatycznie zablokuj sejf po %1$s sekundach bezczynności Importuj z pliku Importuj tokeny z aplikacji Utwórz kopię zapasową w systemie Android @@ -93,8 +89,6 @@ Ostrzeżenie: Jeśli zapomnisz hasła, utracisz dostęp do swoich tokenów. Bez użycia hasła, nie ma sposobu na ich odzyskanie. Autoryzacja biometryczna Oprócz hasła, do odblokowania sejfu możesz użyć danych biometrycznej przechowywanych na tym urządzeniu, takich jak odcisk palca lub twarz. - Hasło - Wpisz hasło Odblokuj sejf Odblokuj\nsejf @@ -114,7 +108,6 @@ Tak Nie Odblokuj - Autoryzacja biometryczna Zaawansowane Licznik Cyfry @@ -122,14 +115,9 @@ Skanuj kod QR Skanuj zdjęcie Wpisz ręcznie - Dodaj autoryzację biometryczną - Dodaj slot biometryczny Ustaw autoryzację biometryczną - Dodaj hasło - Sejf jest tak bezpieczny, jak Twoje hasło. Jeśli zmienisz ustawienia zabezpieczeń biometrycznych w swoim urządzeniu, będziesz musiał ponownie aktywować autoryzację biometryczną w aplikacji Aegis. Kopiuj Edytuj - Usuń Hasło Potwierdź hasło Pokaż hasło @@ -154,8 +142,6 @@ Czy chcesz odrzucić zmiany? Zmiany nie zostały zapisane - Folder - Kliknij, aby wybrać Wystąpił błąd podczas zapisywania profilu Witaj Aegis jest darmową, bezpieczną i otwartoźródłową aplikacją uwierzytelniającą @@ -189,7 +175,6 @@ %d ikony %d ikon - Pakiet ikon Niestandardowa Odmowa uprawnienia Nowy format (wersja 0.6.3 lub nowsza) @@ -220,17 +205,11 @@ Aplikacja Aegis nie jest kompatybilna z algorytmem uwierzytelniającym firmy Microsoft. Podczas konfigurowania uwierzytelniania w aplikacji Office 365 wybierz opcję \"Skonfiguruj aplikację bez powiadomień\". Dane surowe Odblokowywanie sejfu - Odblokowywanie sejfu (naprawa) - Musisz posiadać co najmniej jeden slot hasła (to urządzenie) - Usuń slot - Czy na pewno chcesz usunąć ten slot? Usuń grupę Czy na pewno chcesz usunąć tę grupę? Wpisy z tej grupy zostaną automatycznie przeniesione do kategorii \'Brak grupy\'. Usuń pakiet ikon Czy na pewno chcesz usunąć ten pakiet ikon? Wpisy, które używają ikon z tego pakietu nie zostaną zmienione. - Wystąpił błąd podczas próby dodania nowego slotu: - Nie można zresetować skali długości animacji. Paski postępu będą niewidoczne. Szczegóły Zablokuj Nazwa @@ -242,7 +221,6 @@ Konto (od Z do A) Niestandardowe Nowa grupa… - Wpisz nazwę grupy Grupa Nazwa grupy Edytuj grupy diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 9feb5de973..f500cf1ff4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -2,7 +2,6 @@ Configurações Sobre - Importar Excluir Transferir Editar ícone @@ -14,7 +13,6 @@ Nome do serviço Sugerido Contagem de uso - Preferências Aplicativo Entradas Android @@ -38,8 +36,6 @@ Exibe códigos em grupos de 2 dígitos ao invés de grupos de 3 dígitos Exibir o nome da conta Habilite essa opção para exibir o nome da conta ao lado do nome do serviço - Tempo de espera - Bloqueia automaticamente o cofre depois de %1$s segundos de inatividade Importar de arquivo Importa tokens de um arquivo Participar do sistema de backup do Android @@ -94,8 +90,6 @@ Aviso: Se você esquecer sua senha, você perderá definitivamente acesso aos seus tokens. Não há como recuperá-los sem a senha. Biometria Em adição a uma senha, biometria registrada nesse dispositivo, como uma impressão digital ou sua face, pode ser usada para desbloquear o cofre. - Senha - Insira sua senha Desbloquear o cofre Desbloqueie\no cofre @@ -115,7 +109,6 @@ Sim Não Desbloquear - Biometria Avançado Contador Dígitos @@ -123,14 +116,9 @@ Escanear QR code Escanear imagem Inserir manualmente - Adicionar biometria - Adicionar chave biométrica Preparar desbloqueio biométrico - Adicionar senha - O cofre só é tão seguro quanto o seu segredo mais fraco. Se você alterar as configurações de autenticação biométrica do seu dispositivo, você precisará reativar o desbloqueio biométrico dentro do Aegis. Copiar Editar - Excluir Senha Confirmar senha Mostrar senha @@ -153,8 +141,6 @@ Descartar mudanças? Suas mudanças não foram salvas - Pasta - Toque para selecionar Erro ao salvar o perfil Bem-vindo Aegis é um app 2FA grátis, seguro e de código aberto @@ -186,7 +172,6 @@ %d ícone %d ícones - Pacote de ícones Personalizado Permissão negada Novo formato (v0.6.3 ou mais novo) @@ -215,17 +200,11 @@ Aegis não é compatível com o algoritmo 2FA proprietário da Microsoft. Certifique-se de selecionar \"Configurar aplicativo sem notificações\" ao configurar 2FA no Office 365. Raw Desbloqueando o cofre - Desbloque o cofre (reparando) - Você deve ter ao menos uma senha (esse dispositivo) - Remover chave - Você tem certeza que deseja remover essa chave? Remover grupo Você tem certeza que deseja remover esse grupo? Entradas nesse grupo serão automaticamente para \'Sem grupo\'. Remover pacote de ícones Tem certeza que deseja remover este pacote de ícones? Entradas que usam ícones deste pacote não serão afetadas. - Um erro ocorreu durante a tentativa de adicionar uma nova chave: - Não foi possível resetar a escala de duração da animação. Barras de progresso ficarão invisíveis. Detalhes Bloquear Conta @@ -238,7 +217,6 @@ Contagem de uso Personalizado Novo grupo… - Insira o nome de grupo Grupo Nome do grupo Editar grupos diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml index f0e3b34a21..984aa9da23 100644 --- a/app/src/main/res/values-pt-rPT/strings.xml +++ b/app/src/main/res/values-pt-rPT/strings.xml @@ -2,7 +2,6 @@ Configurações Sobre - Importar Excluir Transferir Editar ícone @@ -10,7 +9,6 @@ Descartar Salvar Emissor - Preferências Tema Modo de exibição Idioma @@ -18,8 +16,6 @@ Mostrar agrupamento de códigos de 2 dígitos em vez de agrupamento de 3 dígitos Exibir o nome da conta Ative isto para mostrar o nome da conta ao lado do emissor - Tempo esgotado - Bloquear automaticamente o cofre após %1$s segundos de inatividade Importar arquivo Importar tokens de um arquivo Cria automaticamente backups do cofre no armazenamento externo quando alterações forem feitas. Isso só é suportado para cofres criptografados. @@ -69,8 +65,6 @@ Aviso: Se você esquecer sua senha, você perderá permanentemente o acesso aos seus tokens. Não há como recuperá-los sem a senha. Biometria Além de uma senha, a biometria registrada neste dispositivo, como uma impressão digital ou seu rosto, pode ser usada para desbloquear o cofre. - Senha - Digite sua senha Desbloquear o cofre Desbloqueie\no cofre @@ -86,7 +80,6 @@ Sim Não Desbloquear - Biometria Avançado Contador Dígitos @@ -94,14 +87,9 @@ Escanear código QR Escanear imagem Inserir manualmente - Adicionar biométrica - Adicionar slot biométrico Configurar desbloqueio biométrico - Adicionar senha - O cofre é tão seguro quanto o seu segredo mais fraco. Se você alterar as configurações de autenticação biométrica do seu dispositivo, você precisará reativar o desbloqueio biométrico dentro do Aegis. Copiar Editar - Apagar Senha Confirme a Senha Mostrar senha @@ -122,8 +110,6 @@ Descartar alterações? Suas alterações não foram salvas - Pasta - Toque para selecionar Erro ao salvar perfil Bem-vindo Aegis é um aplicativo 2FA gratuito, seguro e opensource @@ -172,15 +158,9 @@ Ocorreu um erro ao tentar ler o código QR Raw Desbloqueando o cofre - Desbloqueando o cofre (reparando) - Você deve ter pelo menos um slot de senha (este dispositivo) - Remover slot - Tem certeza que deseja remover este slot? Remover grupo Tem certeza que deseja remover este grupo? As entradas neste grupo automaticamente alternarão para \'Sem grupo\'. - Ocorreu um erro ao tentar adicionar um novo slot: - Não foi possível redefinir a duração da animação. Barras de progresso ficarão invisíveis. Detalhes Bloquear Nome @@ -191,7 +171,6 @@ Conta (Z a A) Personalizado Novo grupo… - Digite o nome do grupo Nome do grupo Editar grupos Gerenciar e excluir seus grupos aqui diff --git a/app/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml index 5eeb3ba505..350bb950ea 100644 --- a/app/src/main/res/values-ro-rRO/strings.xml +++ b/app/src/main/res/values-ro-rRO/strings.xml @@ -2,7 +2,6 @@ Setări Despre - Import Șterge Transfer Editare pictogramă @@ -14,7 +13,6 @@ Emitent Sugerat Numărul de utilizări - Preferințe Aplicație Intrări Android @@ -38,8 +36,6 @@ Arată codul în grupuri din 2 cifre în loc de grupuri din 3 cifre Arată numele contului Activează pentru a afișa numele contului lângă emitent - Inactivitate - Blochează automat seiful după %1$s secunde de inactivitate Importă din fișier Importă token-uri dintr-un fișier Participă la copia de rezervă a sistemului Android @@ -95,8 +91,6 @@ Avertisment: Dacă îți uiți parola, vei pierde definitiv accesul la token-urile dvs. Nu există nicio modalitate de a le recupera fără parolă. Biometrice În plus față de o parolă, datele biometrice înregistrate pe acest dispozitiv, cum ar fi o amprentă sau fața ta, pot fi folosite pentru a debloca seiful. - Parolă - Introdu parola Deblocare seif Deblocare\nseif @@ -116,7 +110,6 @@ Da Nu Deblocare - Biometrice Avansat Contor Cifre @@ -124,14 +117,9 @@ Scanare cod QR Scanare imagine Introducere manuală - Adaugare biometrice - Adăugare slot biometric Configurează deblocarea biometrică - Adăugare parolă - Seiful este la fel de sigur ca cel mai slab secret. Dacă modifici setările biometrice de autentificare ale dispozitivului tău, va trebui să reactivezi deblocarea biometrică în interiorul Aegis. Copiere Editare - Ştergere Parolă Confirmă parola Arată parola @@ -155,8 +143,6 @@ Renunți la modificări? Modificările nu au fost salvate - Dosar - Apasă pentru a selecta Eroare la salvarea profilului Bun venit Aegis este o aplicație 2FA gratuită, sigură și cu sursă deschisă @@ -189,7 +175,6 @@ Pictograme %d %d pictograme - Pachetul de pictograme Personalizat Permisiune refuzată Format nou (v0.6.3 sau mai nou) @@ -219,17 +204,11 @@ Aegis nu este compatibil cu algoritmul 2FA brevetat al Microsoft. Te rugăm să selectezi \"Configurare aplicație fără notificări\" la configurarea 2FA în Office 365. Brut Deblocarea seifului - Deblocarea seifului (reparare) - Trebuie să ai cel puțin un slot pentru parolă (aceste dispozitive) - Elimină slotul - Ești sigur că dorești să ștergi acest slot? Elimină grupul Ești sigur că dorești să ștergi acest grup? Intrările din acest grup vor comuta automat la \'Niciun grup\'. Ștergeți pachetul de pictograme Sunteţi sigur că doriţi să ştergeţi acest pachet de pictograme? Intrările care folosesc pictograme din acest pachet nu vor fi afectate. - A aparut o eroare în timp ce se încerca adăugarea unui nou slot: - Imposibil de a reseta scala duratei animatorului. Barele de progres vor fi invizibile. Detalii Blocare Nume @@ -242,7 +221,6 @@ Numărul de utilizări Personalizat Grup nou… - Introdu numele grupului Grup Numele grupului Editează grupuri diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 3539336443..84da67d16d 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -2,7 +2,6 @@ Настройки О программе - Импорт Удалить Передача Изменить значок @@ -14,7 +13,6 @@ Эмитент Рекомендуется Счётчик использований - Предпочтения Приложение Записи Android @@ -38,8 +36,6 @@ Объединять код в двузначные блоки вместо трёхзначных Показывать учётную запись Включите, чтобы показывать название учётной записи рядом с эмитентом - Задержка - Автоматически блокировать хранилище после %1$s секунд бездействия Импорт из файла Импорт ключей из файла Система резервного копирования Android @@ -96,8 +92,6 @@ Внимание: при утрате пароля вы навсегда потеряете доступ к своим ключам. Восстановить доступ к ним без пароля будет невозможно. Биометрия В дополнение к паролю, биометрические данные, зарегистрированные на этом устройстве, такие как отпечаток пальца или лицо, могут быть использованы для разблокировки хранилища. - Пароль - Введите свой пароль Разблокировать хранилище Открыть\nхранилище @@ -117,7 +111,6 @@ Да Нет Разблокировать - Биометрия Расширенные Счётчик Цифры @@ -125,14 +118,9 @@ Сканировать QR-код Сканировать изображение Ввести вручную - Добавить биометрические данные - Добавить биометрический слот Настройка биометрической разблокировки - Добавить пароль - Хранилище настолько же безопасно, насколько безопасен ваш самый слабый секрет. При изменении настроек биометрической аутентификации на устройстве биометрическую разблокировку в Aegis придётся включить заново. Копировать Изменить - Удалить Пароль Подтвердите пароль Показать пароль @@ -157,8 +145,6 @@ Отменить изменения? Ваши изменения не были сохранены - Папка - Нажмите, чтобы выбрать Ошибка сохранения профиля Добро пожаловать Aegis — бесплатное, безопасное и открытое приложение 2FA. @@ -192,7 +178,6 @@ %d значков %d значков - Набор значков Произвольный Доступ запрещён Новый формат (v0.6.3 или новее) @@ -223,17 +208,11 @@ Aegis не совместим с проприетарным алгоритмом 2FA Microsoft. Не забудьте выбрать «Установить приложение без уведомлений» при настройке 2FA в Office 365. Необработанный Разблокировка хранилища - Разблокировка хранилища (восстановление) - У вас должен быть хотя бы один слот для пароля (это устройство) - Удалить слот - Удалить этот слот? Удалить группу Удалить эту группу? Записи в этой группе будут автоматически перемещены в «Нет группы». Удалить набор значков Удалить этот набор значков? Сами записи, использующие значки из данного набора, не будут затронуты. - Произошла ошибка при попытке добавить новый слот: - Не удалось установить продолжительность анимации. Индикаторы прогресса не будут отображаться. Подробнее Заблокировать Имя @@ -246,7 +225,6 @@ Количество использований Произвольно Новая группа… - Введите название группы Группа Название группы Редактировать группы diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml index 83ffc9a1ab..ee730251f4 100644 --- a/app/src/main/res/values-sk-rSK/strings.xml +++ b/app/src/main/res/values-sk-rSK/strings.xml @@ -2,7 +2,6 @@ Nastavenia O programe - Importovať Odstrániť Prenos Upraviť ikonu @@ -10,7 +9,6 @@ Zahodiť Uložiť Vydavateľ - Predvoľby Aplikácia Android Automatické zálohy @@ -27,8 +25,6 @@ Zobraziť kód v 2-miestnom zoskupení namiesto 3-miestneho zoskupenia Zobraziť názov účtu Povolením tejto možnosti zobrazíte názov účtu vedľa vydavateľa - Časový limit - Automaticky uzamknúť trezor po %1$s sekundách nečinnosti Importovať zo súboru Importovať tokeny zo súboru Po vykonaní zmien automaticky vytvárať zálohy trezoru na externom úložisku. Toto je podporované iba pre šifrované trezory. @@ -69,8 +65,6 @@ Upozornenie: Ak zabudnete heslo, natrvalo stratíte prístup k svojim tokenom. Neexistuje žiadny spôsob, ako ich obnoviť bez hesla. Biometrické údaje Na odomknutie trezoru možno okrem hesla použiť aj biometriu zaregistrovanú v tomto zariadení, napríklad odtlačok prsta alebo vašu tvár. - Heslo - Zadajte svoje logovacie heslo Odomknúť trezor Odomknúť \n trezor @@ -85,19 +79,15 @@ Áno Nie Odomknúť - Biometrické údaje Pokročilé Počítadlo Číslice Skenovať QR kód Skenovať obrázok Zadať ručne - Pridať biometriku Nastaviť biometrické odomknutie - Pridať heslo Kopírovať Upraviť - Odstrániť Heslo Potvrďte heslo Zobraziť heslo @@ -107,8 +97,6 @@ Vymazať položky Zrušiť zmeny? Vaše zmeny neboli uložené - Priečinok - Klepnutím vyberte Pri ukladaní profilu sa vyskytla chyba Vitajte Nastavenie je dokončené @@ -128,7 +116,6 @@ Účet (Z až A) Vlastný Nová skupina… - Zadajte názov skupiny Skupina Názov skupiny Upraviť skupiny diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 3d73d8a3ea..b7e16281d4 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -2,7 +2,6 @@ Inställningar Om - Importera Radera Överför Redigera ikon @@ -14,7 +13,6 @@ Utfärdare Förslag Användningsfrekvens - Inställningar App Poster Android @@ -38,8 +36,6 @@ Visa kod med 2-siffrig gruppering i stället för 3-siffrig gruppering Visa kontonamnet Aktivera denna för att visa kontonamnet bredvid utfärdaren - Tidsgräns - Lås valvet automatiskt efter %1$s sekunders inaktivitet Importera från fil Importera polletter från en fil Delta i Androids säkerhetskopiering @@ -93,8 +89,6 @@ Ett lösenord behövs för att låsa upp valvet. Varning: Om du glömmer ditt lösenord, förlorar du permanent åtkomst till dina polletter. Det finns inget sätt att återfå dem utan lösenordet. Biometri - Lösenord - Ange ditt lösenord Lås upp valvet Lås upp\nvalvet @@ -112,17 +106,14 @@ Ja Nej Lås upp - Biometri Avancerat Siffror Hemlig nyckel Skanna QR-kod Skanna bild Ange manuellt - Lägg till lösenord Kopiera Redigera - Radera Lösenord Bekräfta lösenord Visa lösenord @@ -132,7 +123,6 @@ Radera post Ignorera ändringar? Dina ändringar har inte sparats - Mapp Välkommen Kopierad Fel kopierade till urklipp @@ -145,14 +135,12 @@ %d ikon %d ikoner - Ikonpaket Anpassad Nytt format (v0.6.3 eller nyare) Gammalt format (v0.6.2 eller äldre) Välj önskat tema Välj önskat visningsläge Låser upp valvet - Låser upp valvet (reparerar) Ta bort grupp Ta bort ikonpaket Är du säker på att du vill ta bort detta ikonpaket? Poster som använder ikoner från detta paket kommer inte att påverkas. diff --git a/app/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml index ee03c276d9..e7af64437b 100644 --- a/app/src/main/res/values-tr-rTR/strings.xml +++ b/app/src/main/res/values-tr-rTR/strings.xml @@ -2,7 +2,6 @@ Ayarlar Hakkında - İçe aktar Sil Aktar Simgeyi düzenle @@ -14,7 +13,6 @@ Sağlayıcı Önerilen Kullanım sayısı - Tercihler Uygulama Kayıtlar Android @@ -38,8 +36,6 @@ Kodu 3 haneli gruplama yerine 2 haneli gruplamayla göster Hesap adını göster Hesap adını sağlayıcının yanında göstermek için bunu etkinleştirin - Zaman aşımı - Kasayı %1$s saniyelik hareketsizliğin ardından otomatik kilitle Dosyadan içeriye aktar Veritabanını dosyadan içeriye aktar Android yedekleme sistemine dahil ol @@ -93,8 +89,6 @@ Uyarı: Eğer parolanızı unutursanız kodlarınıza erişimi kalıcı olarak kaybedebilirsiniz. Biyometrik Parolaya ek olarak bu cihazda kayıtlı olan parmak izi ve yüz tanıma gibi biyometrik doğrulama yöntemleri kasayı açmada kullanılabilir. - Parola - Parolanızı girin Kasa kilidini aç Kasa kilidini \naç @@ -114,7 +108,6 @@ Evet Hayır Kilidi aç - Biyometrik Gelişmiş Sayaç Haneler @@ -122,14 +115,9 @@ QR kod tara Resim tara El ile gir - Biyometrik doğrulama ekle - Biyometrik doğrulama elemanı ekle Biyometrik kilit açma kurulumu - Parola ekle - Kasanız en zayıf parolanız kadar güvende olacaktır. Eğer cihazınızda biyometrik doğrulama ayarlarını değiştirirseniz, biyometrik kilit açmayı Aegis içinde yeniden aktifleştirmeniz gerekir. Kopyala Düzenle - Sil Parola Parolayı doğrula Parolayı göster @@ -152,8 +140,6 @@ Değişiklikler göz ardı edilsin mi? Değişiklikleriniz kaydedilmedi - Klasör - Seçmek için dokunun Profil kaydı sırasında hata Hoşgeldiniz Aegis özgür, güvenli ve açık kaynak 2FA uygulamasıdır @@ -185,7 +171,6 @@ %d simgesi %d simgeleri - İkon paketi Özel İzin verilmesi reddedildi Yeni biçim (v0.6.3 veya daha yeni) @@ -214,17 +199,11 @@ Aegis Microsoft\'un özel çift faktörlü doğrulama algoritmasıyla uyumlu değildir. Office 365\' te çift faktörlü doğrulamayı yapılandırırken \"Uygulamayı bildirim olmadan kur\" u seçtiğinizden emin olun. İşlenmemiş Kasa kilidi açılıyor - Kasa kilidi açılıyor (tamir ediliyor) - En az bir parola elemanınız olmalı (bu cihaz) - Eleman sil - Bu elemanı silmek istediğinize emin misiniz? Grubu sil Bu grubu silmek istediğinize emin misiniz? Bu gruptaki girdiler otomatik olarak \'Grupsuz\' grubuna geçecek. İkon paketini sil Bu ikon paketini silmek istediğinize emin misiniz? Bu pakete ait ikonları kullanan girdiler bundan etkilenmeyeceklerdir. - Yeni bir anahtarlık oluşturmaya çalışırken hata oluştu: - Animatör süresi ölçeği sıfırlanamıyor. İlerleme çubukları görünmez olacak. Detaylar Kilitle Ad @@ -237,7 +216,6 @@ Kullanım sayısı İsteğe göre Yeni grup… - Yeni grup adı girin Grup Grup adı Grupları düzenle diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index d87717f2ce..5bb6a56c55 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -2,7 +2,6 @@ Налаштування Про додаток - Імпорт Видалити Передати Змінити іконку @@ -10,7 +9,6 @@ Відхилити Зберегти Емітент - Налаштування Додаток Записи Android @@ -32,8 +30,6 @@ Показувати код в двоцифровому групуванні замість трицифрового Показати обліковий запис Увімкніть, щоб показувати обліковий запис поруч з емітентом - Тайм-аут - Автоматично блокувати сховище через %1$s секунд неактивності Імпорт з файлу Імпортувати токени з файлу Брати участь у системі резервного копіювання Android @@ -90,8 +86,6 @@ Увага: якщо ви забудете пароль, ви назавжди втратите доступ до своїх токенів. Їх неможливо відновити без пароля. Біометрика Окрім пароля, для розблокування сховища можна використовувати біометричні дані зареєстровані на цьому пристрої, такі як відбитки пальців або ваше обличчя. - Пароль - Введіть пароль Розблокування сховища Розблокування\nсховища @@ -111,7 +105,6 @@ Так Ні Розблокувати - Біометрика Розширені Лічильник К-сть цифр @@ -119,14 +112,9 @@ Сканувати QR-код Сканувати зображення Ввести вручну - Додати біометрику - Додати біометричний слот Налаштувати біометричне розблокування - Додати пароль - Сховище захищене лише настільки як ваш найслабший секрет. Якщо ви зміните налаштування біометричної автентифікації свого пристрою, вам потрібно буде знову активувати біометричне розблокування в Aegis. Копіювати Редагувати - Видалити Пароль Підтвердьте пароль Показати пароль @@ -150,8 +138,6 @@ Відхилити зміни? Ваші зміни не були збережені - Тека - Торкніться, щоб вибрати Помилка під час збереження профілю Ласкаво просимо Aegis є безкоштовним, безпечним та відкритим додатком для 2FA @@ -205,15 +191,9 @@ Aegis не сумісний з пропрієтарним алгоритмом 2FA від Microsoft. Будь ласка, оберіть \"Налаштування програми без сповіщень\" при налаштуванні 2FA в Office 365. Raw Розблокування сховища - Розблокування сховища (відновлення) - Ви повинні мати принаймні один слот для пароля (цей пристрій) - Видалити слот - Ви впевнені, що хочете видалити цей слот? Видалити групу Ви впевнені, що хочете видалити цю групу? Записи у цій групі автоматично перемістяться в \'Без групи\'. - Сталася помилка під час спроби додати новий слот: - Не вдається налаштувати тривалість анімації. Індикатори прогресу будуть невидимими. Деталі Заблокувати Назва @@ -225,7 +205,6 @@ Обліковий запис (Я - А) Довільно Нова група… - Введіть назву групи Група Назва групи Редагувати групи diff --git a/app/src/main/res/values-vi-rVN/strings.xml b/app/src/main/res/values-vi-rVN/strings.xml index a9bf33c485..05efc40450 100644 --- a/app/src/main/res/values-vi-rVN/strings.xml +++ b/app/src/main/res/values-vi-rVN/strings.xml @@ -2,7 +2,6 @@ Cài đặt Giới thiệu - Nhập Xóa Truyền Chỉnh sửa logo @@ -14,7 +13,6 @@ Người cấp Được đề xuất Số lần sử dụng - Cài đặt Ứng dụng Mục Android @@ -38,8 +36,6 @@ Hiện mã theo kiểu gộp nhóm 2 chữ số thay vì gộp nhóm 3 chữ số Hiện tên tài khoản Bật tuỳ chọn này để hiện tên tài khoản bên cạnh người cấp - Thời gian chờ - Tự động khoá kho sau %1$s giây không có hoạt động Nhập từ tệp Nhập các token từ một tệp Tham gia vào hệ thống sao lưu của Android @@ -93,8 +89,6 @@ Cảnh báo: Nếu bạn quên mật khẩu, bạn sẽ mất quyền truy cập vào các token vĩnh viễn. Không có cách nào để phục hồi chúng mà không có mật khẩu. Sinh trắc học Bên cạnh mật khẩu, thì sinh trắc học đã được đăng ký trên thiết bị, như vân tay hoặc khuôn mặt, có thể được sử dụng để mở khoá kho. - Mật khẩu - Nhập mật khẩu Mở khoá kho Mở khoá\nkho @@ -114,7 +108,6 @@ Không Mở khoá - Sinh trắc học Nâng cao Đếm Chữ số @@ -122,14 +115,9 @@ Quét mã QR Quét hình ảnh Nhập thủ công - Thêm sinh trắc học - Thêm ô sinh trắc học Thiết lập mở khoá sinh trắc học - Thêm mật khẩu - Kho này chỉ có cùng độ bảo mật như bí mật yếu nhất của bạn. Nếu bạn thay đổi cài đặt xác thực sinh trắc học của thiết bị, thì bạn sẽ cần phải kích hoạt lại mở khoá sinh trắc học trong Aegis. Sao chép Chỉnh sửa - Xóa Mật khẩu Xác nhận mật khẩu Hiện mật khẩu @@ -151,8 +139,6 @@ Bỏ các thay đổi? Các thay đổi chưa được lưu - Thư mục - Nhấn để chọn Lỗi khi lưu hồ sơ Chào mừng Aegis là một ứng dụng Xác thực 2 yếu tố tự do, bảo mật và mã nguồn mở @@ -183,7 +169,6 @@ %d biểu tượng - Gói biểu tượng Tùy chỉnh Quyền bị từ chối Định dạng mới (v0.6.3 hoặc mới hơn) @@ -211,17 +196,11 @@ Aegis không tương thích với thuật toán Xác thực 2 yếu tố độc quyền của Microsoft. Vui lòng chắc chắn là bạn chọn \"Thiết lập ứng dụng không có thông báo\" khi thiết lập Xác thực 2 yếu tố trong Office 365. Thô Đang mở khoá kho - Đang mở khoá kho (đang sửa chữa) - Bạn phải có ít nhất một ô mật khẩu (thiết bị này) - Xoá ô - Bạn có chắc bạn muốn xoá ô này không? Xoá nhóm Bạn có chắc bạn muốn xoá nhóm này không? Các mục trong nhóm này sẽ tự động chuyển thành \"Không có nhóm\". Xoá gói biểu tượng Bạn có chắc bạn muốn xoá gói biểu tượng này không? Các mục sử dụng biểu tượng trong gói này sẽ không bị ảnh hưởng. - Đã xảy ra lỗi trong khi cố gắng thêm một ô mới: - Không thể đặt lại khoảng thời gian hoạt ảnh. Các thanh tiến trình sẽ trở nên vô hình. Chi tiết Khoá Tên @@ -234,7 +213,6 @@ Số lần sử dụng Tùy chỉnh Nhóm mới… - Nhập tên nhóm Nhóm Tên nhóm Chỉnh sửa nhóm diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml index 63fc816444..90bf00118e 100644 --- a/app/src/main/res/values-w820dp/dimens.xml +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -2,5 +2,4 @@ - 64dp diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 3a25fab578..7ae7ec7b6d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -2,7 +2,6 @@ 设置 关于 - 导入 删除 转移 编辑图标 @@ -14,7 +13,6 @@ 服务商 已建议 使用计数 - 设置 应用程序 条目 安卓 @@ -38,8 +36,6 @@ 以 2 位分组而不是 3 位分组显示验证码 显示帐户名称 在应用名称旁边显示账户名称 - 超时 - 在无操作 %1$s 秒后自动锁定数据库 从文件导入 从文件导入令牌 参与Android的备份系统 @@ -93,8 +89,6 @@ 警告:如果忘记密码,将永久失去对令牌的访问权限。没有密码,则无法恢复它们。 生物识别 除密码外,在此设备上注册的生物识别信息(例如指纹或人脸),也可用于解锁数据库。 - 密码 - 输入您的密码 解锁数据库 解锁\n数据库 @@ -114,7 +108,6 @@ 确定 取消 解锁 - 生物识别 高级设置 计数器 位数 @@ -122,14 +115,9 @@ 扫描二维码 扫描本地图片 手动输入 - 添加生物识别 - 添加生物识别方式 设置生物识别解锁 - 添加密码 - 只有当应用的每个环节都保证安全时,数据库才能保证安全。如果您更改了设备上的生物识别验证设置,您将需要在 Aegis 中重新启用生物识别解锁。 复制 编辑 - 删除 密码 确认密码 显示密码 @@ -151,8 +139,6 @@ 放弃更改? 您的更改尚未保存 - 文件夹 - 点击选择 保存配置文件时出错 欢迎使用 Aegis 是一款免费,安全和开源的双重认证(2FA)应用 @@ -183,7 +169,6 @@ %d 个图标 - 图标包 自定义 获取权限失败 新格式(v0.6.3 及以上版本) @@ -211,17 +196,11 @@ Aegis 不兼容 Microsoft 专有的 2FA 算法。请确保在设置 Office 365 的 2FA 时选择“在无通知情况下安装应用程序”。 源文件 解锁数据库 - 解锁数据库(修复) - 您必须至少有一个密码槽 (本设备) - 删除槽 - 您确定要删除这个槽吗? 删除群组 您确定要删除这个群组吗?此群组内的条目将自动移动到“无群组”。 删除图标包 您确定要删除此图标包吗?使用此包图标的条目不会受到影响。 - 试图添加新槽时出错: - 无法重置动画持续时间。进度条将不可见。 详细信息 锁定 名称 @@ -234,7 +213,6 @@ 使用计数 自定义 新建群组… - 输入群组名 群组 群组名称 编辑群组 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 18f38a9cd0..c5f5c1c1d0 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -2,7 +2,6 @@ 設定 關於 - 匯入 刪除 轉移 編輯圖示 @@ -10,7 +9,6 @@ 放棄 儲存 服務商 - 偏好設定 應用程式 條目 Android @@ -28,8 +26,6 @@ 以 2 位分組而不是 3 位分組顯示驗證碼 顯示帳戶名稱 在服務商名稱旁邊顯示帳戶名稱 - 越時 - 在無操作 %1$s 秒後自動鎖定保險箱 從檔案匯入 從檔案匯入憑證 使用 Android 的備份系統 @@ -82,8 +78,6 @@ 警告:如果您忘記密碼,您將會無法還原憑證而不可逆的失去它們。 生物識別 除密碼外,在此裝置上註冊的生物識別驗證(例如指紋或人臉),也可用於解鎖保險箱。 - 密碼 - 輸入您的密碼 解鎖保險箱 解鎖\n保險箱 @@ -102,7 +96,6 @@ 確定 取消 解鎖 - 生物識別 高級設定 計數器 位數 @@ -110,14 +103,9 @@ 掃描QR碼 掃描本地圖片 手動輸入 - 新增生物識別 - 新增生物識別位 設置生物識別解鎖 - 新增密碼 - 您的私密檔案僅透過保險箱上輕薄的一層驗證保護。是以為了維護您的隱私,當您更改了裝置上的生物識別驗證機制時,我們將停用 Aegis 上的此驗證方式,請手動重新啟用。 複製 編輯 - 刪除 密碼 確認密碼 顯示密碼 @@ -138,8 +126,6 @@ 放棄更改? 您的更改尚未儲存 - 資料夾 - 輕觸以選擇 保存設定文件時發生錯誤 歡迎使用 Aegis 是一款免費,安全和開源的雙重認證(2FA)應用程式 @@ -188,15 +174,9 @@ 試圖讀取QR碼時出錯 源文件 解鎖保險箱 - 解鎖保險箱(修復中) - 您必須至少有一個密碼位 (此裝置) - 删除槽 - 您確定要刪除這個槽嗎? 刪除群組 您確定要刪除這個群組嗎?此群組內的條目將自動移動到 “無群組”。 - 新增驗證位時發生錯誤 - 無法重置動畫持續時間。進度條將不可見。 詳細信息 鎖定 名稱 @@ -207,7 +187,6 @@ 帳戶(Z 到 A) 自定義 新增群組 - 輸入群組名稱 群組名稱 編輯群組 管理和移除您的群組 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index be6b7cd428..0a5b877f30 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,11 +1,9 @@ Aegis Aegis Authenticator - AegisDev Beem Development Settings About - Import Delete Transfer Edit icon @@ -19,7 +17,6 @@ Suggested Usage count - Preferences App Entries Android @@ -43,8 +40,6 @@ Show code in 2-digit grouping instead of 3-digit grouping Show the account name Enable this to show the account name next to the issuer - Timeout - Automatically lock the vault after %1$s seconds of inactivity Import from file Import tokens from a file Android cloud backups @@ -104,8 +99,6 @@ Warning: If you forget your password, you will permanently lose access to your tokens. There is no way to recover them without the password. Biometrics In addition to a password, biometrics registered on this device, like a fingerprint or your face, can be used to unlock the vault. - Password - Enter your password Unlock the vault @@ -135,7 +128,6 @@ Yes No Unlock - Biometrics Advanced Counter Digits @@ -143,14 +135,9 @@ Scan QR code Scan image Enter manually - Add biometric - Add biometric slot Set up biometric unlock - Add password - The vault is only as secure as your weakest secret. If you change the biometric authentication settings of your device, you will need to reactivate biometric unlock within Aegis. Copy Edit - Delete Password Confirm password Show password @@ -174,8 +161,6 @@ Discard changes? Your changes have not been saved - Folder - Tap to select Error saving profile Welcome Aegis is a free, secure and open source 2FA app @@ -207,7 +192,6 @@ %d icon %d icons - Icon pack Custom Permission denied New format (v0.6.3 or newer) @@ -236,17 +220,11 @@ Aegis is not compatible with Microsoft\'s proprietary 2FA algorithm. Please make sure to select \"Setup application without notifications\" when configuring 2FA in Office 365. Raw Unlocking the vault - Unlocking the vault (repairing) - You must have at least one password slot (this devices) - Remove slot - Are you sure you want to remove this slot? Remove group Are you sure you want to remove this group? Entries in this group will automatically switch to \'No group\'. Remove icon pack Are you sure you want to remove this icon pack? Entries that use icons from this pack will not be affected. - An error occurred while trying to add a new slot: - Unable to reset animator duration scale. Progress bars will be invisible. Details Lock Name @@ -259,7 +237,6 @@ Usage count Custom New group… - Enter a group name Group Group name Edit groups From 6f6670668538effa9ab40d3ef426e194d69c23c7 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 6 Mar 2022 13:54:14 +0100 Subject: [PATCH 032/389] Don't pass MIME type along when selecting icon pack file to import Fixes #757 --- .../ui/fragments/preferences/IconPacksManagerFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/IconPacksManagerFragment.java b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/IconPacksManagerFragment.java index 13e52379da..c73b9fe541 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/IconPacksManagerFragment.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/fragments/preferences/IconPacksManagerFragment.java @@ -150,7 +150,7 @@ private boolean removeIconPack(IconPack pack) { private void startImportIconPack() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("application/zip"); + intent.setType("*/*"); _vaultManager.startActivityForResult(this, intent, CODE_IMPORT); } From 71eb6b133cc8607787e8b85a1676cbbfca674edd Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 6 Mar 2022 14:54:36 +0100 Subject: [PATCH 033/389] Remove SlotManagerActivity from AndroidManifest --- app/src/main/AndroidManifest.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0f3894288..83ec8c1ea3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -74,9 +74,6 @@ - From 903948d57c5eede93505e7b43be26ccc8e042230 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Wed, 16 Mar 2022 14:43:13 +0100 Subject: [PATCH 034/389] Stop using dependency injection in AegisBackupAgent Because the app launches in a restricted mode when restoring a backup, dependency injection with Dagger Hilt doesn't work inside BackupAgent. This would cause backup restore operations to fail. This issue got introduced by the recent switch to Dagger Hilt, which has not been included in a release of Aegis yet. --- .../aegis/AegisBackupAgent.java | 21 +++---------------- .../aegis/vault/VaultManager.java | 2 +- .../aegis/vault/VaultRepository.java | 14 ++++++------- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java b/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java index df989a7d79..dc7dd0024f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java +++ b/app/src/main/java/com/beemdevelopment/aegis/AegisBackupAgent.java @@ -9,7 +9,6 @@ import android.util.Log; import com.beemdevelopment.aegis.util.IOUtils; -import com.beemdevelopment.aegis.vault.VaultManager; import com.beemdevelopment.aegis.vault.VaultRepository; import java.io.File; @@ -17,24 +16,17 @@ import java.io.IOException; import java.io.InputStream; -import dagger.hilt.InstallIn; -import dagger.hilt.android.EarlyEntryPoint; -import dagger.hilt.android.EarlyEntryPoints; -import dagger.hilt.components.SingletonComponent; - public class AegisBackupAgent extends BackupAgent { private static final String TAG = AegisBackupAgent.class.getSimpleName(); - private VaultManager _vaultManager; private Preferences _prefs; @Override public void onCreate() { super.onCreate(); - EntryPoint entryPoint = EarlyEntryPoints.get(this, EntryPoint.class); - _vaultManager = entryPoint.getVaultManager(); - _prefs = entryPoint.getPreferences(); + // cannot use injection with Dagger Hilt here, because the app is launched in a restricted mode on restore + _prefs = new Preferences(this); } @Override @@ -57,7 +49,7 @@ public synchronized void onFullBackup(FullBackupDataOutput data) throws IOExcept createBackupDir(); File vaultBackupFile = getVaultBackupFile(); try { - _vaultManager.getVault().backupTo(vaultBackupFile); + VaultRepository.copyFileTo(this, vaultBackupFile); } catch (IOException e) { Log.e(TAG, String.format("onFullBackup() failed: %s", e)); deleteBackupDir(); @@ -128,11 +120,4 @@ private void deleteBackupDir() { private File getVaultBackupFile() { return new File(new File(getFilesDir(), "backup"), VaultRepository.FILENAME); } - - @EarlyEntryPoint - @InstallIn(SingletonComponent.class) - interface EntryPoint { - Preferences getPreferences(); - VaultManager getVaultManager(); - } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java index 7d92bda39b..375a2ef667 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultManager.java @@ -189,7 +189,7 @@ public void scheduleBackup() throws VaultRepositoryException { } File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir); - getVault().backupTo(tempFile); + VaultRepository.copyFileTo(_context, tempFile); _backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount()); } catch (IOException e) { throw new VaultRepositoryException(e); diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java index aa85780341..f888e6ad05 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultRepository.java @@ -107,6 +107,13 @@ public static VaultRepository fromFile(Context context, VaultFile file, VaultFil return new VaultRepository(context, vault, creds); } + public static void copyFileTo(Context context, File destFile) throws IOException { + try (InputStream inStream = VaultRepository.getAtomicFile(context).openRead(); + OutputStream outStream = new FileOutputStream(destFile)) { + IOUtils.copy(inStream, outStream); + } + } + void save() throws VaultRepositoryException { try { JSONObject obj = _vault.toJson(); @@ -172,13 +179,6 @@ public void exportGoogleUris(OutputStream outStream) throws VaultRepositoryExcep } } - public void backupTo(File destFile) throws IOException { - try (InputStream inStream = getAtomicFile(_context).openRead(); - OutputStream outStream = new FileOutputStream(destFile)) { - IOUtils.copy(inStream, outStream); - } - } - public void addEntry(VaultEntry entry) { _vault.getEntries().add(entry); } From b8fa67304a2736e59fd570b7fa0816a6b026f5ae Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Sun, 27 Mar 2022 21:37:16 +0200 Subject: [PATCH 035/389] Release v2.0.3 (cherry picked from commit 32ee0915a74ac5dc43cebee333c6d614a050f7e4) --- app/build.gradle | 4 ++-- app/src/main/assets/changelog.html | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d6eed9ec46..0b6ecedeef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,8 +25,8 @@ android { applicationId "${packageName}" minSdkVersion 21 targetSdkVersion 31 - versionCode 50 - versionName "2.0.2" + versionCode 51 + versionName "2.0.3" multiDexEnabled true buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\"" buildConfigField "String", "GIT_BRANCH", "\"${getGitBranch()}\"" diff --git a/app/src/main/assets/changelog.html b/app/src/main/assets/changelog.html index c4e0307944..731c38da45 100644 --- a/app/src/main/assets/changelog.html +++ b/app/src/main/assets/changelog.html @@ -31,6 +31,11 @@
+

Version 2.0.3

+

New

+
    +
  • Support for importing 2FAS Authenticator's new backup format
  • +

Version 2.0.2

New