Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adb80dfe43 | ||
|
|
0f439506bd | ||
|
|
6e54497492 | ||
|
|
9fde453ead | ||
|
|
7cd48f1c09 | ||
|
|
4c3d277fce | ||
|
|
0111626d80 | ||
|
|
0b83546db7 | ||
|
|
08ab8237e7 | ||
|
|
ae5502b650 | ||
|
|
821d0b5147 | ||
|
|
b4ed1f35ce | ||
|
|
a468342187 | ||
|
|
4f6035dc49 | ||
|
|
506adcf81f | ||
|
|
49c8c2d57d | ||
|
|
2ab698c2d3 | ||
|
|
770f5f9724 | ||
|
|
992dcd651b | ||
|
|
98a38b03e4 | ||
|
|
626995ec91 | ||
|
|
00b0c8deaf | ||
|
|
8e5b51c42f | ||
|
|
b81ec0f073 | ||
|
|
d49d42254b | ||
|
|
95d5b4afa1 | ||
|
|
cca8c9bdf7 | ||
|
|
4e00191039 | ||
|
|
dd09595866 | ||
|
|
b27bfc3fa9 | ||
|
|
c6a9716d16 | ||
|
|
acd7a54ee9 | ||
|
|
e7d240549e | ||
|
|
70fedc53b9 | ||
|
|
bed8c9172d | ||
|
|
ef1ac3e5e9 | ||
|
|
cc163f952f | ||
|
|
95180ba963 | ||
|
|
56d7f0ef7b | ||
|
|
fbd6a5931e | ||
|
|
66dd3561cf | ||
|
|
58db3fdaa2 | ||
|
|
92b6738ff4 | ||
|
|
63f57667ba | ||
|
|
34718a7b89 | ||
|
|
6ac91e6cf3 | ||
|
|
a39d6ab346 | ||
|
|
91d89670d2 | ||
|
|
6a2992530e | ||
|
|
1f839a886d |
+31
-24
@@ -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}"
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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());
|
||||
+10
-8
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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ı
|
||||
@@ -0,0 +1 @@
|
||||
Aegis Authenticator
|
||||
Reference in New Issue
Block a user