Compare commits

...
Author SHA1 Message Date
Alexander Bakker adb80dfe43 Release v1.2.1 2020-07-10 20:58:40 +02:00
Alexander Bakker 0f439506bd Insert a state sanity check at the end of the intro
This is a temporary 'fix' for a rare issue where the intro could end up in a bad state
2020-07-10 20:57:22 +02:00
Alexander Bakker 6e54497492 Release v1.2 2020-06-16 17:31:04 +02:00
Alexander BakkerandGitHub 9fde453ead Merge pull request #472 from michaelschattgen/feature/fix-menu-title
Fix menu item title for transfer
2020-06-16 13:54:04 +02:00
Michael Schättgen 7cd48f1c09 Fix menu item title for transfer 2020-06-16 13:35:54 +02:00
Michael SchättgenandGitHub 4c3d277fce Merge pull request #465 from alexbakker/backup-error-bar
Improve backup error handling and frequency
2020-06-16 09:52:37 +02:00
Michael SchättgenandGitHub 0111626d80 Merge pull request #467 from alexbakker/fix-refresh-issue
Fix an issue where codes were not refreshed
2020-06-16 09:20:17 +02:00
Alexander Bakker 0b83546db7 Fix an issue where codes were not refreshed
Sometimes I get so enthusiastic when refactoring that I accidentally remove too
much. This issue was introduced in 98a38b03e4
2020-06-14 20:50:18 +02:00
Alexander Bakker 08ab8237e7 Improve backup error handling and frequency
This patch improves our backup functionality in a number of ways:
- Only backup the vault when important changes are made, not when the order of
  entries is changed, for instance.
- Don't bubble up backup errors when saving the vault.
- Instead, show an error bar in the main view if the most recent backup attempt
  failed.

<img src="https://alexbakker.me/u/kbhhj2hcgx.png" width="300" />

Clicking on the error bar will take the user to the backup settings.
2020-06-14 20:39:09 +02:00
Alexander Bakker ae5502b650 Release v1.2-beta5 2020-06-10 14:59:22 +02:00
Michael SchättgenandGitHub 821d0b5147 Merge pull request #460 from alexbakker/fix-intro-back
Disable wizard mode when the done slide of the intro is reached
2020-06-10 10:51:35 +02:00
Alexander Bakker b4ed1f35ce Disable wizard mode when the done slide of the intro is reached
This fixes a number of issues that can happen when navigating back to previous
slides, by disabling back navigation entirely. One such issue is that when
navigating back from the last slide, one would always land on the
password/biometrics setup slide, even if "none" was selected on the security
picker slide.
2020-06-10 09:59:37 +02:00
Alexander BakkerandGitHub a468342187 Merge pull request #455 from michaelschattgen/feature/lock-screen
Add lockscreen overhaul
2020-06-08 17:49:35 +02:00
Michael Schättgen 4f6035dc49 Add lockscreen overhaul 2020-06-08 10:35:44 +02:00
Michael SchättgenandGitHub 506adcf81f Merge pull request #459 from alexbakker/fix-371
Don't allow drag and drop if the entry list is filtered in any way
2020-06-07 23:33:21 +02:00
Michael SchättgenandGitHub 49c8c2d57d Merge pull request #458 from alexbakker/respect-anim-settings
Respect the global animator duration scale setting
2020-06-07 23:30:34 +02:00
Michael SchättgenandGitHub 2ab698c2d3 Merge pull request #457 from alexbakker/rename-slides
Rename the security slides of the intro
2020-06-07 23:09:07 +02:00
Alexander Bakker 770f5f9724 Rename the security slides of the intro
These names make it a bit more clear what each slide is for
2020-06-07 18:16:00 +02:00
Alexander Bakker 992dcd651b Don't allow drag and drop if the entry list is filtered in any way 2020-06-07 17:18:19 +02:00
Alexander Bakker 98a38b03e4 Respect the global animator duration scale setting
This changes the way the TOTP progress bar works, so that we can respect the
global animator duration scale setting.
2020-06-07 17:02:59 +02:00
Michael SchättgenandGitHub 626995ec91 Merge pull request #445 from alexbakker/fix-reminder
Fix an issue where the password reminder popup was not shown
2020-06-06 13:51:53 +02:00
Michael SchättgenandGitHub 00b0c8deaf Merge pull request #439 from alexbakker/copy-on-tap-option
Add an option to copy tokens on tap (and disable it by default)
2020-06-06 13:51:44 +02:00
Michael SchättgenandGitHub 8e5b51c42f Merge pull request #454 from alexbakker/fix-andotp-iters
Fix importing andOTP backups with more than 10000 PBKDF iterations
2020-06-06 13:50:33 +02:00
Alexander Bakker b81ec0f073 Fix importing andOTP backups with more than 10000 PBKDF iterations
These larger numbers of iterations cause the key derivation process to take
longer, so I also moved that task to the background. andOTP now also has a
proper "issuer" field, so we make use of that as well.

Also fixes an issue where padded base32 could not be decoded. This issue is only
present for the andOTP importer as far as I know, so that's why that change is
included here.
2020-06-06 13:44:23 +02:00
Alexander Bakker d49d42254b Add an option to copy tokens on tap (and disable it by default) 2020-06-06 12:42:27 +02:00
Michael SchättgenandGitHub 95d5b4afa1 Merge pull request #437 from alexbakker/fix-copy-anim
Fix issue where the copy animation continued after view holder rebind
2020-06-06 12:24:26 +02:00
Alexander BakkerandGitHub cca8c9bdf7 Merge pull request #428 from michaelschattgen/feature/qr-transfer
Add ability to transfer tokens with qr codes
2020-06-06 12:24:14 +02:00
Michael Schättgen 4e00191039 Add ability to transfer tokens with qr codes
Add minor improvements

Add label to activity
2020-06-06 12:23:28 +02:00
Michael SchättgenandGitHub dd09595866 Merge pull request #435 from alexbakker/fix-363
Don't add new entries to the view if restoring from instance state
2020-06-06 12:22:47 +02:00
Michael SchättgenandGitHub b27bfc3fa9 Merge pull request #432 from alexbakker/fix-252
Improve icon editing flow
2020-06-06 11:56:54 +02:00
Michael SchättgenandGitHub c6a9716d16 Merge pull request #431 from alexbakker/update-deps-again
Update dependencies and sort them alphabetically
2020-06-06 11:54:12 +02:00
Michael SchättgenandGitHub acd7a54ee9 Merge pull request #430 from orangenbaumblatt/feature/improve-action-bar-consistency
Improve Action Bar consistency for Settings and About
2020-06-04 01:29:44 +02:00
Alexander BakkerandGitHub e7d240549e Merge pull request #452 from lotusexpeditor/master
Add Turkish Translation
2020-06-02 11:25:27 +02:00
lotusexpeditorandGitHub 70fedc53b9 Add Turkish Translation 2020-06-02 04:13:56 +03:00
Alexander Bakker bed8c9172d Release v1.2-beta4 2020-05-30 13:52:20 +02:00
Michael SchättgenandGitHub ef1ac3e5e9 Merge pull request #441 from alexbakker/fix-lock-crash
Fix crash that would occur when locking the screen with a locked vault
2020-05-30 13:06:25 +02:00
Alexander Bakker cc163f952f Fix an issue where the password reminder popup was not shown
We no longer create BiometricPrompt prematurely, but forgot to adjust the check for the password reminder.
2020-05-28 11:55:56 +02:00
Michael SchättgenandGitHub 95180ba963 Merge pull request #444 from alexbakker/fix-export-backup
Fix an issue where backups where enabled when exporting the vault
2020-05-28 10:01:19 +02:00
Alexander Bakker 56d7f0ef7b Fix an issue where backups where enabled when exporting the vault
This is really stupid.
2020-05-28 09:54:29 +02:00
Alexander Bakker fbd6a5931e Fix crash that would occur when locking the screen with a locked vault 2020-05-26 11:33:35 +02:00
Alexander Bakker 66dd3561cf Release v1.2-beta3 2020-05-25 17:32:32 +02:00
Michael SchättgenandGitHub 58db3fdaa2 Merge pull request #438 from alexbakker/fix-intro-creds
Fix issue where VaultManager was erroneously initialized with creds in intro
2020-05-25 16:41:35 +02:00
Alexander Bakker 92b6738ff4 Fix issue where VaultManager was erroneously initialized with creds in intro
Also fixes another issue where previously, if a user made it to the last intro
slide and then navigated back to change the security options, any changes would
be ignored.
2020-05-25 16:29:14 +02:00
Alexander Bakker 63f57667ba Fix issue where the copy animation continued after view holder rebind 2020-05-25 12:02:21 +02:00
Alexander Bakker 34718a7b89 Don't add new entries to the view if restoring from instance state
Also addresses other potential issues with the same cause. Fixes #363.
2020-05-24 20:24:04 +02:00
orangenbaumblatt 6ac91e6cf3 Improve Action Bar consistency for Settings and About
Change Group Manager back icon
2020-05-24 15:10:59 +02:00
Alexander Bakker a39d6ab346 Improve icon editing flow
This improves the entry icon editing flow as suggested in #252:
- Add an "Edit icon" menu item
- Save the icon even if the checkmark was not clicked
- Exit icon edit mode with the back button

Close #252.
2020-05-24 13:49:30 +02:00
Alexander Bakker 91d89670d2 Update dependencies and sort them alphabetically 2020-05-24 12:52:02 +02:00
Alexander Bakker 6a2992530e Release v1.2-beta2 2020-05-16 21:36:52 +02:00
Alexander Bakker 1f839a886d Don't replace the Bouncy Castle security provider
We only use Bouncy Castle for scrypt, so replacing the security provider was a
nice to have. It's causing issues because Proguard removes Bouncy Castle
classes. As we just released a beta, this is a quick fix and we may revisit this
later.
2020-05-16 21:32:13 +02:00
53 changed files with 1401 additions and 377 deletions
+31 -24
View File
@@ -20,8 +20,8 @@ android {
applicationId "com.beemdevelopment.aegis"
minSdkVersion 21
targetSdkVersion 29
versionCode 30
versionName "1.2-beta1"
versionCode 36
versionName "1.2.1"
multiDexEnabled true
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
buildConfigField "String", "GIT_BRANCH", "\"${getGitBranch()}\""
@@ -91,37 +91,44 @@ protobuf {
}
dependencies {
def glideVersion = '4.11.0'
def guavaVersion = '29.0'
def junitVersion = '5.6.2'
def libsuVersion = '2.5.1'
annotationProcessor 'androidx.annotation:annotation:1.1.0'
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.protobuf:protobuf-javalite:3.8.0'
implementation 'net.lingala.zip4j:zip4j:2.5.2'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation "androidx.biometric:biometric:1.0.1"
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.guava:guava:28.2-android'
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'com.github.apl-devs:appintro:6.0.0'
implementation 'com.github.avito-tech:krop:0.44'
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
implementation 'de.hdodenhof:circleimageview:3.0.1'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
implementation "com.github.topjohnwu.libsu:io:${libsuVersion}"
implementation 'com.github.bumptech.glide:annotations:4.10.0'
implementation 'com.github.bumptech.glide:glide:4.10.0'
implementation "com.mikepenz:iconics-core:3.2.5"
implementation 'com.mikepenz:material-design-iconic-typeface:2.2.0.5@aar'
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
implementation("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
implementation "com.github.bumptech.glide:annotations:${glideVersion}"
implementation "com.github.bumptech.glide:glide:${glideVersion}"
implementation("com.github.bumptech.glide:recyclerview-integration:${glideVersion}") {
transitive = false
}
annotationProcessor 'androidx.annotation:annotation:1.1.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
testImplementation 'com.google.guava:guava:28.2-jre"'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
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.1.0'
implementation 'com.google.protobuf:protobuf-javalite:3.12.1'
implementation "com.mikepenz:iconics-core:3.2.5"
implementation 'com.mikepenz:material-design-iconic-typeface:2.2.0.5@aar'
implementation 'de.hdodenhof:circleimageview:3.1.0'
implementation 'de.psdev.licensesdialog:licensesdialog:2.1.0'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'net.lingala.zip4j:zip4j:2.6.0'
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
testImplementation "com.google.guava:guava:${guavaVersion}-jre"
testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
}
+13 -4
View File
@@ -6,8 +6,12 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application
android:name=".AegisApplication"
@@ -18,6 +22,8 @@
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:theme">
<activity android:name=".ui.TransferEntriesActivity"
android:label="@string/title_activity_transfer" />
<activity
android:name=".ui.AboutActivity"
android:label="@string/title_activity_about" />
@@ -53,8 +59,11 @@
<activity
android:name=".ui.IntroActivity"
android:theme="@style/Theme.Intro" />
<activity android:name=".ui.AuthActivity" />
<activity android:name=".ui.PreferencesActivity" />
<activity android:name=".ui.AuthActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".ui.PreferencesActivity"
android:label="@string/title_activity_preferences" />
<activity
android:name=".ui.SlotManagerActivity"
android:label="Manage key slots" />
+12
View File
@@ -31,6 +31,11 @@
</head>
<body>
<div></div>
<h3>Version 1.2.1</h3>
<h4>Fixes</h4>
<ul>
<li>Fix a rare issue where the intro could end up in a bad state</li>
</ul>
<h3>Version 1.2</h3>
<h4>New</h4>
<ul>
@@ -41,6 +46,7 @@
<li>Add support for importing a plain text Google Authenticator URI file</li>
<li>Add support for importing from the new Google Authenticator export QR codes</li>
<li>Add support for otpauth://steam URI's</li>
<li>Add an option to copy tokens on tap (and disable it by default)</li>
<li>Improve method to notify users on copy</li>
<li>Add support for backups</li>
<li>Improve multiselect flow</li>
@@ -49,17 +55,23 @@
<li>Use most frequent period to show progress</li>
<li>Append a timestamp to the filename of exported vaults</li>
<li>Add Hungarian translation</li>
<li>Add Turkish translation</li>
<li>Display a warning if automatic time sync is not enabled</li>
<li>Minor card entry layout overhaul</li>
<li>Ability to transfer tokens with qr codes</li>
<li>Lockscreen overhaul</li>
</ul>
<h4>Fixes</h4>
<ul>
<li>Improve overall exception handling and error feedback to the user</li>
<li>Improve icon editing flow</li>
<li>Protect writes of the vault file against corruption with AtomicFile</li>
<li>Make the parsing logic of the QR code URI more robust</li>
<li>Importing from Authy now asks for password if needed</li>
<li>Update Russian localization</li>
<li>Increase password reminder period to 30 days</li>
<li>Fix importing andOTP backups with more than 10000 PBKDF iterations</li>
<li>Respect the global animator duration scale setting</li>
</ul>
<p>Various other minor improvements</p>
<h3>Version 1.1.4</h3>
@@ -112,7 +112,7 @@ public class AegisApplication extends Application {
}
public boolean isAutoLockEnabled() {
return _prefs.isAutoLockEnabled() && _manager.isEncryptionEnabled() && !isVaultLocked();
return _prefs.isAutoLockEnabled() && !isVaultLocked() && _manager.isEncryptionEnabled() ;
}
public void registerLockListener(LockListener listener) {
@@ -160,6 +160,14 @@ public class Preferences {
_prefs.edit().putInt("pref_backups_versions", versions).apply();
}
public void setBackupsError(Exception e) {
_prefs.edit().putString("pref_backups_error", e == null ? null : e.toString()).apply();
}
public String getBackupsError() {
return _prefs.getString("pref_backups_error", null);
}
public boolean isTimeSyncWarningEnabled() {
return _prefs.getBoolean("pref_warn_time_sync", true);
}
@@ -167,4 +175,8 @@ public class Preferences {
public void setIsTimeSyncWarningEnabled(boolean enabled) {
_prefs.edit().putBoolean("pref_warn_time_sync", enabled).apply();
}
public boolean isCopyOnTapEnabled() {
return _prefs.getBoolean("pref_copy_on_tap", false);
}
}
@@ -3,7 +3,6 @@ package com.beemdevelopment.aegis.crypto;
import android.os.Build;
import org.bouncycastle.crypto.generators.SCrypt;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -13,9 +12,7 @@ import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
@@ -39,15 +36,6 @@ public class CryptoUtils {
public static final int CRYPTO_SCRYPT_r = 8;
public static final int CRYPTO_SCRYPT_p = 1;
// replace the BC provider from Android with the one bundled with the app
static {
final Provider provider = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
if (provider != null && !provider.getClass().equals(BouncyCastleProvider.class)) {
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
Security.addProvider(new BouncyCastleProvider());
}
}
public static SecretKey deriveKey(byte[] input, SCryptParameters params) {
byte[] keyBytes = SCrypt.generate(input, params.getSalt(), params.getN(), params.getR(), params.getP(), CRYPTO_AEAD_KEY_SIZE);
return new SecretKeySpec(keyBytes, 0, keyBytes.length, "AES");
@@ -3,21 +3,19 @@ package com.beemdevelopment.aegis.encoding;
import com.google.common.io.BaseEncoding;
public class Base32 {
private static final BaseEncoding _encoding = BaseEncoding.base32().omitPadding();
private Base32() {
}
public static byte[] decode(String s) throws EncodingException {
try {
return _encoding.decode(s.toUpperCase());
return BaseEncoding.base32().decode(s.toUpperCase());
} catch (IllegalArgumentException e) {
throw new EncodingException(e);
}
}
public static String encode(byte[] data) {
return _encoding.encode(data);
return BaseEncoding.base32().omitPadding().encode(data);
}
}
@@ -8,7 +8,6 @@ import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.CryptParameters;
import com.beemdevelopment.aegis.crypto.CryptResult;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.HotpInfo;
@@ -17,6 +16,8 @@ import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.ui.Dialogs;
import com.beemdevelopment.aegis.ui.tasks.ProgressDialogTask;
import com.beemdevelopment.aegis.vault.VaultEntry;
import org.json.JSONArray;
import org.json.JSONException;
@@ -49,8 +50,6 @@ public class AndOtpImporter extends DatabaseImporter {
private static final int SALT_SIZE = 12;
private static final int KEY_SIZE = 256; // bits
private static final int MAX_ITERATIONS = 10000;
public AndOtpImporter(Context context) {
super(context);
}
@@ -96,38 +95,12 @@ public class AndOtpImporter extends DatabaseImporter {
_data = data;
}
private DecryptedState decrypt(char[] password, boolean oldFormat) throws DatabaseImporterException {
private DecryptedState decryptData(SecretKey key, int offset) throws DatabaseImporterException {
byte[] nonce = Arrays.copyOfRange(_data, offset, offset + NONCE_SIZE);
byte[] tag = Arrays.copyOfRange(_data, _data.length - TAG_SIZE, _data.length);
CryptParameters params = new CryptParameters(nonce, tag);
try {
SecretKey key;
int offset = 0;
if (oldFormat) {
// WARNING: DON'T DO THIS IN YOUR OWN CODE
// this exists solely to support the old andOTP backup format
// it is not a secure way to derive a key from a password
MessageDigest hash = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
key = new SecretKeySpec(keyBytes, "AES");
} else {
offset = INT_SIZE + SALT_SIZE;
byte[] iterBytes = Arrays.copyOfRange(_data, 0, INT_SIZE);
int iterations = ByteBuffer.wrap(iterBytes).getInt();
if (iterations < 1 || iterations > MAX_ITERATIONS) {
throw new DatabaseImporterException(String.format("Invalid number of iterations for PBKDF: %d", iterations));
}
byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, offset);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, iterations, KEY_SIZE);
key = factory.generateSecret(spec);
}
// extract nonce and tag
byte[] nonce = Arrays.copyOfRange(_data, offset, offset + NONCE_SIZE);
byte[] tag = Arrays.copyOfRange(_data, _data.length - TAG_SIZE, _data.length);
CryptParameters params = new CryptParameters(nonce, tag);
Cipher cipher = CryptoUtils.createDecryptCipher(key, nonce);
int len = _data.length - offset - NONCE_SIZE - TAG_SIZE;
CryptResult result = CryptoUtils.decrypt(_data, offset + NONCE_SIZE, len, cipher, params);
@@ -137,13 +110,50 @@ public class AndOtpImporter extends DatabaseImporter {
} catch (NoSuchAlgorithmException
| InvalidAlgorithmParameterException
| InvalidKeyException
| InvalidKeySpecException
| NoSuchPaddingException
| IllegalBlockSizeException e) {
throw new RuntimeException(e);
}
}
private void decrypt(Context context, char[] password, boolean oldFormat, DecryptListener listener) throws DatabaseImporterException {
if (oldFormat) {
// WARNING: DON'T DO THIS IN YOUR OWN CODE
// this exists solely to support the old andOTP backup format
// it is not a secure way to derive a key from a password
MessageDigest hash;
try {
hash = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
SecretKey key = new SecretKeySpec(keyBytes, "AES");
DecryptedState state = decryptData(key, 0);
listener.onStateDecrypted(state);
} else {
int offset = INT_SIZE + SALT_SIZE;
byte[] iterBytes = Arrays.copyOfRange(_data, 0, INT_SIZE);
int iterations = ByteBuffer.wrap(iterBytes).getInt();
if (iterations < 1) {
throw new DatabaseImporterException(String.format("Invalid number of iterations for PBKDF: %d", iterations));
}
byte[] salt = Arrays.copyOfRange(_data, INT_SIZE, offset);
AndOtpKeyDerivationTask.Params params = new AndOtpKeyDerivationTask.Params(password, salt, iterations);
AndOtpKeyDerivationTask task = new AndOtpKeyDerivationTask(context, key1 -> {
try {
DecryptedState state = decryptData(key1, offset);
listener.onStateDecrypted(state);
} catch (DatabaseImporterException e) {
listener.onError(e);
}
});
task.execute(params);
}
}
@Override
public void decrypt(Context context, DecryptListener listener) {
String[] choices = new String[]{
@@ -158,8 +168,7 @@ public class AndOtpImporter extends DatabaseImporter {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
Dialogs.showPasswordInputDialog(context, password -> {
try {
DecryptedState state = decrypt(password, i != 0);
listener.onStateDecrypted(state);
decrypt(context, password, i != 0, listener);
} catch (DatabaseImporterException e) {
listener.onError(e);
}
@@ -221,12 +230,17 @@ public class AndOtpImporter extends DatabaseImporter {
String name;
String issuer = "";
String[] parts = obj.getString("label").split(" - ");
if (parts.length > 1) {
issuer = parts[0];
name = parts[1];
if (obj.has("issuer")) {
name = obj.getString("label");
issuer = obj.getString("issuer");
} else {
name = parts[0];
String[] parts = obj.getString("label").split(" - ");
if (parts.length > 1) {
issuer = parts[0];
name = parts[1];
} else {
name = parts[0];
}
}
return new VaultEntry(info, name, issuer);
@@ -235,4 +249,64 @@ public class AndOtpImporter extends DatabaseImporter {
}
}
}
private static class AndOtpKeyDerivationTask extends ProgressDialogTask<AndOtpKeyDerivationTask.Params, SecretKey> {
private Callback _cb;
public AndOtpKeyDerivationTask(Context context, Callback cb) {
super(context, context.getString(R.string.unlocking_vault));
_cb = cb;
}
@Override
protected SecretKey doInBackground(AndOtpKeyDerivationTask.Params... args) {
setPriority();
AndOtpKeyDerivationTask.Params params = args[0];
SecretKey key;
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(params.getPassword(), params.getSalt(), params.getIterations(), KEY_SIZE);
key = factory.generateSecret(spec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new RuntimeException(e);
}
return key;
}
@Override
protected void onPostExecute(SecretKey key) {
super.onPostExecute(key);
_cb.onTaskFinished(key);
}
public static class Params {
private char[] _password;
private byte[] _salt;
private int _iterations;
public Params(char[] password, byte[] salt, int iterations) {
_iterations = iterations;
_password = password;
_salt = salt;
}
public char[] getPassword() {
return _password;
}
public int getIterations() {
return _iterations;
}
public byte[] getSalt() {
return _salt;
}
}
public interface Callback {
void onTaskFinished(SecretKey key);
}
}
}
@@ -8,10 +8,11 @@ import com.beemdevelopment.aegis.encoding.Base64;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
public class GoogleAuthInfo {
public class GoogleAuthInfo implements Serializable {
public static final String SCHEME = "otpauth";
public static final String SCHEME_EXPORT = "otpauth-migration";
@@ -6,6 +6,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
@@ -43,6 +44,11 @@ public class AboutActivity extends AegisActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
View btnLicenses = findViewById(R.id.btn_licenses);
btnLicenses.setOnClickListener(v -> showLicenseDialog());
@@ -133,4 +139,17 @@ public class AboutActivity extends AegisActivity {
private String getThemeColorAsHex(@AttrRes int attributeId) {
return String.format("%06X", (0xFFFFFF & ThemeHelper.getThemeColor(attributeId, getTheme())));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
}
@@ -1,10 +1,8 @@
package com.beemdevelopment.aegis.ui;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.provider.Settings;
import android.view.WindowManager;
import android.widget.Toast;
@@ -47,9 +45,6 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
// apply a dirty hack to make progress bars work even if animations are disabled
setGlobalAnimationDurationScale();
// register a callback to listen for lock events
_app.registerLockListener(this);
}
@@ -127,9 +122,9 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
this.getResources().updateConfiguration(config, this.getResources().getDisplayMetrics());
}
protected boolean saveVault() {
protected boolean saveVault(boolean backup) {
try {
getApp().getVaultManager().save();
getApp().getVaultManager().save(backup);
return true;
} catch (VaultManagerException e) {
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();
@@ -152,17 +147,6 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
return !(this instanceof MainActivity) && _app.isVaultLocked();
}
private void setGlobalAnimationDurationScale() {
float durationScale = Settings.Global.getFloat(getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, 0);
if (durationScale != 1) {
try {
ValueAnimator.class.getMethod("setDurationScale", float.class).invoke(null, 1f);
} catch (Throwable t) {
Toast.makeText(this, R.string.progressbar_error, Toast.LENGTH_SHORT).show();
}
}
}
protected Theme getCurrentTheme() {
return _currentTheme;
}
@@ -2,6 +2,7 @@ package com.beemdevelopment.aegis.ui;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
@@ -14,6 +15,7 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -24,6 +26,7 @@ import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.CancelAction;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.crypto.MasterKey;
@@ -33,7 +36,6 @@ import com.beemdevelopment.aegis.helpers.UiThreadExecutor;
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
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.beemdevelopment.aegis.vault.slots.BiometricSlot;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
@@ -68,7 +70,7 @@ public class AuthActivity extends AegisActivity {
_textPassword = findViewById(R.id.text_password);
LinearLayout boxBiometricInfo = findViewById(R.id.box_biometric_info);
Button decryptButton = findViewById(R.id.button_decrypt);
Button biometricsButton = findViewById(R.id.button_biometrics);
TextView biometricsButton = findViewById(R.id.button_biometrics);
_textPassword.setOnEditorActionListener((v, actionId, event) -> {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
@@ -126,6 +128,7 @@ public class AuthActivity extends AegisActivity {
// display a help message if a matching invalidated keystore entry was found
if (invalidated) {
boxBiometricInfo.setVisibility(View.VISIBLE);
biometricsButton.setVisibility(View.GONE);
}
}
@@ -144,6 +147,34 @@ public class AuthActivity extends AegisActivity {
});
}
@Override
protected void setPreferredTheme(Theme theme) {
if (theme == Theme.SYSTEM || theme == Theme.SYSTEM_AMOLED) {
// set the theme based on the system theme
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
theme = Theme.LIGHT;
break;
case Configuration.UI_MODE_NIGHT_YES:
theme = theme == Theme.SYSTEM_AMOLED ? Theme.AMOLED : Theme.DARK;
break;
}
}
switch (theme) {
case LIGHT:
setTheme(R.style.AppTheme_Light_NoActionBar);
break;
case DARK:
setTheme(R.style.AppTheme_Dark_NoActionBar);
break;
case AMOLED:
setTheme(R.style.AppTheme_TrueBlack_NoActionBar);
break;
}
}
private void selectPassword() {
_textPassword.selectAll();
@@ -178,7 +209,7 @@ public class AuthActivity extends AegisActivity {
@Override
public void onAttachedToWindow() {
if (_bioPrompt != null && _prefs.isPasswordReminderNeeded()) {
if (_bioKey != null && _prefs.isPasswordReminderNeeded()) {
showPasswordReminder();
}
}
@@ -241,10 +272,9 @@ public class AuthActivity extends AegisActivity {
} else {
try {
AegisApplication app = getApp();
VaultManager vault = app.initVaultManager(app.loadVaultFile(), creds);
app.initVaultManager(app.loadVaultFile(), creds);
if (isSlotRepaired) {
vault.setCredentials(creds);
saveVault();
saveVault(true);
}
} catch (VaultManagerException e) {
e.printStackTrace();
@@ -6,6 +6,7 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@@ -67,6 +68,7 @@ public class EditEntryActivity extends AegisActivity {
private boolean _hasCustomIcon = false;
// keep track of icon changes separately as the generated jpeg's are not deterministic
private boolean _hasChangedIcon = false;
private boolean _isEditingIcon;
private CircleImageView _iconView;
private ImageView _saveImageButton;
@@ -245,11 +247,7 @@ public class EditEntryActivity extends AegisActivity {
});
_iconView.setOnClickListener(v -> {
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
Intent chooserIntent = Intent.createChooser(galleryIntent, "Select photo");
startActivityForResult(Intent.createChooser(chooserIntent, "Select photo"), PICK_IMAGE_REQUEST);
startIconSelectionActivity();
});
_advancedSettingsHeader.setOnClickListener(v -> openAdvancedSettings());
@@ -326,6 +324,11 @@ public class EditEntryActivity extends AegisActivity {
@Override
public void onBackPressed() {
if (_isEditingIcon) {
stopEditingIcon(false);
return;
}
AtomicReference<String> msg = new AtomicReference<>();
AtomicReference<VaultEntry> entry = new AtomicReference<>();
@@ -370,6 +373,9 @@ public class EditEntryActivity extends AegisActivity {
deleteAndFinish(_origEntry);
});
break;
case R.id.action_edit_icon:
startIconSelectionActivity();
break;
case R.id.action_default_icon:
TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
_iconView.setImageDrawable(drawable);
@@ -382,6 +388,52 @@ public class EditEntryActivity extends AegisActivity {
return true;
}
private void startIconSelectionActivity() {
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.select_icon));
startActivityForResult(Intent.createChooser(chooserIntent, getString(R.string.select_icon)), PICK_IMAGE_REQUEST);
}
private void startEditingIcon(Uri data) {
Glide.with(this)
.asBitmap()
.load(data)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(false)
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
_kropView.setBitmap(resource);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
_iconView.setVisibility(View.GONE);
_kropView.setVisibility(View.VISIBLE);
_saveImageButton.setOnClickListener(v -> {
stopEditingIcon(true);
});
_isEditingIcon = true;
}
private void stopEditingIcon(boolean save) {
if (save) {
_iconView.setImageBitmap(_kropView.getCroppedBitmap());
}
_iconView.setVisibility(View.VISIBLE);
_kropView.setVisibility(View.GONE);
_hasCustomIcon = _hasCustomIcon || save;
_hasChangedIcon = save;
_isEditingIcon = false;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_edit, menu);
@@ -415,7 +467,7 @@ public class EditEntryActivity extends AegisActivity {
intent.putExtra("entryUUID", entry.getUUID());
intent.putExtra("delete", delete);
if (saveVault()) {
if (saveVault(true)) {
setResult(RESULT_OK, intent);
finish();
}
@@ -424,35 +476,7 @@ public class EditEntryActivity extends AegisActivity {
@Override
protected void onActivityResult(int requestCode, final int resultCode, Intent data) {
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
Glide.with(this)
.asBitmap()
.load(data.getData())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(false)
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
_kropView.setBitmap(resource);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
_iconView.setVisibility(View.GONE);
_kropView.setVisibility(View.VISIBLE);
_saveImageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
_iconView.setImageBitmap(_kropView.getCroppedBitmap());
_iconView.setVisibility(View.VISIBLE);
_kropView.setVisibility(View.GONE);
_hasCustomIcon = true;
_hasChangedIcon = true;
}
});
startEditingIcon(data.getData());
}
super.onActivityResult(requestCode, resultCode, data);
@@ -556,6 +580,10 @@ public class EditEntryActivity extends AegisActivity {
}
private boolean onSave() {
if (_isEditingIcon) {
stopEditingIcon(true);
}
VaultEntry entry;
try {
entry = parseEntry();
@@ -11,7 +11,6 @@ import java.text.Collator;
import java.util.ArrayList;
import java.util.TreeSet;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -29,9 +28,10 @@ public class GroupManagerActivity extends AegisActivity implements GroupAdapter.
_groups = new TreeSet<>(Collator.getInstance());
_groups.addAll(intent.getStringArrayListExtra("groups"));
ActionBar bar = getSupportActionBar();
bar.setHomeAsUpIndicator(R.drawable.ic_close);
bar.setDisplayHomeAsUpEnabled(true);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
// set up the recycler view
_adapter = new GroupAdapter(this);
@@ -8,23 +8,30 @@ import androidx.fragment.app.Fragment;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticatedSlide;
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticationSlide;
import com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide;
import com.beemdevelopment.aegis.ui.slides.SecuritySetupSlide;
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.slots.BiometricSlot;
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
import com.github.appintro.AppIntro2;
import com.github.appintro.AppIntroFragment;
import com.github.appintro.model.SliderPage;
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 AppIntro2 {
private CustomAuthenticatedSlide _authenticatedSlide;
private CustomAuthenticationSlide _authenticationSlide;
private SecuritySetupSlide securitySetupSlide;
private SecurityPickerSlide _securityPickerSlide;
private Fragment _endSlide;
private AegisApplication _app;
@@ -56,12 +63,12 @@ public class IntroActivity extends AppIntro2 {
homeSliderPage.setBackgroundColor(getResources().getColor(R.color.colorSecondary));
addSlide(AppIntroFragment.newInstance(homeSliderPage));
_authenticationSlide = new CustomAuthenticationSlide();
_authenticationSlide.setBgColor(getResources().getColor(R.color.colorSecondary));
addSlide(_authenticationSlide);
_authenticatedSlide = new CustomAuthenticatedSlide();
_authenticatedSlide.setBgColor(getResources().getColor(R.color.colorSecondary));
addSlide(_authenticatedSlide);
_securityPickerSlide = new SecurityPickerSlide();
_securityPickerSlide.setBgColor(getResources().getColor(R.color.colorSecondary));
addSlide(_securityPickerSlide);
securitySetupSlide = new SecuritySetupSlide();
securitySetupSlide.setBgColor(getResources().getColor(R.color.colorSecondary));
addSlide(securitySetupSlide);
SliderPage endSliderPage = new SliderPage();
endSliderPage.setTitle(getString(R.string.setup_completed));
@@ -74,15 +81,19 @@ public class IntroActivity extends AppIntro2 {
@Override
public void onSlideChanged(Fragment oldFragment, Fragment newFragment) {
if (oldFragment == _authenticationSlide && newFragment != _endSlide) {
if (oldFragment == _securityPickerSlide && newFragment != _endSlide) {
// skip to the last slide if no encryption will be used
int cryptType = getIntent().getIntExtra("cryptType", CustomAuthenticationSlide.CRYPT_TYPE_INVALID);
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
int cryptType = getIntent().getIntExtra("cryptType", CRYPT_TYPE_INVALID);
if (cryptType == CRYPT_TYPE_NONE) {
// TODO: no magic indices
goToNextSlide(false);
}
}
if (newFragment == _endSlide) {
setWizardMode(false);
}
setSwipeLock(true);
}
@@ -90,14 +101,25 @@ public class IntroActivity extends AppIntro2 {
public void onDonePressed(Fragment currentFragment) {
super.onDonePressed(currentFragment);
int cryptType = _authenticatedSlide.getCryptType();
VaultFileCredentials creds = _authenticatedSlide.getCredentials();
int cryptType = securitySetupSlide.getCryptType();
VaultFileCredentials creds = securitySetupSlide.getCredentials();
if (cryptType == CRYPT_TYPE_INVALID
|| (cryptType == CRYPT_TYPE_NONE && creds != null)
|| (cryptType == CRYPT_TYPE_PASS && (creds == null || !creds.getSlots().has(PasswordSlot.class)))
|| (cryptType == CRYPT_TYPE_BIOMETRIC && (creds == null || !creds.getSlots().has(PasswordSlot.class) || !creds.getSlots().has(BiometricSlot.class)))) {
Dialogs.showErrorDialog(this, R.string.intro_crypto_error, String.format("State of SecuritySetupSlide not properly propagated, cryptType: %d, creds: %s", cryptType, creds), (dialog, which) -> finishIntro(CRYPT_TYPE_NONE, null));
return;
}
finishIntro(cryptType, creds);
}
private void finishIntro(int cryptType, VaultFileCredentials creds) {
Vault vault = new Vault();
VaultFile vaultFile = new VaultFile();
try {
JSONObject obj = vault.toJson();
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
if (cryptType == CRYPT_TYPE_NONE) {
vaultFile.setContent(obj);
} else {
vaultFile.setContent(obj, creds);
@@ -110,7 +132,11 @@ public class IntroActivity extends AppIntro2 {
return;
}
_app.initVaultManager(vault, creds);
if (cryptType == CRYPT_TYPE_NONE) {
_app.initVaultManager(vault, null);
} else {
_app.initVaultManager(vault, creds);
}
// skip the intro from now on
_prefs.setIntroDone(true);
@@ -16,6 +16,8 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.appcompat.view.ActionMode;
@@ -81,6 +83,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private SearchView _searchView;
private FloatingActionsMenu _fabMenu;
private EntryListView _entryListView;
private LinearLayout _btnBackupError;
private FabScrollHelper _fabScrollHelper;
@@ -108,6 +111,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setTapToRevealTime(getPreferences().getTapToRevealTime());
_entryListView.setSortCategory(getPreferences().getCurrentSortCategory(), false);
_entryListView.setViewMode(getPreferences().getCurrentViewMode());
_entryListView.setIsCopyOnTapEnabled(getPreferences().isCopyOnTapEnabled());
// set up the floating action button
_fabMenu = findViewById(R.id.fab);
@@ -124,6 +128,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
startScanActivity();
});
_btnBackupError = findViewById(R.id.btn_backup_error);
_btnBackupError.setOnClickListener(view -> {
startPreferencesActivity("pref_backups");
});
_fabScrollHelper = new FabScrollHelper(_fabMenu);
_selectedEntries = new ArrayList<>();
}
@@ -215,24 +224,28 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private void onPreferencesResult(Intent data) {
// refresh the entire entry list if needed
if (data.getBooleanExtra("needsRecreate", false)) {
recreate();
} else if (data.getBooleanExtra("needsRefresh", false)) {
boolean showAccountName = getPreferences().isAccountNameVisible();
int codeGroupSize = getPreferences().getCodeGroupSize();
boolean searchAccountName = getPreferences().isSearchAccountNameEnabled();
boolean highlightEntry = getPreferences().isEntryHighlightEnabled();
boolean tapToReveal = getPreferences().isTapToRevealEnabled();
int tapToRevealTime = getPreferences().getTapToRevealTime();
ViewMode viewMode = getPreferences().getCurrentViewMode();
_entryListView.setShowAccountName(showAccountName);
_entryListView.setCodeGroupSize(codeGroupSize);
_entryListView.setSearchAccountName(searchAccountName);
_entryListView.setHighlightEntry(highlightEntry);
_entryListView.setTapToReveal(tapToReveal);
_entryListView.setTapToRevealTime(tapToRevealTime);
_entryListView.setViewMode(viewMode);
_entryListView.refresh(true);
if (_loaded) {
if (data.getBooleanExtra("needsRecreate", false)) {
recreate();
} else if (data.getBooleanExtra("needsRefresh", false)) {
boolean showAccountName = getPreferences().isAccountNameVisible();
int codeGroupSize = getPreferences().getCodeGroupSize();
boolean searchAccountName = getPreferences().isSearchAccountNameEnabled();
boolean highlightEntry = getPreferences().isEntryHighlightEnabled();
boolean tapToReveal = getPreferences().isTapToRevealEnabled();
int tapToRevealTime = getPreferences().getTapToRevealTime();
ViewMode viewMode = getPreferences().getCurrentViewMode();
boolean copyOnTap = getPreferences().isCopyOnTapEnabled();
_entryListView.setShowAccountName(showAccountName);
_entryListView.setCodeGroupSize(codeGroupSize);
_entryListView.setSearchAccountName(searchAccountName);
_entryListView.setHighlightEntry(highlightEntry);
_entryListView.setTapToReveal(tapToReveal);
_entryListView.setTapToRevealTime(tapToRevealTime);
_entryListView.setViewMode(viewMode);
_entryListView.setIsCopyOnTapEnabled(copyOnTap);
_entryListView.refresh(true);
}
}
}
@@ -254,27 +267,33 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
} else {
for (VaultEntry entry : entries) {
_vault.addEntry(entry);
_entryListView.addEntry(entry);
if (_loaded) {
_entryListView.addEntry(entry);
}
}
saveVault();
saveVault(true);
}
}
private void onAddEntryResult(Intent data) {
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
_entryListView.addEntry(entry);
if (_loaded) {
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
_entryListView.addEntry(entry);
}
}
private void onEditEntryResult(Intent data) {
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
if (_loaded) {
UUID entryUUID = (UUID) data.getSerializableExtra("entryUUID");
if (data.getBooleanExtra("delete", false)) {
_entryListView.removeEntry(entryUUID);
} else {
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
_entryListView.replaceEntry(entryUUID, entry);
if (data.getBooleanExtra("delete", false)) {
_entryListView.removeEntry(entryUUID);
} else {
VaultEntry entry = _vault.getEntryByUUID(entryUUID);
_entryListView.replaceEntry(entryUUID, entry);
}
}
}
@@ -389,6 +408,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
startActivityForResult(Intent.createChooser(chooserIntent, getString(R.string.select_picture)), CODE_SCAN_IMAGE);
}
private void startPreferencesActivity(String preference) {
Intent intent = new Intent(this, PreferencesActivity.class);
intent.putExtra("pref", preference);
startActivityForResult(intent, CODE_PREFERENCES);
}
private void doShortcutActions() {
Intent intent = getIntent();
String action = intent.getStringExtra("action");
@@ -478,6 +503,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
handleDeeplink();
updateLockIcon();
doShortcutActions();
updateBackupErrorBar();
}
@Override
@@ -506,7 +532,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.removeEntry(oldEntry);
}
saveVault();
saveVault(true);
}
@Override
@@ -556,8 +582,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings: {
Intent intent = new Intent(this, PreferencesActivity.class);
startActivityForResult(intent, CODE_PREFERENCES);
startPreferencesActivity(null);
return true;
}
case R.id.action_about: {
@@ -615,10 +640,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
private void loadEntries() {
List<VaultEntry> entries = new ArrayList<>(_vault.getEntries());
_entryListView.addEntries(entries);
_entryListView.runEntriesAnimation();
_loaded = true;
if (!_loaded) {
_entryListView.addEntries(_vault.getEntries());
_entryListView.runEntriesAnimation();
_loaded = true;
}
}
private void startAuthActivity() {
@@ -635,6 +661,15 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
}
private void updateBackupErrorBar() {
String error = null;
if (_app.getPreferences().isBackupsEnabled()) {
error = _app.getPreferences().getBackupsError();
}
_btnBackupError.setVisibility(error == null ? View.GONE : View.VISIBLE);
}
@Override
public void onEntryClick(VaultEntry entry) {
if (_actionMode != null) {
@@ -646,8 +681,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
return;
}
copyEntryCode(entry);
}
@Override
@@ -684,12 +717,16 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
@Override
public void onEntryDrop(VaultEntry entry) {
saveVault();
saveVault(false);
}
@Override
public void onEntryChange(VaultEntry entry) {
saveVault();
saveVault(true);
}
public void onEntryCopy(VaultEntry entry) {
copyEntryCode(entry);
}
@Override
@@ -745,6 +782,20 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
mode.finish();
return true;
case R.id.action_share_qr:
Intent intent = new Intent(getBaseContext(), TransferEntriesActivity.class);
ArrayList<GoogleAuthInfo> authInfos = new ArrayList<>();
for (VaultEntry entry : _selectedEntries) {
GoogleAuthInfo authInfo = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
authInfos.add(authInfo);
}
intent.putExtra("authInfos", authInfos);
startActivity(intent);
mode.finish();
return true;
case R.id.action_delete:
Dialogs.showDeleteEntriesDialog(MainActivity.this, (d, which) -> {
deleteEntries(_selectedEntries);
@@ -1,6 +1,8 @@
package com.beemdevelopment.aegis.ui;
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
public class PreferencesActivity extends AegisActivity {
private PreferencesFragment _fragment;
@@ -9,6 +11,11 @@ public class PreferencesActivity extends AegisActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
if (savedInstanceState == null) {
_fragment = new PreferencesFragment();
_fragment.setArguments(getIntent().getExtras());
@@ -18,6 +25,18 @@ public class PreferencesActivity extends AegisActivity {
}
}
@Override
public void onResume() {
super.onResume();
Intent intent = getIntent();
String preference = intent.getStringExtra("pref");
if (preference != null) {
_fragment.scrollToPreference(preference);
intent.removeExtra("pref");
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
// pass permission request results to the fragment
@@ -40,4 +59,17 @@ public class PreferencesActivity extends AegisActivity {
outState.putParcelable("result", _fragment.getResult());
super.onSaveInstanceState(outState);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
}
@@ -228,6 +228,12 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
return true;
});
Preference copyOnTapPreference = findPreference("pref_copy_on_tap");
copyOnTapPreference.setOnPreferenceChangeListener((preference, newValue) -> {
_result.putExtra("needsRefresh", true);
return true;
});
Preference entryHighlightPreference = findPreference("pref_highlight_entry");
entryHighlightPreference.setOnPreferenceChangeListener((preference, newValue) -> {
_result.putExtra("needsRefresh", true);
@@ -428,8 +434,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
public void onResume() {
super.onResume();
updateEncryptionPreferences();
updateBackupPreference();
}
@@ -460,6 +466,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
// intentional fallthrough
case CODE_EXPORT_ENCRYPT:
onExportResult(resultCode, data, requestCode == CODE_EXPORT_ENCRYPT);
break;
case CODE_BACKUPS:
onSelectBackupsLocationResult(resultCode, data);
break;
@@ -742,13 +749,14 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
_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 boolean saveVault() {
try {
_vault.save();
_vault.save(true);
} catch (VaultManagerException e) {
e.printStackTrace();
Dialogs.showErrorDialog(getContext(), R.string.saving_error, e);
@@ -0,0 +1,136 @@
package com.beemdevelopment.aegis.ui;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultManager;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class TransferEntriesActivity extends AegisActivity {
private List<GoogleAuthInfo> _authInfos;
private ImageView _qrImage;
private TextView _issuer;
private TextView _accountName;
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);
setContentView(R.layout.activity_share_entry);
_vault = getApp().getVaultManager();
_qrImage = findViewById(R.id.ivQrCode);
_issuer = findViewById(R.id.tvIssuer);
_accountName = findViewById(R.id.tvAccountName);
_entriesCount = findViewById(R.id.tvEntriesCount);
_nextButton = findViewById(R.id.btnNext);
_previousButton = findViewById(R.id.btnPrevious);
if (getSupportActionBar() != null){
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
}
Intent intent = getIntent();
_authInfos = (ArrayList<GoogleAuthInfo>) intent.getSerializableExtra("authInfos");
int controlVisibility = _authInfos.size() != 1 ? View.VISIBLE : View.INVISIBLE;
_entriesCount.setVisibility(controlVisibility);
_nextButton.setVisibility(controlVisibility);
_nextButton.setOnClickListener(v -> {
if (_currentEntryCount < _authInfos.size()) {
_previousButton.setVisibility(View.VISIBLE);
_currentEntryCount++;
generateQR();
if (_currentEntryCount == _authInfos.size()) {
_nextButton.setText(R.string.done);
}
} else {
finish();
}
});
_previousButton.setOnClickListener(v -> {
if (_currentEntryCount > 1 ) {
_nextButton.setText(R.string.next);
_currentEntryCount--;
generateQR();
if (_currentEntryCount == 1) {
_previousButton.setVisibility(View.INVISIBLE);
}
}
});
generateQR();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
private void generateQR() {
GoogleAuthInfo selectedEntry = _authInfos.get(_currentEntryCount - 1);
_issuer.setText(selectedEntry.getIssuer());
_accountName.setText(selectedEntry.getAccountName());
_entriesCount.setText(String.format(getString(R.string.entries_count), _currentEntryCount, _authInfos.size()));
QRCodeWriter writer = new QRCodeWriter();
BitMatrix bitMatrix = null;
try {
bitMatrix = writer.encode(selectedEntry.getUri().toString(), BarcodeFormat.QR_CODE, 512, 512);
} catch (WriterException e) {
Dialogs.showErrorDialog(this, R.string.unable_to_generate_qrcode, e);
return;
}
int width = bitMatrix.getWidth();
int height = bitMatrix.getHeight();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
_qrImage.setImageBitmap(bitmap);
}
}
@@ -16,7 +16,7 @@ import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.github.appintro.SlidePolicy;
import com.google.android.material.snackbar.Snackbar;
public class CustomAuthenticationSlide extends Fragment implements SlidePolicy, RadioGroup.OnCheckedChangeListener {
public class SecurityPickerSlide extends Fragment implements SlidePolicy, RadioGroup.OnCheckedChangeListener {
public static final int CRYPT_TYPE_INVALID = 0;
public static final int CRYPT_TYPE_NONE = 1;
public static final int CRYPT_TYPE_PASS = 2;
@@ -27,7 +27,7 @@ public class CustomAuthenticationSlide extends Fragment implements SlidePolicy,
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_authentication_slide, container, false);
final View view = inflater.inflate(R.layout.fragment_security_picker_slide, container, false);
_buttonGroup = view.findViewById(R.id.rg_authenticationMethod);
_buttonGroup.setOnCheckedChangeListener(this);
onCheckedChanged(_buttonGroup, _buttonGroup.getCheckedRadioButtonId());
@@ -32,7 +32,7 @@ import com.google.android.material.snackbar.Snackbar;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
public class CustomAuthenticatedSlide extends Fragment implements SlidePolicy, SlideSelectionListener {
public class SecuritySetupSlide extends Fragment implements SlidePolicy, SlideSelectionListener {
private int _bgColor;
private EditText _textPassword;
private EditText _textPasswordConfirm;
@@ -43,7 +43,7 @@ public class CustomAuthenticatedSlide extends Fragment implements SlidePolicy, S
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_authenticated_slide, container, false);
final View view = inflater.inflate(R.layout.fragment_security_setup_slide, container, false);
_textPassword = view.findViewById(R.id.text_password);
_textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
_checkPasswordVisibility = view.findViewById(R.id.check_toggle_visibility);
@@ -60,7 +60,6 @@ public class CustomAuthenticatedSlide extends Fragment implements SlidePolicy, S
}
});
_creds = new VaultFileCredentials();
view.findViewById(R.id.main).setBackgroundColor(_bgColor);
return view;
}
@@ -95,7 +94,10 @@ public class CustomAuthenticatedSlide extends Fragment implements SlidePolicy, S
@Override
public void onSlideSelected() {
Intent intent = getActivity().getIntent();
_cryptType = intent.getIntExtra("cryptType", CustomAuthenticationSlide.CRYPT_TYPE_INVALID);
_cryptType = intent.getIntExtra("cryptType", SecurityPickerSlide.CRYPT_TYPE_INVALID);
if (_cryptType != SecurityPickerSlide.CRYPT_TYPE_NONE) {
_creds = new VaultFileCredentials();
}
}
@Override
@@ -106,14 +108,14 @@ public class CustomAuthenticatedSlide extends Fragment implements SlidePolicy, S
@Override
public boolean isPolicyRespected() {
switch (_cryptType) {
case CustomAuthenticationSlide.CRYPT_TYPE_NONE:
case SecurityPickerSlide.CRYPT_TYPE_NONE:
return true;
case CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC:
case SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC:
if (!_creds.getSlots().has(BiometricSlot.class)) {
return false;
}
// intentional fallthrough
case CustomAuthenticationSlide.CRYPT_TYPE_PASS:
case SecurityPickerSlide.CRYPT_TYPE_PASS:
if (EditTextHelper.areEditTextsEqual(_textPassword, _textPasswordConfirm)) {
return _creds.getSlots().has(PasswordSlot.class);
}
@@ -135,7 +137,7 @@ public class CustomAuthenticatedSlide extends Fragment implements SlidePolicy, S
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
snackbar.show();
}
} else if (_cryptType != CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC) {
} else if (_cryptType != SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC) {
deriveKey();
} else if (!_creds.getSlots().has(BiometricSlot.class)) {
showBiometricPrompt();
@@ -18,6 +18,7 @@ import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.vault.VaultEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -37,6 +38,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
private boolean _highlightEntry;
private boolean _tapToReveal;
private int _tapToRevealTime;
private boolean _copyOnTap;
private String _groupFilter;
private SortCategory _sortCategory;
private ViewMode _viewMode;
@@ -88,6 +90,10 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_highlightEntry = highlightEntry;
}
public void setIsCopyOnTapEnabled(boolean enabled) {
_copyOnTap = enabled;
}
public VaultEntry getEntryAt(int position) {
return _shownEntries.get(position);
}
@@ -127,7 +133,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
checkPeriodUniformity();
}
public void addEntries(List<VaultEntry> entries) {
public void addEntries(Collection<VaultEntry> entries) {
_entries.addAll(entries);
updateShownEntries();
checkPeriodUniformity(true);
@@ -218,7 +224,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
notifyDataSetChanged();
} else {
for (EntryHolder holder : _holders) {
holder.refreshCode();
holder.refresh();
}
}
}
@@ -342,7 +348,10 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
boolean handled = false;
if (_selectedEntries.isEmpty()) {
holder.animateCopyText();
if (_copyOnTap) {
_view.onEntryCopy(entry);
holder.animateCopyText();
}
if (_highlightEntry || _tapToReveal) {
if (_focusedEntry == entry) {
@@ -547,6 +556,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
void onEntryMove(VaultEntry entry1, VaultEntry entry2);
void onEntryDrop(VaultEntry entry);
void onEntryChange(VaultEntry entry);
void onEntryCopy(VaultEntry entry);
void onPeriodUniformityChanged(boolean uniform, int period);
void onSelect(VaultEntry entry);
void onDeselect(VaultEntry entry);
@@ -45,7 +45,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private boolean _hidden;
private PeriodProgressBar _progressBar;
private TotpProgressBar _progressBar;
private View _view;
private UiRefresher _refresher;
@@ -68,6 +68,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_buttonRefresh = view.findViewById(R.id.buttonRefresh);
_selected = view.findViewById(R.id.ivSelected);
_selectedHandler = new Handler();
_animationHandler = new Handler();
_progressBar = view.findViewById(R.id.progressBar);
int primaryColorId = view.getContext().getResources().getColor(R.color.colorPrimary);
@@ -83,8 +84,6 @@ public class EntryHolder extends RecyclerView.ViewHolder {
if (!_hidden) {
refreshCode();
}
_progressBar.refresh();
}
@Override
@@ -106,6 +105,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_selected.clearAnimation();
_selected.setVisibility(View.GONE);
_selectedHandler.removeCallbacksAndMessages(null);
_animationHandler.removeCallbacksAndMessages(null);
// only show the progress bar if there is no uniform period and the entry type is TotpInfo
setShowProgress(showProgress);
@@ -197,10 +197,17 @@ public class EntryHolder extends RecyclerView.ViewHolder {
public void startRefreshLoop() {
_refresher.start();
_progressBar.start();
}
public void stopRefreshLoop() {
_refresher.stop();
_progressBar.stop();
}
public void refresh() {
_progressBar.restart();
refreshCode();
}
public void refreshCode() {
@@ -246,9 +253,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
}
public void animateCopyText() {
if (_animationHandler != null) {
_animationHandler.removeCallbacksAndMessages(null);
}
_animationHandler.removeCallbacksAndMessages(null);
Animation slideDownFadeIn = AnimationUtils.loadAnimation(itemView.getContext(), R.anim.slide_down_fade_in);
Animation slideDownFadeOut = AnimationUtils.loadAnimation(itemView.getContext(), R.anim.slide_down_fade_out);
Animation fadeOut = AnimationUtils.loadAnimation(itemView.getContext(), R.anim.fade_out);
@@ -257,14 +263,9 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_profileCopied.startAnimation(slideDownFadeIn);
_description.startAnimation(slideDownFadeOut);
_animationHandler = new Handler();
_animationHandler.postDelayed(new Runnable() {
@Override
public void run() {
_profileCopied.startAnimation(fadeOut);
_description.startAnimation(fadeIn);
}
_animationHandler.postDelayed(() -> {
_profileCopied.startAnimation(fadeOut);
_description.startAnimation(fadeIn);
}, 3000);
}
@@ -33,6 +33,7 @@ import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.util.ViewPreloadSizeProvider;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -45,7 +46,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private RecyclerView _recyclerView;
private RecyclerView.ItemDecoration _dividerDecoration;
private ViewPreloadSizeProvider<VaultEntry> _preloadSizeProvider;
private PeriodProgressBar _progressBar;
private TotpProgressBar _progressBar;
private boolean _showProgress;
private ViewMode _viewMode;
private LinearLayout _emptyStateView;
@@ -125,8 +126,8 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
}
public void setGroupFilter(String group, boolean apply) {
_touchCallback.setIsLongPressDragEnabled(group == null);
_adapter.setGroupFilter(group, apply);
_touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed());
if (apply) {
runEntriesAnimation();
@@ -134,7 +135,11 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
}
public void setIsLongPressDragEnabled(boolean enabled) {
_touchCallback.setIsLongPressDragEnabled(enabled);
_touchCallback.setIsLongPressDragEnabled(enabled && _adapter.isDragAndDropAllowed());
}
public void setIsCopyOnTapEnabled(boolean enabled) {
_adapter.setIsCopyOnTapEnabled(enabled);
}
public void setActionModeState(boolean enabled, VaultEntry entry) {
@@ -149,8 +154,8 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
}
public void setSortCategory(SortCategory sortCategory, boolean apply) {
_touchCallback.setIsLongPressDragEnabled(sortCategory == SortCategory.CUSTOM);
_adapter.setSortCategory(sortCategory, apply);
_touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed());
if (apply) {
runEntriesAnimation();
@@ -158,8 +163,8 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
}
public void setSearchFilter(String search) {
_touchCallback.setIsLongPressDragEnabled(search == null);
_adapter.setSearchFilter(search);
_touchCallback.setIsLongPressDragEnabled(_adapter.isDragAndDropAllowed());
}
public void setViewMode(ViewMode mode) {
@@ -170,8 +175,9 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
public void refresh(boolean hard) {
if (_showProgress) {
_progressBar.refresh();
_progressBar.restart();
}
_adapter.refresh(hard);
}
@@ -204,6 +210,11 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
_listener.onEntryChange(entry);
}
@Override
public void onEntryCopy(VaultEntry entry) {
_listener.onEntryCopy(entry);
}
@Override
public void onSelect(VaultEntry entry) { _listener.onSelect(entry); }
@@ -214,12 +225,13 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
public void onPeriodUniformityChanged(boolean isUniform, int period) {
setShowProgress(isUniform);
if (_showProgress) {
_refresher.stop();
_progressBar.setVisibility(View.VISIBLE);
_progressBar.setPeriod(period);
_progressBar.start();
_refresher.start();
} else {
_progressBar.setVisibility(View.GONE);
_progressBar.stop();
_refresher.stop();
}
}
@@ -253,7 +265,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
updateEmptyState();
}
public void addEntries(List<VaultEntry> entries) {
public void addEntries(Collection<VaultEntry> entries) {
_adapter.addEntries(entries);
updateEmptyState();
}
@@ -322,6 +334,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
void onEntryMove(VaultEntry entry1, VaultEntry entry2);
void onEntryDrop(VaultEntry entry);
void onEntryChange(VaultEntry entry);
void onEntryCopy(VaultEntry entry);
void onLongEntryClick(VaultEntry entry);
void onScroll(int dx, int dy);
void onSelect(VaultEntry entry);
@@ -1,54 +0,0 @@
package com.beemdevelopment.aegis.ui.views;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.ProgressBar;
import com.beemdevelopment.aegis.otp.TotpInfo;
import androidx.annotation.RequiresApi;
public class PeriodProgressBar extends ProgressBar {
private int _period;
public PeriodProgressBar(Context context) {
super(context);
}
public PeriodProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PeriodProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public PeriodProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public void setPeriod(int period) {
_period = period;
}
public void refresh() {
// reset the progress bar
int maxProgress = getMax();
setProgress(maxProgress);
// calculate the progress the bar should start at
long millisTillRotation = TotpInfo.getMillisTillNextRotation(_period);
long period = _period * maxProgress;
int currentProgress = maxProgress - (int) ((((double) period - millisTillRotation) / period) * maxProgress);
// start progress animation
ObjectAnimator animation = ObjectAnimator.ofInt(this, "progress", currentProgress, 0);
animation.setDuration(millisTillRotation);
animation.setInterpolator(new LinearInterpolator());
animation.start();
}
}
@@ -0,0 +1,82 @@
package com.beemdevelopment.aegis.ui.views;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.provider.Settings;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.ProgressBar;
import androidx.annotation.RequiresApi;
import com.beemdevelopment.aegis.otp.TotpInfo;
public class TotpProgressBar extends ProgressBar {
private int _period = 30;
private Handler _handler;
private float _animDurationScale;
public TotpProgressBar(Context context) {
super(context);
}
public TotpProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TotpProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public TotpProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public void setPeriod(int period) {
_period = period;
}
public void start() {
stop();
_handler = new Handler();
_animDurationScale = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f);
refresh();
}
public void stop() {
if (_handler != null) {
_handler.removeCallbacksAndMessages(null);
_handler = null;
}
}
public void restart() {
stop();
start();
}
private void refresh() {
// calculate the current progress the bar should start at
int maxProgress = getMax();
long millisTillRotation = TotpInfo.getMillisTillNextRotation(_period);
int currentProgress = (int) (maxProgress * ((float) millisTillRotation / (_period * 1000)));
// start progress animation, compensating for any changes to the animator duration scale settings
int animPart = maxProgress / _period;
int animEnd = (currentProgress / animPart) * animPart;
int animPartDuration = (int) (1000 / _animDurationScale);
float animDurationFraction = (float) (currentProgress - animEnd) / animPart;
int realAnimDuration = (int) (1000 * animDurationFraction);
int animDuration = (int) (animPartDuration * animDurationFraction);
ObjectAnimator animation = ObjectAnimator.ofInt(this, "progress", currentProgress, animEnd);
animation.setDuration(animDuration);
animation.setInterpolator(new LinearInterpolator());
animation.start();
// the animation only lasts for (less than) one second, so restart it after that
_handler.postDelayed(this::refresh, realAnimDuration);
}
}
@@ -96,7 +96,7 @@ public class VaultManager {
}
}
public void save() throws VaultManagerException {
public void save(boolean backup) throws VaultManagerException {
try {
JSONObject obj = _vault.toJson();
@@ -108,13 +108,18 @@ public class VaultManager {
}
save(_context, file);
if (_prefs.isBackupsEnabled()) {
backup();
}
} catch (VaultFileException e) {
throw new VaultManagerException(e);
}
if (backup && _prefs.isBackupsEnabled()) {
try {
backup();
_prefs.setBackupsError(null);
} catch (VaultManagerException e) {
_prefs.setBackupsError(e);
}
}
}
public void export(OutputStream stream, boolean encrypt) throws VaultManagerException {
@@ -190,11 +195,11 @@ public class VaultManager {
public void enableEncryption(VaultFileCredentials creds) throws VaultManagerException {
_creds = creds;
save();
save(true);
}
public void disableEncryption() throws VaultManagerException {
_creds = null;
save();
save(true);
}
}
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/auth_button"/>
<stroke android:color="@color/auth_button" android:width="1dp" />
<!--corners allow us to make the rounded corners button-->
<corners android:radius="4dp" />
</shape>
</item>
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z" />
</vector>
@@ -0,0 +1,8 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="7dp" android:left="7dp" android:right="7dp" android:bottom="7dp">
<vector android:height="24dp" android:viewportHeight="3328" android:tint="#FFFFFF"
android:viewportWidth="3333" android:width="24.036058dp">
<path android:fillColor="#FF000000" android:fillType="evenOdd" android:pathData="M5,0h1211v1206L5,1206L5,0zM3025,3025h309v304h-309v-304zM2432,3025h309v290h-618v-592h299v-300h304v-598h309v294h294v304h-294v304h-603v299zM1515,2417h299v-304h-289v-304h289v-304h-294v304h-309v-304h304L1515,603h309v902h299v304h294v-304h309v304h-294v304h-309v598h-299v603h-309v-897zM3020,1505h309v304h-309v-304zM608,1505h309v304L608,1809v-304zM5,1505h309v304L5,1809v-304zM1515,0h309v304h-309L1515,0zM0,2118h1211v1206L0,3324L0,2118zM294,2411h623v620L294,3031v-620zM2118,0h1211v1206L2118,1206L2118,0zM2412,293h623v620h-623L2412,293zM299,293h623v620L299,913L299,293z"/>
</vector>
</item>
</layer-list>
+78 -47
View File
@@ -7,37 +7,44 @@
android:fitsSystemWindows="true"
android:background="?attr/background"
tools:context="com.beemdevelopment.aegis.ui.AuthActivity">
<LinearLayout
android:orientation="vertical"
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="32dp"
tools:layout_editor_absoluteY="8dp"
tools:layout_editor_absoluteX="8dp">
android:layout_height="match_parent"
android:layout_margin="48dp"
android:orientation="vertical"
tools:layout_editor_absoluteX="8dp"
tools:layout_editor_absoluteY="8dp">
<ImageView
android:id="@+id/imageView2"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="20dp"
app:srcCompat="@drawable/ic_aegis_iconx" />
<TextView
android:text="@string/authentication"
android:textColor="?attr/primaryText"
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"
android:id="@+id/textView2" />
android:fontFamily="sans-serif-light"
android:singleLine="false"
android:text="@string/authentication_multiline"
android:textColor="?attr/authText"
android:textFontWeight="500"
android:textSize="46sp" />
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="12dp">
<TextView
android:id="@+id/textView4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/authentication_enter_password"
android:textColor="?attr/secondaryText" />
android:layout_marginTop="12dp"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:paddingBottom="25dp"
app:hintEnabled="false"
app:passwordToggleEnabled="true"
app:passwordToggleTint="#949494">
@@ -46,48 +53,72 @@
android:id="@+id/text_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
android:hint="@string/password"
android:inputType="textPassword"
android:textColor="?attr/primaryText"
android:textColorHint="?attr/authText"
app:backgroundTint="?attr/authText" />
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="25dp">
<Button
android:id="@+id/button_decrypt"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/button_rounded_corners"
android:minWidth="125dp"
android:text="@string/unlock" />
<Button
android:id="@+id/button_biometrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="125dp"
android:text="@string/biometrics"
android:visibility="gone" />
android:text="@string/unlock"
android:textColor="@color/primary_text_inverted"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/box_biometric_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="12dp"
android:visibility="gone">
<ImageView
<TextView
android:id="@+id/button_biometrics"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_info_outline_black_24dp"
android:layout_marginEnd="15dp"/>
android:layout_gravity="center"
android:minWidth="125dp"
android:text="@string/auth_use_biometrics"
android:textColor="?attr/authText"
android:visibility="gone" />
<TextView
<LinearLayout
android:id="@+id/box_biometric_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/invalidated_biometrics"/>
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_info_outline_black_24dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/invalidated_biometrics" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:textStyle="bold"
android:textAllCaps="true"
android:text="@string/app_name_full"
android:paddingBottom="50dp"
android:textColor="@color/divider" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+36 -5
View File
@@ -8,12 +8,43 @@
android:fitsSystemWindows="true"
tools:context="com.beemdevelopment.aegis.ui.MainActivity">
<fragment
android:name="com.beemdevelopment.aegis.ui.views.EntryListView"
android:id="@+id/key_profiles"
android:layout_height="match_parent"
<LinearLayout
android:layout_width="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/btn_backup_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="10dp"
android:paddingVertical="10dp"
android:background="@color/colorAccent"
android:foreground="?android:selectableItemBackground"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/icon_primary_inverted"
android:src="@drawable/ic_alert_black_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/backup_error_bar_message"
android:textColor="@color/primary_text_inverted"
android:layout_marginStart="5dp" />
</LinearLayout>
<fragment
android:name="com.beemdevelopment.aegis.ui.views.EntryListView"
android:id="@+id/key_profiles"
android:layout_height="fill_parent"
android:layout_width="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
</LinearLayout>
<!-- note: the fab should always be the last element to be sure it's displayed on top -->
<com.getbase.floatingactionbutton.FloatingActionsMenu
@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/cardBackground"
tools:context="com.beemdevelopment.aegis.ui.TransferEntriesActivity">
<ImageView
android:id="@+id/ivQrCode"
android:layout_width="250dp"
android:layout_height="250dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.496"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.399" />
<TextView
android:id="@+id/tvTransfer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/transfer_entry"
android:textColor="?attr/primaryText"
android:textSize="22sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/tvDescription"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tvDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:text="@string/transfer_entry_description"
app:layout_constraintBottom_toTopOf="@+id/ivQrCode"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/tvIssuer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
tools:text="Issuer"
android:textColor="?attr/primaryText"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ivQrCode" />
<TextView
android:id="@+id/tvAccountName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
tools:text="Accountname"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvIssuer" />
<Button
android:id="@+id/btnNext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:background="@android:color/transparent"
android:text="@string/next"
android:textAllCaps="false"
android:textColor="@color/code_primary_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnPrevious"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:background="@android:color/transparent"
android:text="@string/previous"
android:textAllCaps="false"
android:visibility="invisible"
android:textColor="@color/code_primary_text"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tvEntriesCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:text="@string/entries_count"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/btnNext"
app:layout_constraintHorizontal_bias="0.506"
app:layout_constraintStart_toStartOf="@+id/btnPrevious" />
</androidx.constraintlayout.widget.ConstraintLayout>
+2 -2
View File
@@ -152,12 +152,12 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.beemdevelopment.aegis.ui.views.PeriodProgressBar
<com.beemdevelopment.aegis.ui.views.TotpProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="4dp"
android:id="@+id/progressBar"
android:max="1000"
android:max="5000"
android:layout_weight="1"/>
</LinearLayout>
@@ -151,7 +151,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.beemdevelopment.aegis.ui.views.PeriodProgressBar
<com.beemdevelopment.aegis.ui.views.TotpProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="3dp"
+1 -1
View File
@@ -150,7 +150,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.beemdevelopment.aegis.ui.views.PeriodProgressBar
<com.beemdevelopment.aegis.ui.views.TotpProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="3dp"
@@ -6,14 +6,14 @@
android:background="?attr/background"
android:orientation="vertical">
<com.beemdevelopment.aegis.ui.views.PeriodProgressBar
<com.beemdevelopment.aegis.ui.views.TotpProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="4dp"
android:progressDrawable="@drawable/progress_horizontal"
android:id="@+id/progressBar"
android:visibility="gone"
android:max="1000"/>
android:max="5000"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
@@ -22,4 +22,12 @@
android:icon="@drawable/ic_delete_white"
android:tint="?attr/iconColorPrimary"
app:showAsAction="always"/>
<item
android:id="@+id/action_share_qr"
android:title="@string/action_transfer"
android:orderInCategory="100"
android:icon="@drawable/ic_qr_code_full"
android:tint="?attr/iconColorPrimary"
app:showAsAction="always"/>
</menu>
+6 -3
View File
@@ -8,12 +8,15 @@
app:showAsAction="ifRoom"
android:title="@string/save"/>
<item
android:id="@+id/action_delete"
android:title="@string/action_delete"
android:id="@+id/action_edit_icon"
android:title="@string/action_edit_icon"
app:showAsAction="never"/>
<item
android:id="@+id/action_default_icon"
android:title="@string/action_default_icon"
app:showAsAction="never"/>
<item
android:id="@+id/action_delete"
android:title="@string/action_delete"
app:showAsAction="never"/>
</menu>
+4 -4
View File
@@ -31,10 +31,10 @@
<license>MIT License</license>
</notice>
<notice>
<name>Spongycastle</name>
<url>https://github.com/rtyley/spongycastle/</url>
<copyright>Copyright (c) 2000-2017 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)</copyright>
<license>Apache Software License 2.0</license>
<name>Bouncy Castle</name>
<url>https://www.bouncycastle.org/</url>
<copyright>Copyright (c) 2000-2019 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)</copyright>
<license>MIT License</license>
</notice>
<notice>
<name>CircleImageView</name>
+236
View File
@@ -0,0 +1,236 @@
<resources>
<string name="action_settings">Ayarlar</string>
<string name="action_about">Hakkında</string>
<string name="action_import">İçe aktar</string>
<string name="action_delete">Sil</string>
<string name="action_default_icon">Varsayılan simgeyi geri yükle</string>
<string name="discard">Göz ardı et</string>
<string name="save">Kaydet</string>
<string name="issuer">Sağlayıcı</string>
<string name="settings">Ayarlar</string>
<string name="pref_appearance_group_title">Görünüm</string>
<string name="pref_general_group_title">Genel</string>
<string name="pref_security_group_title">Güvenlik</string>
<string name="pref_tools_group_title">Araçlar</string>
<string name="pref_backups_group_title">Yedekler</string>
<string name="pref_select_theme_title">Tema</string>
<string name="pref_view_mode_title">Görüntüleme biçimi</string>
<string name="pref_lang_title">Dil</string>
<string name="pref_code_group_size_title">Kod hanelerinin gruplanması</string>
<string name="pref_code_group_size_summary">Kodu 3 haneli gruplama yerine 2 haneli gruplamayla göster</string>
<string name="pref_account_name_title">Hesap adını göster</string>
<string name="pref_account_name_summary">Hesap adını sağlayıcının yanında göstermek için bunu etkinleştirin</string>
<string name="pref_timeout_title">Zaman aşımı</string>
<string name="pref_timeout_summary">Kasayı %1$s saniyelik hareketsizliğin ardından otomatik kilitle</string>
<string name="pref_slots_title">Anahtarlık</string>
<string name="pref_slots_summary">Kasayı açabilecek anahtarların listesini yönet</string>
<string name="pref_import_file_title">Dosyadan içeriye aktar</string>
<string name="pref_import_file_summary">Veritabanını dosyadan içeriye aktar</string>
<string name="pref_backups_title">Kasayı yedekle</string>
<string name="pref_backups_summary">Kasanın yedeğini harici depolamaya kaydet. Sadece şifrelenmiş kasalarda desteklenir.</string>
<string name="pref_backups_location_title">Yedek dosyaların kaydedileceği klasör</string>
<string name="pref_backups_location_summary">Yedekler şurada depolanacak</string>
<string name="pref_backups_trigger_title">Yedeklemeyi tetikle</string>
<string name="pref_backups_trigger_summary">Yedeklemeyi elle tetikle</string>
<string name="pref_backups_versions_title">Saklanacak sürüm sayısı</string>
<string name="pref_backups_versions_summary">Yedeklerin %1$d versiyonunu tut</string>
<string name="pref_import_app_title">Uygulamadan içeriye aktar</string>
<string name="pref_import_app_summary">Başka bir uygulamanın veritabanını içeriye aktar (kök kullanıcı erişimi gerektirir)</string>
<string name="pref_export_title">Dışarıya aktar</string>
<string name="pref_export_summary">Kasayı dışarıya aktar</string>
<string name="pref_password_reminder_title">Parola hatırlatma</string>
<string name="pref_password_reminder_summary">Parolayı periyodik olarak girmem için bir hatırlatma göster ki bu sayede unutmayayım</string>
<string name="pref_secure_screen_title">Ekran güvenliği</string>
<string name="pref_secure_screen_summary">Uygulama içindeyken ekran görüntüsü almayı ve diğer ekran yakalama girişimlerini engelle.</string>
<string name="pref_tap_to_reveal_title">Görmek için dokunun</string>
<string name="pref_tap_to_reveal_summary">Kodlar varsayılan olarak gizlenir. Kodu görmek için üstüne dokunun</string>
<string name="pref_tap_to_reveal_time_title">Gösterme için zaman aşımı</string>
<string name="pref_auto_lock_title">Otomatik kilitleme</string>
<string name="pref_auto_lock_summary">Cihazı kilitlediğimde veya uygulamayı kapattığımda otomatik olarak kilitle.</string>
<string name="pref_encryption_title">Şifreleme</string>
<string name="pref_encryption_summary">Kasayı şifrele ve açmak için bir parola veya biyometrik doğrulama kullan</string>
<string name="pref_biometrics_title">Biyometrik kilit açma</string>
<string name="pref_biometrics_summary">Kasayı açmak üzere biyometrik kimrik doğrulamaya izin ver</string>
<string name="pref_set_password_title">Parolayı değiştir</string>
<string name="pref_set_password_summary">Kasanızı açmak üzere kullanmak için yeni bir parola atayın</string>
<string name="choose_authentication_method">Güvenlik</string>
<string name="authentication_method_explanation">Aegis güvenlik odaklı bir 2FA uygulamasıdır. Kodlar isteğiniz üzerine parolayla şifreleyebileceğiniz bir kasada saklanabilir. Eğer bir saldırgan şifrelenmiş kasa dosyanızı elde ederse parolayı bilmeden içeriğine erişemeyecektir.\n\nCihazınız için en iyi olduğunu düşündüğümüz seçeneği seçtik.</string>
<string name="authentication_method_none">Hiçbiri</string>
<string name="authentication_method_none_description">Kasayı açmak için parola gerektirme ve kasayı şifreleme. <b>Bu seçenek tavsiye edilmez</b>.</string>
<string name="authentication_method_password">Parola</string>
<string name="authentication_method_password_description">Kasayı açmak için bir parola gerektirir.</string>
<string name="authentication_method_password_explanation">Uyarı: Eğer parolanızı unutursanız kodlarınıza erişimi kalıcı olarak kaybedebilirsiniz.</string>
<string name="authentication_method_biometrics">Biyometrik</string>
<string name="authentication_method_biometrics_description">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.</string>
<string name="authentication_method_set_password">Parola</string>
<string name="authentication_enter_password">Parolanızı girin</string>
<string name="authentication">Kasa kilidini aç</string>
<string name="set_password">Lütfen bir parola girin</string>
<string name="set_group">Lütfen bir grup adı girin</string>
<string name="set_number">Lütfen bir sayı girin</string>
<string name="set_password_confirm">Lütfen parolayı doğrulayın</string>
<string name="invalidated_biometrics">Cihazınızın güvenlik ayarlarında bir değişim tespit edildi. Lütfen \"Aegis -> Ayarlar -> Biyometrik\" menüsüne gidin ve biyometrik doğrulamayı tekrar aktifleştirin.</string>
<string name="password_reminder">Parolanızı en son gireli bayağı zaman geçti. Halen hatırlıyor musunuz?</string>
<string name="enter_password_authy_message">Görünüşe göre Authy kodlarınız şifreli. Lütfen Aegis\'i kapatın Authy\'i açın ve parolanızla kodlarınızın kilidini açın. Bunun yerine aşağıya parolanızı girerseniz Aegis, Authy kodlarınızı sizin için deşifre etmeyi deneyebilir.</string>
<string name="yes">Evet</string>
<string name="no">Hayır</string>
<string name="unlock">Kilidi aç</string>
<string name="biometrics">Biyometrik</string>
<string name="advanced">Gelişmiş</string>
<string name="seconds">saniye</string>
<string name="counter">Sayaç</string>
<string name="digits">Haneler</string>
<string name="secret">Gizli Anahtar</string>
<string name="scan">QR kod tara</string>
<string name="scan_image">Resim tara</string>
<string name="enter_manually">El ile gir</string>
<string name="add_biometric">Biyometrik doğrulama ekle</string>
<string name="add_biometric_slot">Biyometrik doğrulama elemanı ekle</string>
<string name="set_up_biometric">Biyometrik kilit açma kurulumu</string>
<string name="add_password">Parola ekle</string>
<string name="slots_warning">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.</string>
<string name="copy">Kopyala</string>
<string name="edit">Düzenle</string>
<string name="delete">Sil</string>
<string name="password">Parola</string>
<string name="confirm_password">Parolayı doğrula</string>
<string name="show_password">Parolayı göster</string>
<string name="new_entry">Yeni Girdi</string>
<string name="add_new_entry">Yeni Girdi Ekle</string>
<string name="unlock_vault_error">Kasa kilidi açılamadı</string>
<string name="unlock_vault_error_description">Hatalı parola. Parolanızı yanlış yazmadığınıza emin olun.</string>
<string name="password_equality_error">Parolalar aynı olmalı ve boş olmamalıdır</string>
<string name="snackbar_authentication_method">Lütfen bir kimlik doğrulama yöntemi seçin</string>
<string name="encrypting_vault">Kasa Şifreleniyor</string>
<string name="delete_entry">Girdiyi sil</string>
<string name="delete_entry_description">Bu girdiyi silmek istediğinize emin misiniz?</string>
<string name="delete_entries">Girdileri sil</string>
<string name="delete_entries_description">Are you sure you want to delete %d entries?</string>
<string name="discard_changes">Değişiklikler göz ardı edilsin mi?</string>
<string name="discard_changes_description">Değişiklikleriniz kaydedilmedi</string>
<string name="folder">Klasör</string>
<string name="tap_to_select">Seçmek için dokunun</string>
<string name="saving_profile_error">Profil kaydı sırasında hata</string>
<string name="welcome">Hoşgeldiniz</string>
<string name="app_description">Aegis özgür, güvenli ve açık kaynak 2FA uygulamasıdır</string>
<string name="setup_completed">Kurulum Tamamlandı</string>
<string name="setup_completed_description">Aegis kuruldu ve kullanılmaya hazır.</string>
<string name="vault_not_found">Kasa bulunmadı, kurulum başlatılıyor...</string>
<string name="copied">Kopyalandı</string>
<string name="errors_copied">Hatalar panoya kopyalandı</string>
<string name="version_copied">Sürüm panoya kopyalandı</string>
<string name="error_occurred">Bir hata meydana geldi</string>
<string name="decryption_error">Kasayı açmayı denerken bir hata meydana geldi</string>
<string name="decryption_corrupt_error">Kasayı açmayı denerken bir hata meydana geldi. Kasa dosyanız bozuk olabilir.</string>
<string name="saving_error">Kasayı kaydetmeye çalışırken bir hata meydana geldi</string>
<string name="vault_init_error">Kasayı oluşturmaya çalışırken bir hata meydana geldi</string>
<string name="vault_load_error">Kasayı depolama alanından yüklemeye çalışırken bir hata meydana geldi</string>
<string name="biometric_decrypt_error">Kasayı biyometrik doğrulama ile açmaya çalışırken bir hata meydana geldi. Bu genellikle cihazınızın güvenlik ayarları değiştğinde olur. Lütfen kasanızı parolanızla açın ve biyometrik doğrulamayı Aegis\'in ayar menüsünden yeniden yapılandırın.</string>
<string name="biometric_init_error">Biyometrik doğrulamaya hazırlanırken bir hata meydana geldi. Bu genellikle cihazınızın güvenlik ayarları değiştğinde olur. Lütfen kasanızı parolanızla açın ve biyometrik doğrulamayı Aegis\'in ayar menüsünden yeniden yapılandırın.</string>
<string name="disable_encryption">Şifrelemeyi devre dışı bırak</string>
<string name="disable_encryption_description">Şifrelemeyi devre dışı bırakmak istediğinize emin misiniz? Bu kasa içeriğinin açık metin olarak saklanmasına neden olacaktır.</string>
<string name="enable_encryption_error">Şifrelemeyi aktifleştirirken bir hata meydana geldi</string>
<string name="disable_encryption_error">Şifrelemeyi devre dışı bırakırken bir hata meydana geldi</string>
<string name="backup_error">Yedek oluşturmaya çalışırken bir hata meydana geldi</string>
<string name="permission_denied">İzin verilmesi reddedildi</string>
<string name="andotp_new_format">Yeni biçim (v0.6.3 veya daha yeni) </string>
<string name="andotp_old_format">Eski biçim (v0.6.2 veya daha eski) </string>
<string name="choose_andotp_importer">andOTP yedek dosyası hangi biçimde?</string>
<string name="choose_totpauth_importer">Bu TOTP Authenticator yedeği bir parola ile şifrelenmiş miydi?</string>
<string name="choose_application">Verileri içeriye aktarmak istediğiniz uygulamayı seçin</string>
<string name="choose_theme">İstediğiniz temayı seçin</string>
<string name="choose_view_mode">İstediğiniz görünüm biçimini seçin</string>
<string name="parsing_file_error">Dosyayı anlamlandırmaya çalışırken hata meydana geldi</string>
<string name="file_not_found">Hata: Dosya bulunamadı</string>
<string name="reading_file_error">Dosyayı okumaya çalışırken bir hata meydana geldi</string>
<string name="app_lookup_error">Hata: Uygulama yüklenmemiş</string>
<string name="root_error">Hata: Kök kullanıcı erişimi elde edilemedi</string>
<string name="imported_entries_count">%d girdi içe aktarıldı.</string>
<string name="read_entries_count">%d girdi okundu. %d hatalı.</string>
<string name="import_error_title">İçe aktarmaya çalışırken bir veya birden fazla hata meydana geldi</string>
<string name="exporting_vault_error">Kasayı dışarıya aktarmaya çalışırken hata meydana geldi</string>
<string name="exported_vault">Kasa dışarıya aktarıldı</string>
<string name="export_warning">Bu hareket kasayı Aegis\'in özel depolama alanından dışarıya aktaracak!</string>
<string name="encryption_set_password_error">Parola atamayı denerken bir hata meydana geldi.</string>
<string name="encryption_enable_biometrics_error">Biyometrik kilit açmayı aktifleştirirken bir hata meydana geldi. Bazı cihazlarda biyometrik kimlik doğrulama kötü uygulanmış olabilir ve görünüşe göre cihazınız bu cihazlardan biri. Bunun yerine sadece parola ile doğrulama ayarına geçiş yapın.</string>
<string name="no_cameras_available">Hiçbir kamera kullanılabilir değil</string>
<string name="read_qr_error">QR kodu okumaya çalışırken bir hata meydana geldi</string>
<string name="authentication_method_raw">İşlenmemiş</string>
<string name="unlocking_vault">Kasa kilidi açılıyor</string>
<string name="unlocking_vault_repair">Kasa kilidi açılıyor (tamir ediliyor)</string>
<string name="password_slot_error">En az bir parola elemanınız olmalı</string>
<string name="remove_slot">Eleman sil</string>
<string name="remove_slot_description">Bu elemanı silmek istediğinize emin misiniz?</string>
<string name="remove_group">Grubu sil</string>
<string name="remove_group_description">Bu grubu silmek istediğinize emin misiniz? Bu gruptaki girdiler otomatik olarak \'Grupsuz\' grubuna geçecek.</string>
<string name="adding_new_slot_error">Yeni bir anahtarlık oluşturmaya çalışırken hata oluştu:</string>
<string name="progressbar_error">Animatör süresi ölçeği sıfırlanamıyor. İlerleme çubukları görünmez olacak.</string>
<string name="details">Detaylar</string>
<string name="filter">Filtrele</string>
<string name="filter_ungrouped">Gruplandırılmamış</string>
<string name="lock">Kilitle</string>
<string name="all">Hepsi</string>
<string name="name">Ad</string>
<string name="no_group">Grupsuz</string>
<string name="sort_alphabetically">Sağlayıcı (A\'dan Z\'ye)</string>
<string name="sort_alphabetically_reverse">Sağlayıcı (Z\'den A\'ya)</string>
<string name="sort_alphabetically_name">Hesap (A\'dan Z\'ye)</string>
<string name="sort_alphabetically_name_reverse">Hesap (Z\'den A\'ya)</string>
<string name="sort_custom">İsteğe göre</string>
<string name="new_group">Yeni grup…</string>
<string name="enter_group_name">Yeni grup adı girin</string>
<string name="group_name_hint">Grup adı</string>
<string name="preference_manage_groups">Grupları düzenle</string>
<string name="preference_manage_groups_summary">Gruplandırmalarınızı burada düzenleyebilir ve silebilirsiniz</string>
<string name="pref_search_name_title">Hesap adlarında ara</string>
<string name="pref_search_name_summary">Arama sonuçlarına hesap adı eşleşmelerini de ekle</string>
<string name="pref_highlight_entry_title">Dokunulan kodları belirt</string>
<string name="pref_highlight_entry_summary">Kodların birbirlerinden ayırt edilmelerini kolaylaştırmak için geçici olarak belirginleştir</string>
<string name="tap_to_reveal">Gizli</string>
<string name="selected">Seçilen</string>
<string name="dark_theme_title">Karanlık Tema</string>
<string name="light_theme_title">Aydınlık Tema</string>
<string name="amoled_theme_title">AMOLED Tema</string>
<string name="system_theme_title">Pil Tasarrufu Karar Versin</string>
<string name="system_amoled_theme_title">Pil Tasarrufu Karar Versin (AMOLED)</string>
<string name="normal_viewmode_title">Normal</string>
<string name="compact_mode_title">Sıkı</string>
<string name="small_mode_title">Küçük</string>
<string name="unknown_issuer">Bilinmeyen sağlayıcı</string>
<string name="unknown_account_name">Bilinmeyen hesap adı</string>
<string name="import_error_dialog">Aegis %d kodu içeriye aktaramadı. Bu kodlar atlanacak. Hatalar hakkında daha fazla bilgi görmek için \'Detaylar\'a dokunun.</string>
<string name="unable_to_read_qrcode">QR kod okunamadı ve işlenemedi</string>
<string name="select_picture">Resim seç</string>
<string name="toggle_checkboxes">Kutucukları işaretleyin</string>
<string name="search">Ara</string>
<string name="channel_name_lock_status">Kilit durumu</string>
<string name="channel_description_lock_status">Aegis kasa kilidinin açık olduğunu bildirmek üzere kalıcı bir bildirim gösterebilir.</string>
<string name="vault_unlocked_state">Kasa kilidi açık. Kilitlemek için buraya dokunun.</string>
<string name="title_activity_about">Hakkında</string>
<string name="version">Sürüm</string>
<string name="changelog">Değişiklik Günlüğü</string>
<string name="whats_new">Yeni ne var?</string>
<string name="github_description">Kaynak kod, sorunlar ve bilgiler</string>
<string name="licenses">Lisanslar</string>
<string name="licenses_description">Aegis\'in kullandığı kütüphanelerin lisansları</string>
<string name="country_netherlands">Hollanda</string>
<string name="email_us">Bir e-posta yazın</string>
<string name="visit_website">Sitemizi ziyaret edin</string>
<string name="about_support">Destek</string>
<string name="support_rate">Puanla</string>
<string name="support_rate_description">Bizi desteklemek için Google Play Store\'da görüşünüzü bırakın</string>
<string name="webview_error">Bu cihazda değişiklik günlüğünü görüntülemek için gerekli olan \"web view\" desteklenmiyor. Bu sistem bileşeni yerinde yok.</string>
<string name="email">E-posta</string>
<string name="time_sync_warning_title">Otomatik zaman senkronizasyonu</string>
<string name="time_sync_warning_message">Aegis doğru kodları oluşturmak üzere sistem zamanın senkron olmasına ihtiyaç duyar. Birkaç saniyelik sapma hatalı kod oluşumuna neden olabilir. Görünüşe göre cihazınız zamanı otomatik senkronize etmeye ayarlanmamış. Şimdi ayarlamak ister misiniz?</string>
<string name="time_sync_warning_disable">Beni uyarmayı bırak. Ne yaptığımı biliyorum.</string>
<string name="google_qr_export_unrelated">Alakasız QR kod bulundu. Tarayıcıyı yeniden başlatmayı deneyin.</string>
<string name="google_qr_export_scanned">%d/%d QR kod tarandıQR</string>
<string name="google_qr_export_unexpected">Beklenen QR kodu #%d idi, ancak bunun yerine #%d tarandı</string>
<string name="empty_list">Gösterilecek kod yok. Girdi eklemeye sağ alt köşedeki artı işaretine dokunarak başlayın.</string>
<string name="empty_list_title">Hiçbir girdi bulunamadı.</string>
</resources>
+3
View File
@@ -45,6 +45,7 @@
<item>Greek</item>
<item>Russian</item>
<item>简体中文</item>
<item>Turkish</item>
</string-array>
<string-array name="pref_lang_values" translatable="false">
@@ -60,6 +61,8 @@
<item>el</item>
<item>ru</item>
<item>zh</item>
<item>tr</item>
</string-array>
</resources>
+1
View File
@@ -2,6 +2,7 @@
<resources>
<attr name="primaryText" format="color" />
<attr name="secondaryText" format="color" />
<attr name="authText" format="color" />
<attr name="backgroundColor" format="color" />
<attr name="cardBackground" format="color" />
<attr name="cardBackgroundFocused" format="color" />
+3
View File
@@ -9,6 +9,8 @@
<color name="colorAccentPressed">#FF5252</color>
<color name="primary_text">#212121</color>
<color name="secondary_text">#A0A0A0</color>
<color name="auth_text">#4c4c4c</color>
<color name="auth_button">#0d86c1</color>
<color name="icon_about">#757575</color>
<color name="extra_info_text">#8e8e8e</color>
<color name="primary_text_inverted">#ffffff</color>
@@ -34,6 +36,7 @@
<color name="hint_text_dark">#a7a7a7</color>
<color name="secondary_text_dark">#FF5252</color>
<color name="background_dark">#212121</color>
<color name="background_dark_statusbar">#101010</color>
<color name="background_true_dark">#000000</color>
<color name="warning_color">#f4511e</color>
+21
View File
@@ -7,6 +7,8 @@
<string name="action_about">About</string>
<string name="action_import">Import</string>
<string name="action_delete">Delete</string>
<string name="action_transfer">Transfer</string>
<string name="action_edit_icon">Edit icon</string>
<string name="action_default_icon">Restore default icon</string>
<string name="discard">Discard</string>
<string name="save">Save</string>
@@ -71,6 +73,11 @@
<string name="authentication_method_set_password">Password</string>
<string name="authentication_enter_password">Enter your password</string>
<string name="authentication">Unlock the vault</string>
<!-- The newline (\n) on the following line is just for aesthetic purposes !-->
<string name="authentication_multiline">Unlock\nthe vault</string>
<string name="auth_use_biometrics">Or tap here to use <font fgcolor='#FF0D86C1'>biometrics</font></string>
<string name="set_password">Please enter a password</string>
<string name="set_group">Please enter a group name</string>
<string name="set_number">Please enter a number</string>
@@ -147,6 +154,7 @@
<string name="choose_application">Select the application you\'d like to import from</string>
<string name="choose_theme">Select your desired theme</string>
<string name="choose_view_mode">Select your desired view mode</string>
<string name="intro_crypto_error">An error occurred while trying to set up encryption for your vault. Please try setting up encryption manually in the settings menu of Aegis.</string>
<string name="parsing_file_error">An error occurred while trying to parse the file</string>
<string name="file_not_found">Error: File not found</string>
<string name="reading_file_error">An error occurred while trying to read the file</string>
@@ -189,10 +197,13 @@
<string name="group_name_hint">Group name</string>
<string name="preference_manage_groups">Edit groups</string>
<string name="preference_manage_groups_summary">Manage and delete your groups here</string>
<string name="title_activity_preferences">Settings</string>
<string name="pref_search_name_title">Search in account names</string>
<string name="pref_search_name_summary">Include account name matches in the search results</string>
<string name="pref_highlight_entry_title">Highlight tokens when tapped</string>
<string name="pref_highlight_entry_summary">Make tokens easier to distinguish from each other by temporarily highlighting them when tapped</string>
<string name="pref_copy_on_tap_title">Copy tokens when tapped</string>
<string name="pref_copy_on_tap_summary">Copy tokens to the clipboard by tapping them</string>
<string name="tap_to_reveal">Hidden</string>
<string name="selected">Selected</string>
<string name="dark_theme_title">Dark theme</string>
@@ -207,7 +218,9 @@
<string name="unknown_account_name">Unknown account name</string>
<string name="import_error_dialog">Aegis could not import %d tokens. These tokens will be skipped. Press \'details\' to see more information about the errors.</string>
<string name="unable_to_read_qrcode">Unable to read and process QR code</string>
<string name="unable_to_generate_qrcode">Unable to generate QR code</string>
<string name="select_picture">Select picture</string>
<string name="select_icon">Select icon</string>
<string name="toggle_checkboxes">Toggle checkboxes</string>
<string name="search">Search</string>
<string name="channel_name_lock_status">Lock status</string>
@@ -237,6 +250,7 @@
<string name="google_qr_export_unrelated">Unrelated QR code found. Try restarting the scanner.</string>
<string name="google_qr_export_scanned">Scanned %d/%d QR codes</string>
<string name="google_qr_export_unexpected">Expected QR code #%d, but scanned #%d instead</string>
<string name="backup_error_bar_message"><b>Vault backup failed recently</b></string>
<string name="custom_notices_format_style" translatable="false" >
body {
@@ -256,4 +270,11 @@
</string>
<string name="empty_list">There are no codes to be shown. Start adding entries by tapping the plus sign in the bottom right corner</string>
<string name="empty_list_title">No entries found</string>
<string name="done">Done</string>
<string name="title_activity_transfer">Transfer entries</string>
<string name="entries_count">%d / %d entries</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="transfer_entry">Transfer entry</string>
<string name="transfer_entry_description">Scan this QR code with the authenticator app you would like to transfer this entry to</string>
</resources>
+41 -5
View File
@@ -41,6 +41,47 @@
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
</style>
<style name="AppTheme.Light.NoActionBar" parent="AppTheme">
<item name="android:statusBarColor">@color/background</item>
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="android:windowContentOverlay">@null</item>
<item name="primaryText">@color/primary_text</item>
<item name="background">@color/card_background</item>
<item name="authText">@color/auth_text</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">@color/background</item>
<item name="android:windowLightStatusBar" tools:targetApi="o_mr1">true</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
</style>
<style name="AppTheme.Dark.NoActionBar" parent="AppTheme">
<item name="android:statusBarColor">@color/background_dark_statusbar</item>
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="android:windowContentOverlay">@null</item>
<item name="primaryText">@color/primary_text_dark</item>
<item name="background">@color/background_dark</item>
<item name="authText">@color/primary_text_inverted</item>
<item name="android:navigationBarColor" tools:targetApi="o_mr1">@color/background_dark</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">true</item>
</style>
<style name="AppTheme.TrueBlack.NoActionBar" parent="AppTheme">
<item name="android:statusBarColor">@color/background_true_dark</item>
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="android:windowContentOverlay">@null</item>
<item name="primaryText">@color/primary_text_dark</item>
<item name="background">@color/background_true_dark</item>
<item name="authText">@color/primary_text_inverted</item>
<item name="android:navigationBarColor" tools:targetApi="lollipop">@color/background_true_dark</item>
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">false</item>
</style>
<style name="ActionModeStyle" parent="@style/Widget.AppCompat.Light.ActionMode.Inverse">
<item name="background">@color/colorPrimary</item>
<item name="titleTextStyle">@style/ActionModeTitleTextStyle</item>
@@ -125,11 +166,6 @@
<item name="android:windowBackground">@color/background_true_dark</item>
</style>
<style name="AppTheme.Dark.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.Dark.TransparentActionBar">
<item name="android:actionBarStyle">@style/ThemeActionBar</item>
<item name="android:windowActionBarOverlay">true</item>
+6
View File
@@ -58,6 +58,12 @@
android:title="@string/pref_search_name_title"
android:summary="@string/pref_search_name_summary"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_copy_on_tap"
android:title="@string/pref_copy_on_tap_title"
android:summary="@string/pref_copy_on_tap_summary"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_highlight_entry"
+34
View File
@@ -0,0 +1,34 @@
Çevrimiçi hizmetlerinde doğrulama kodlarınızı yönetmeniz için özgür,
güvenli ve açık kaynak 2FA uygulaması
<b>Şifreleme</b>
Tüm tek kullanımlık kodlarınız bir kasada saklanır. Kasaya bir parola atamayı
seçerseniz, ki bu şiddetle tavsiye edilir, kasanız AES-256 kullanılarak şifrelenecektir.
Kötü niyetli birisi kasanızı ele geçirmeyi başarsa bile kasa içeriğini parolayı bilmeden
elde etmesi mümkün olmayabilir.
<b>Biyometrik Kilit Açma</b>
Her tek kullanımlık koda ihtiyacınız olduğunda kasayı parolayla açmak yavaş olabilir.
Neyse ki, cihanızınız biyometrik sensöre sahipse biyometrik kilit açmayı aktifleştirebilirsiniz.
<b>Uyumluluk</b>
Aegis HOTP ve TOTP algoritmalarını destekler. Bu iki algoritma endüstride standarttır ve
geniş ölçekte desteklenir. Bu Aegis'i binlerce servisle uyumlu yapar.
Bazı örnekler: Google, GitHub, Dropbox, Facebook ve Instagram.
Aynı zamanda Google Authenticator ile de uyumludur. Herhangi bir site Google Authenticator
için QR kod gösteriyorsa bu kod aynı zamanda Aegis tarafından da tanınır.
<b>Gruplama</b>
Bir sürü tek kullanımlık parolanız mı var? Onlara kolay erişmek için gruplandırabilirsiniz.
Kişisel, İş veya Sosyal hepsi kendi grubunda bulunabilir.
<b>Yedekler</b>
Çevrimiçi hesaplarınıza erişiminizi hiçbir zaman kaybetmemeniz için Aegis Authenticator
kasanızı başka bir cihaza aktarabileceğiniz şekilde dışarıya aktarmayı destekler.
Aegis Authenticator AndOTP ve FreeOTP veritabanlarını içeriye aktarmayı da destekler,
bu Aegis'e geçiş yapmanızı sizin için kolaylaştırır.
<b>Açık Kaynak Kod ve Lisanslar</b>
Aegis Authenticator açık kaynaktır (GPL v3 altında lisanslanır) ve
kaynak kodu şurada bulunabilir: http://github.com/beemdevelopment/Aegis
+1
View File
@@ -0,0 +1 @@
Çevrimiçi hizmetlerinde doğrulama kodlarınızı yönetmeniz için özgür, güvenli ve açık kaynak 2FA uygulaması
+1
View File
@@ -0,0 +1 @@
Aegis Authenticator