Compare commits
103
Commits
v1.0-beta1
...
v1.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbedc9d1d3 | ||
|
|
3bdcc25ebe | ||
|
|
d0108e9859 | ||
|
|
025c89d78c | ||
|
|
c52e60b410 | ||
|
|
3cc644193f | ||
|
|
8020792024 | ||
|
|
837a27f9d7 | ||
|
|
5c71f6254b | ||
|
|
bbea6c2ae4 | ||
|
|
fa799e9542 | ||
|
|
7f1ce1e645 | ||
|
|
dc6bf3795a | ||
|
|
a3538f95e2 | ||
|
|
85c358043d | ||
|
|
ab8f01284a | ||
|
|
97d824d779 | ||
|
|
eedf9d2935 | ||
|
|
1ede56e137 | ||
|
|
f8c106bcb9 | ||
|
|
10c206270a | ||
|
|
f7a0ee7174 | ||
|
|
f5e8ba96e6 | ||
|
|
3506fdaad9 | ||
|
|
8824b779f5 | ||
|
|
4cab92d615 | ||
|
|
35a14d3198 | ||
|
|
e46041ece0 | ||
|
|
28ebaf48c8 | ||
|
|
fc96e7919a | ||
|
|
08b80d2e72 | ||
|
|
051cf9370b | ||
|
|
3d13296890 | ||
|
|
93f32c2903 | ||
|
|
f168768033 | ||
|
|
5ab4307963 | ||
|
|
d0baeef064 | ||
|
|
44b83c888c | ||
|
|
da45fa1969 | ||
|
|
5ad3a10251 | ||
|
|
9ec94a472f | ||
|
|
4e4c39eea4 | ||
|
|
3be9aecb88 | ||
|
|
a6a5af781e | ||
|
|
a93ced6e34 | ||
|
|
82135c8b9d | ||
|
|
9f248e0802 | ||
|
|
65aa95504b | ||
|
|
def7c67614 | ||
|
|
be3f30e666 | ||
|
|
b920fe4b8c | ||
|
|
4b6b10526d | ||
|
|
74b2f8f398 | ||
|
|
34ef4b3fb4 | ||
|
|
63b0f6bb99 | ||
|
|
36973669ea | ||
|
|
2e301c5389 | ||
|
|
6a4b0f7bc5 | ||
|
|
8b063815ab | ||
|
|
75b79d3d4a | ||
|
|
b61b73685f | ||
|
|
864e3bd6f4 | ||
|
|
013e4ae28b | ||
|
|
51a0a16afb | ||
|
|
a5165e2345 | ||
|
|
c41a337791 | ||
|
|
e58dec35c0 | ||
|
|
5e7697039a | ||
|
|
45de9cea3a | ||
|
|
9f075e7bc6 | ||
|
|
a88d1cf11e | ||
|
|
a6eec88dcb | ||
|
|
3c4afe36e8 | ||
|
|
5b71df37ec | ||
|
|
d7d3d52a30 | ||
|
|
ac222c4946 | ||
|
|
0200287203 | ||
|
|
5677bd4112 | ||
|
|
cbe3697d68 | ||
|
|
445410fcd7 | ||
|
|
1d513441c6 | ||
|
|
0a31103f91 | ||
|
|
85bf4adc72 | ||
|
|
cc55a6dacb | ||
|
|
e099e2cacd | ||
|
|
658946ff70 | ||
|
|
b6a31ed789 | ||
|
|
9c272a0d4d | ||
|
|
3e626a37db | ||
|
|
e2150e3823 | ||
|
|
6480fef315 | ||
|
|
95df2ceb42 | ||
|
|
189698dddb | ||
|
|
438d5581aa | ||
|
|
7af78b11f5 | ||
|
|
5d68211ce9 | ||
|
|
a7e1a88dc8 | ||
|
|
3054032a37 | ||
|
|
79279630ba | ||
|
|
e019fb6db2 | ||
|
|
85bdecc573 | ||
|
|
21f3d93295 | ||
|
|
888e9d0e4a |
@@ -11,7 +11,8 @@ guidelines](/CONTRIBUTING.md#bug-reports) before submitting an issue. -->
|
||||
|
||||
* __Version__:
|
||||
* __Source__: (Google Play/GitHub/F-Droid/?)
|
||||
* __Vault encrypted:__ Yes (with fingerprint unlock)/Yes/No
|
||||
* __Vault encrypted:__ Yes (with biometric unlock)/Yes/No
|
||||
* __Device:__
|
||||
* __Android version and ROM:__
|
||||
|
||||
###### Steps to reproduce
|
||||
|
||||
@@ -39,3 +39,4 @@ captures/
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
crowdin.properties
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
language: android
|
||||
env:
|
||||
global:
|
||||
- ANDROID_HOME=/usr/local/android-sdk
|
||||
android:
|
||||
components:
|
||||
- android-29
|
||||
- build-tools-29.0.2
|
||||
before_install:
|
||||
- mkdir -p $ANDROID_HOME/licenses
|
||||
- echo "8933bad161af4178b1185d1a37fbf41ea5269c55" > $ANDROID_HOME/licenses/android-sdk-license
|
||||
- echo "d56f5187479451eabf01fb78af6dfcb131a6481e" >> $ANDROID_HOME/licenses/android-sdk-license
|
||||
- echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" >> $ANDROID_HOME/licenses/android-sdk-license
|
||||
script:
|
||||
- ./gradlew build test
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
Aegis is a free, secure and open source 2FA app for Android.
|
||||
|
||||
Aegis' security design and vault format is described in detail in [this document](docs/vault.md).
|
||||
Aegis' security design and vault format is described in detail in [this
|
||||
document](docs/vault.md).
|
||||
|
||||
# Features
|
||||
|
||||
@@ -10,7 +11,7 @@ Aegis' security design and vault format is described in detail in [this document
|
||||
- Secure
|
||||
- Encryption (AES-256)
|
||||
- Password (scrypt)
|
||||
- Fingerprint (Android Keystore)
|
||||
- Biometrics (Android Keystore)
|
||||
- Screen capture prevention
|
||||
- Tap to reveal ability
|
||||
- Multiple ways to add new entries
|
||||
@@ -42,17 +43,22 @@ Aegis' security design and vault format is described in detail in [this document
|
||||
|
||||
## Screenshots
|
||||
|
||||
[<img width=200 alt="Main Activity"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true)
|
||||
[<img width=200 alt="Settings Activity"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true)
|
||||
[<img width=200 alt="Edit Activity"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true)
|
||||
[<img width=200 alt="Screenshot 1"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot1.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot1.png?raw=true)
|
||||
[<img width=200 alt="Screenshot 2"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot2.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot2.png?raw=true)
|
||||
[<img width=200 alt="Screenshot 3"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot3.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot3.png?raw=true)
|
||||
|
||||
[<img width=200 alt="Main Activity"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true)
|
||||
[<img width=200 alt="Main Activity"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true)
|
||||
[<img width=200 alt="Screenshot 4"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot4.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot4.png?raw=true)
|
||||
[<img width=200 alt="Screenshot 5"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot5.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot5.png?raw=true)
|
||||
[<img width=200 alt="Screenshot 6"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot6.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot6.png?raw=true)
|
||||
|
||||
[<img width=200 alt="Screenshot 7"
|
||||
src="metadata/en-US/images/phoneScreenshots/screenshot7.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot7.png?raw=true)
|
||||
|
||||
## Downloads
|
||||
|
||||
@@ -96,6 +102,16 @@ Certificate fingerprints:
|
||||
SHA256: C6:DB:80:A8:E1:4E:52:30:C1:DE:84:15:EF:82:0D:13:DC:90:1D:8F:E3:3C:F3:AC:B5:7B:68:62:D8:58:A8:23
|
||||
```
|
||||
|
||||
### Community
|
||||
|
||||
- [aegis-icons](https://github.com/krisu5/aegis-icons)
|
||||
|
||||
While we're still working on better icon support in Aegis, krisu5 has started
|
||||
a third-party repository for icons that you may want to check out.
|
||||
|
||||
[<img width=500 alt="Aegis-icons preview"
|
||||
src="https://raw.githubusercontent.com/krisu5/aegis-icons/master/showcase.png">](https://github.com/krisu5/aegis-icons)
|
||||
|
||||
## Libraries
|
||||
|
||||
- [TextDrawable](https://github.com/amulyakhare/TextDrawable) by Amulya Khare
|
||||
@@ -104,7 +120,6 @@ Certificate fingerprints:
|
||||
- [AppIntro](https://github.com/AppIntro/AppIntro) by Paolo Rotolo
|
||||
- [Krop](https://github.com/avito-tech/krop) by Avito Technology
|
||||
- [SpongyCastle](https://github.com/rtyley/spongycastle) by Roberto Tyley
|
||||
- [Swirl](https://github.com/mattprecious/swirl) by Matthew Precious
|
||||
- [CircleImageView](https://github.com/hdodenhof/CircleImageView) by Henning
|
||||
Dodenhof
|
||||
- [barcodescanner](https://github.com/dm77/barcodescanner) by Dushyanth
|
||||
@@ -114,7 +129,3 @@ Certificate fingerprints:
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0. See the
|
||||
[LICENSE](LICENSE) file for details.
|
||||
|
||||
## Attribution
|
||||
|
||||
The icon is largely based on a design by Freepik from Flaticon.
|
||||
|
||||
+45
-20
@@ -1,19 +1,43 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
def getCmdOutput = { cmd ->
|
||||
def stdout = new ByteArrayOutputStream()
|
||||
exec {
|
||||
commandLine cmd
|
||||
standardOutput = stdout
|
||||
}
|
||||
return stdout.toString().trim()
|
||||
}
|
||||
|
||||
def getGitHash = { -> return getCmdOutput(["git", "rev-parse", "--short", "HEAD"]) }
|
||||
def getGitBranch = { -> return getCmdOutput(["git", "rev-parse", "--abbrev-ref", "HEAD"]) }
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
compileSdkVersion 29
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.beemdevelopment.aegis"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 28
|
||||
versionCode 18
|
||||
versionName "1.0-beta1"
|
||||
targetSdkVersion 29
|
||||
versionCode 28
|
||||
versionName "1.1.4"
|
||||
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
|
||||
buildConfigField "String", "GIT_BRANCH", "\"${getGitBranch()}\""
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError true
|
||||
disable "MissingTranslation"
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
|
||||
ignoreFailures false
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,32 +63,33 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def libsuVersion = '2.3.2'
|
||||
def libsuVersion = '2.5.1'
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
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.0.0'
|
||||
implementation 'com.takisoft.preferencex:preferencex:1.0.0'
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'com.getbase:floatingactionbutton:1.10.1'
|
||||
implementation 'com.github.apl-devs:appintro:v4.2.2'
|
||||
implementation 'com.github.apl-devs:appintro:5.1.0'
|
||||
implementation 'com.github.avito-tech:krop:0.44'
|
||||
implementation 'com.madgag.spongycastle:core:1.58.0.0'
|
||||
implementation 'com.mattprecious.swirl:swirl:1.0.0'
|
||||
implementation 'de.hdodenhof:circleimageview:2.2.0'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9'
|
||||
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.9.0'
|
||||
implementation 'com.github.bumptech.glide:glide:4.9.0'
|
||||
implementation ("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
|
||||
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") {
|
||||
transitive = false
|
||||
}
|
||||
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.beemdevelopment.aegis;
|
||||
|
||||
import android.app.Application;
|
||||
import android.test.ApplicationTestCase;
|
||||
|
||||
/**
|
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
|
||||
*/
|
||||
public class ApplicationTest extends ApplicationTestCase<Application> {
|
||||
public ApplicationTest() {
|
||||
super(Application.class);
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,23 @@
|
||||
package="com.beemdevelopment.aegis">
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<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" />
|
||||
|
||||
<application
|
||||
android:name=".AegisApplication"
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/${iconName}"
|
||||
android:label="Aegis"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:theme">
|
||||
<activity
|
||||
android:name=".ui.AboutActivity"
|
||||
android:label="@string/title_activity_about" />
|
||||
<activity
|
||||
android:name=".ui.SelectEntriesActivity"
|
||||
android:label="Select entries" />
|
||||
@@ -26,12 +30,15 @@
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="otpauth" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
* {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
body {
|
||||
background-color: %1$s;
|
||||
color: %2$s;
|
||||
}
|
||||
ul {
|
||||
list-style-position: inside;
|
||||
padding: 0;
|
||||
padding-left: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
li {
|
||||
padding-bottom: 8px;
|
||||
list-style-position: outside;
|
||||
margin-left: 1em;
|
||||
}
|
||||
h4 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
h3 {
|
||||
margin-bottom: 7px;
|
||||
padding-top: 7px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div></div>
|
||||
<h3>Version 1.1.4</h3>
|
||||
<h4>Fixes</h4>
|
||||
<ul>
|
||||
<li>The export filename was missing the ".json" extension in some cases</li>
|
||||
</ul>
|
||||
<h3>Version 1.1.3</h3>
|
||||
<h4>New</h4>
|
||||
<ul>
|
||||
<li>Password reminder for users who use biometric unlock</li>
|
||||
</ul>
|
||||
<h4>Fixes</h4>
|
||||
<ul>
|
||||
<li>Tokens would not refresh in some rare cases</li>
|
||||
</ul>
|
||||
<h3>Version 1.1.2</h3>
|
||||
<h4>New</h4>
|
||||
<ul>
|
||||
<li>Ability to select multiple entries</li>
|
||||
<li>Ability to select a file location when exporting the vault (including cloud providers like Google Drive)</li>
|
||||
<li>Explanation and warning for the security options</li>
|
||||
<li>Removed external storage permissions</li>
|
||||
</ul>
|
||||
<h3>Version 1.1.1</h3>
|
||||
<h4>Fixes</h4>
|
||||
<ul>
|
||||
<li>Exporting the vault did not work on Android 10</li>
|
||||
</ul>
|
||||
<h3>Version 1.1</h3>
|
||||
<h4>New</h4>
|
||||
<ul>
|
||||
<li>Support for other types of biometric authentication (i.e. Pixel 4 face unlock)</li>
|
||||
<li>Support for importing from WinAuth</li>
|
||||
<li>Support for Chromebooks</li>
|
||||
<li>Option to highlight entries when tapped</li>
|
||||
<li>Filter for ungrouped tokens</li>
|
||||
<li>Ability to search for token account names</li>
|
||||
<li>Simplified Chinese translation (thanks RunningMelos!)</li>
|
||||
<li>Updated translations (thanks to all Crowdin contributers!)</li>
|
||||
</ul>
|
||||
<h4>Fixes</h4>
|
||||
<ul>
|
||||
<li>The behavior of highlighting and revealing entries was inconsistent</li>
|
||||
<li>The changelog dialog didn't work</li>
|
||||
<li>The persistent notification was shown even after the app was killed</li>
|
||||
</ul>
|
||||
<h3>Version 1.0.3</h3>
|
||||
<h4>New</h4>
|
||||
<ul>
|
||||
<li>Support for andOTP's new backup file format</li>
|
||||
</ul>
|
||||
<h3>Version 1.0.2</h3>
|
||||
<h4>Fixes</h4>
|
||||
<ul>
|
||||
<li>Search feature on Huawei devices</li>
|
||||
</ul>
|
||||
<h4>Notes</h4>
|
||||
<ul>
|
||||
<li>Disabled automatic backups through the Google Play Store</li>
|
||||
</ul>
|
||||
<h3>Version 1.0.1</h3>
|
||||
<h4>Notes</h4>
|
||||
<ul>
|
||||
<li>Temporarily disabled search feature on Huawei devices</li>
|
||||
</ul>
|
||||
<h3>Version 1.0</h3>
|
||||
<h4>New</h4>
|
||||
<ul>
|
||||
<li>New icon</li>
|
||||
<li>Overhaul of interaction with the entry list</li>
|
||||
<li>Persistent notification while the vault is unlocked</li>
|
||||
<li>Language override option</li>
|
||||
<li>Support for importing from FreeOTP+</li>
|
||||
<li>Ability to toggle password visibility during unlock</li>
|
||||
<li>Support for deeplinking otpauth URIs</li>
|
||||
</ul>
|
||||
<h4>Fixes</h4>
|
||||
<ul>
|
||||
<li>Bad overall performance and high battery usage</li>
|
||||
<li>Codes with an uneven number of digits are displayed incorrectly</li>
|
||||
<li>Crash when entering a large value for OTP period</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -12,36 +12,41 @@ import android.content.pm.ShortcutManager;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseManager;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.beemdevelopment.aegis.services.NotificationService;
|
||||
import com.beemdevelopment.aegis.ui.MainActivity;
|
||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||
import com.mikepenz.iconics.Iconics;
|
||||
import com.mikepenz.material_design_iconic_typeface_library.MaterialDesignIconic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public class AegisApplication extends Application {
|
||||
private DatabaseManager _manager;
|
||||
private VaultManager _manager;
|
||||
private Preferences _prefs;
|
||||
private List<LockListener> _lockListeners;
|
||||
|
||||
private static final String CODE_LOCK_STATUS_ID = "lock_status_channel";
|
||||
private static final String CODE_LOCK_DATABASE_ACTION = "lock_database";
|
||||
private static final String CODE_LOCK_VAULT_ACTION = "lock_vault";
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
_manager = new DatabaseManager(this);
|
||||
_manager = new VaultManager(this);
|
||||
_prefs = new Preferences(this);
|
||||
_lockListeners = new ArrayList<>();
|
||||
|
||||
Iconics.init(this);
|
||||
Iconics.registerFont(new MaterialDesignIconic());
|
||||
|
||||
// listen for SCREEN_OFF events
|
||||
ScreenOffReceiver receiver = new ScreenOffReceiver();
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
intentFilter.addAction(CODE_LOCK_DATABASE_ACTION);
|
||||
intentFilter.addAction(CODE_LOCK_VAULT_ACTION);
|
||||
registerReceiver(receiver, intentFilter);
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
@@ -53,7 +58,7 @@ public class AegisApplication extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
public DatabaseManager getDatabaseManager() {
|
||||
public VaultManager getVaultManager() {
|
||||
return _manager;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.beemdevelopment.aegis;
|
||||
|
||||
public enum CancelAction {
|
||||
KILL,
|
||||
CLOSE
|
||||
}
|
||||
|
||||
@@ -6,26 +6,58 @@ import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Preferences {
|
||||
private SharedPreferences _prefs;
|
||||
|
||||
public Preferences(Context context) {
|
||||
_prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
if (getPasswordReminderTimestamp().getTime() == 0) {
|
||||
resetPasswordReminderTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isTapToRevealEnabled() {
|
||||
return _prefs.getBoolean("pref_tap_to_reveal", false);
|
||||
}
|
||||
|
||||
public boolean isSearchAccountNameEnabled() {
|
||||
return _prefs.getBoolean("pref_search_names", false);
|
||||
}
|
||||
|
||||
public boolean isEntryHighlightEnabled() {
|
||||
return _prefs.getBoolean("pref_highlight_entry", false);
|
||||
}
|
||||
|
||||
public boolean isSecureScreenEnabled() {
|
||||
// screen security should be enabled by default, but not for debug builds
|
||||
return _prefs.getBoolean("pref_secure_screen", !BuildConfig.DEBUG);
|
||||
}
|
||||
|
||||
public boolean isPasswordReminderEnabled() {
|
||||
return _prefs.getBoolean("pref_password_reminder", true);
|
||||
}
|
||||
|
||||
public boolean isPasswordReminderNeeded() {
|
||||
long diff = new Date().getTime() - getPasswordReminderTimestamp().getTime();
|
||||
long days = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
|
||||
return isPasswordReminderEnabled() && days >= 7;
|
||||
}
|
||||
|
||||
public Date getPasswordReminderTimestamp() {
|
||||
return new Date(_prefs.getLong("pref_password_reminder_counter", 0));
|
||||
}
|
||||
|
||||
public void resetPasswordReminderTimestamp() {
|
||||
_prefs.edit().putLong("pref_password_reminder_counter", new Date().getTime()).apply();
|
||||
}
|
||||
|
||||
public boolean isAccountNameVisible() {
|
||||
return _prefs.getBoolean("pref_account_name", false);
|
||||
return _prefs.getBoolean("pref_account_name", true);
|
||||
}
|
||||
|
||||
public boolean isIntroDone() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.beemdevelopment.aegis;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.helpers.comparators.AccountNameComparator;
|
||||
import com.beemdevelopment.aegis.helpers.comparators.IssuerNameComparator;
|
||||
|
||||
@@ -24,8 +24,8 @@ public enum SortCategory {
|
||||
return _values[x];
|
||||
}
|
||||
|
||||
public Comparator<DatabaseEntry> getComparator() {
|
||||
Comparator<DatabaseEntry> comparator = null;
|
||||
public Comparator<VaultEntry> getComparator() {
|
||||
Comparator<VaultEntry> comparator = null;
|
||||
|
||||
switch (this) {
|
||||
case ACCOUNT:
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
public class DatabaseException extends Exception {
|
||||
public DatabaseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DatabaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
public class DatabaseFileException extends Exception {
|
||||
public DatabaseFileException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public DatabaseFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Environment;
|
||||
|
||||
import com.beemdevelopment.aegis.BuildConfig;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.services.NotificationService;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.Collator;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DatabaseManager {
|
||||
private static final String FILENAME = "aegis.json";
|
||||
private static final String FILENAME_EXPORT = "aegis_export.json";
|
||||
private static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
|
||||
|
||||
private Database _db;
|
||||
private DatabaseFile _file;
|
||||
private DatabaseFileCredentials _creds;
|
||||
private boolean _encrypt;
|
||||
|
||||
private Context _context;
|
||||
|
||||
public DatabaseManager(Context context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public boolean fileExists() {
|
||||
File file = new File(_context.getFilesDir(), FILENAME);
|
||||
return file.exists() && file.isFile();
|
||||
}
|
||||
|
||||
public void load() throws DatabaseManagerException {
|
||||
assertState(true, false);
|
||||
|
||||
try (FileInputStream file = _context.openFileInput(FILENAME)) {
|
||||
byte[] fileBytes = new byte[(int) file.getChannel().size()];
|
||||
DataInputStream stream = new DataInputStream(file);
|
||||
stream.readFully(fileBytes);
|
||||
stream.close();
|
||||
|
||||
_file = DatabaseFile.fromBytes(fileBytes);
|
||||
_encrypt = _file.isEncrypted();
|
||||
if (!isEncryptionEnabled()) {
|
||||
JSONObject obj = _file.getContent();
|
||||
_db = Database.fromJson(obj);
|
||||
}
|
||||
} catch (IOException | DatabaseFileException | DatabaseException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void lock() {
|
||||
assertState(false, true);
|
||||
_creds = null;
|
||||
_db = null;
|
||||
}
|
||||
|
||||
public void unlock(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
||||
assertState(true, true);
|
||||
|
||||
try {
|
||||
JSONObject obj = _file.getContent(creds);
|
||||
_db = Database.fromJson(obj);
|
||||
_creds = creds;
|
||||
_context.startService(new Intent(_context, NotificationService.class));
|
||||
} catch (DatabaseFileException | DatabaseException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void save(Context context, DatabaseFile file) throws DatabaseManagerException {
|
||||
byte[] bytes = file.toBytes();
|
||||
try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
|
||||
stream.write(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
|
||||
try {
|
||||
JSONObject obj = _db.toJson();
|
||||
if (isEncryptionEnabled()) {
|
||||
_file.setContent(obj, _creds);
|
||||
} else {
|
||||
_file.setContent(obj);
|
||||
}
|
||||
save(_context, _file);
|
||||
} catch (DatabaseFileException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String export(boolean encrypt) throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
|
||||
try {
|
||||
DatabaseFile dbFile = new DatabaseFile();
|
||||
if (encrypt && isEncryptionEnabled()) {
|
||||
dbFile.setContent(_db.toJson(), _creds);
|
||||
} else {
|
||||
dbFile.setContent(_db.toJson());
|
||||
}
|
||||
|
||||
String dirName = !BuildConfig.DEBUG ? _context.getString(R.string.app_name) : _context.getString(R.string.app_name_dev);
|
||||
File dir = new File(Environment.getExternalStorageDirectory(), dirName);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
throw new IOException("error creating external storage directory");
|
||||
}
|
||||
|
||||
byte[] bytes = dbFile.toBytes();
|
||||
File file = new File(dir.getAbsolutePath(), encrypt ? FILENAME_EXPORT : FILENAME_EXPORT_PLAIN);
|
||||
try (FileOutputStream stream = new FileOutputStream(file)) {
|
||||
stream.write(bytes);
|
||||
}
|
||||
|
||||
return file.getAbsolutePath();
|
||||
} catch (IOException | DatabaseFileException e) {
|
||||
throw new DatabaseManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void addEntry(DatabaseEntry entry) {
|
||||
assertState(false, true);
|
||||
_db.getEntries().add(entry);
|
||||
}
|
||||
|
||||
public DatabaseEntry removeEntry(DatabaseEntry entry) {
|
||||
assertState(false, true);
|
||||
return _db.getEntries().remove(entry);
|
||||
}
|
||||
|
||||
public DatabaseEntry replaceEntry(DatabaseEntry entry) {
|
||||
assertState(false, true);
|
||||
return _db.getEntries().replace(entry);
|
||||
}
|
||||
|
||||
public void swapEntries(DatabaseEntry entry1, DatabaseEntry entry2) {
|
||||
assertState(false, true);
|
||||
_db.getEntries().swap(entry1, entry2);
|
||||
}
|
||||
|
||||
public boolean isEntryDuplicate(DatabaseEntry entry) {
|
||||
assertState(false, true);
|
||||
return _db.getEntries().has(entry);
|
||||
}
|
||||
|
||||
public Collection<DatabaseEntry> getEntries() {
|
||||
assertState(false, true);
|
||||
return _db.getEntries().getValues();
|
||||
}
|
||||
|
||||
public TreeSet<String> getGroups() {
|
||||
assertState(false, true);
|
||||
|
||||
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
||||
for (DatabaseEntry entry : getEntries()) {
|
||||
String group = entry.getGroup();
|
||||
if (group != null) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
public DatabaseFileCredentials getCredentials() {
|
||||
assertState(false, true);
|
||||
return _creds;
|
||||
}
|
||||
|
||||
public void setCredentials(DatabaseFileCredentials creds) {
|
||||
assertState(false, true);
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public DatabaseFile.Header getFileHeader() {
|
||||
assertLoaded(true);
|
||||
return _file.getHeader();
|
||||
}
|
||||
|
||||
public boolean isEncryptionEnabled() {
|
||||
assertLoaded(true);
|
||||
return _encrypt;
|
||||
}
|
||||
|
||||
public void enableEncryption(DatabaseFileCredentials creds) throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
_creds = creds;
|
||||
_encrypt = true;
|
||||
save();
|
||||
}
|
||||
|
||||
public void disableEncryption() throws DatabaseManagerException {
|
||||
assertState(false, true);
|
||||
_creds = null;
|
||||
_encrypt = false;
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return _file != null;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return _db == null;
|
||||
}
|
||||
|
||||
private void assertState(boolean locked, boolean loaded) {
|
||||
assertLoaded(loaded);
|
||||
|
||||
if (isLocked() && !locked) {
|
||||
throw new AssertionError("database file has not been unlocked yet");
|
||||
} else if (!isLocked() && locked) {
|
||||
throw new AssertionError("database file has already been unlocked");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertLoaded(boolean loaded) {
|
||||
if (isLoaded() && !loaded) {
|
||||
throw new AssertionError("database file has already been loaded");
|
||||
} else if (!isLoaded() && loaded) {
|
||||
throw new AssertionError("database file has not been loaded yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
|
||||
public class DatabaseManagerException extends Exception {
|
||||
public DatabaseManagerException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FingerprintSlot extends RawSlot {
|
||||
public FingerprintSlot() {
|
||||
super();
|
||||
}
|
||||
|
||||
FingerprintSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
|
||||
super(uuid, key, keyParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return TYPE_FINGERPRINT;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* A class that can prepare initialization of a BiometricSlot by generating a new
|
||||
* key in the Android KeyStore and authenticating a cipher for it through a
|
||||
* BiometricPrompt.
|
||||
*/
|
||||
public class BiometricSlotInitializer extends BiometricPrompt.AuthenticationCallback {
|
||||
private BiometricSlot _slot;
|
||||
private Listener _listener;
|
||||
private BiometricPrompt _prompt;
|
||||
|
||||
public BiometricSlotInitializer(Fragment fragment, Listener listener) {
|
||||
_listener = listener;
|
||||
_prompt = new BiometricPrompt(fragment, new UiThreadExecutor(), this);
|
||||
}
|
||||
|
||||
public BiometricSlotInitializer(FragmentActivity activity, Listener listener) {
|
||||
_listener = listener;
|
||||
_prompt = new BiometricPrompt(activity, new UiThreadExecutor(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new key in the Android KeyStore for the new BiometricSlot,
|
||||
* initializes a cipher with it and shows a BiometricPrompt to the user for
|
||||
* authentication. If authentication is successful, the new slot will be
|
||||
* initialized and delivered back through the listener.
|
||||
*/
|
||||
public void authenticate(BiometricPrompt.PromptInfo info) {
|
||||
if (_slot != null) {
|
||||
throw new IllegalStateException("Biometric authentication already in progress");
|
||||
}
|
||||
|
||||
KeyStoreHandle keyStore;
|
||||
try {
|
||||
keyStore = new KeyStoreHandle();
|
||||
} catch (KeyStoreHandleException e) {
|
||||
fail(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// generate a new Android KeyStore key
|
||||
// and assign it the UUID of the new slot as an alias
|
||||
Cipher cipher;
|
||||
BiometricSlot slot = new BiometricSlot();
|
||||
try {
|
||||
SecretKey key = keyStore.generateKey(slot.getUUID().toString());
|
||||
cipher = Slot.createEncryptCipher(key);
|
||||
} catch (KeyStoreHandleException | SlotException e) {
|
||||
fail(e);
|
||||
return;
|
||||
}
|
||||
|
||||
_slot = slot;
|
||||
_prompt.authenticate(info, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the BiometricPrompt and resets the state of the initializer. It will
|
||||
* also attempt to delete the previously generated Android KeyStore key.
|
||||
*/
|
||||
public void cancelAuthentication() {
|
||||
if (_slot == null) {
|
||||
throw new IllegalStateException("Biometric authentication not in progress");
|
||||
}
|
||||
|
||||
reset();
|
||||
_prompt.cancelAuthentication();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
if (_slot != null) {
|
||||
try {
|
||||
// clean up the unused KeyStore key
|
||||
// this is non-critical, so just fail silently if an error occurs
|
||||
String uuid = _slot.getUUID().toString();
|
||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||
if (keyStore.containsKey(uuid)) {
|
||||
keyStore.deleteKey(uuid);
|
||||
}
|
||||
} catch (KeyStoreHandleException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
_slot = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(int errorCode, CharSequence errString) {
|
||||
reset();
|
||||
_listener.onSlotInitializationFailed(errorCode, errString);
|
||||
}
|
||||
|
||||
private void fail(Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(0, e.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
super.onAuthenticationError(errorCode, errString);
|
||||
fail(errorCode, errString.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
_listener.onInitializeSlot(_slot, Objects.requireNonNull(result.getCryptoObject()).getCipher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onInitializeSlot(BiometricSlot slot, Cipher cipher);
|
||||
void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.biometric.BiometricConstants;
|
||||
import androidx.biometric.BiometricManager;
|
||||
|
||||
public class BiometricsHelper {
|
||||
private BiometricsHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static BiometricManager getManager(Context context) {
|
||||
BiometricManager manager = BiometricManager.from(context);
|
||||
if (manager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
return manager;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isCanceled(int errorCode) {
|
||||
return errorCode == BiometricConstants.ERROR_CANCELED
|
||||
|| errorCode == BiometricConstants.ERROR_USER_CANCELED
|
||||
|| errorCode == BiometricConstants.ERROR_NEGATIVE_BUTTON;
|
||||
}
|
||||
|
||||
public static boolean isAvailable(Context context) {
|
||||
return getManager(context) != null;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
|
||||
public class FingerprintHelper {
|
||||
private FingerprintHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static FingerprintManager getManager(Context context) {
|
||||
if (isSupported() && PermissionHelper.granted(context, Manifest.permission.USE_FINGERPRINT)) {
|
||||
FingerprintManager manager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
if (manager != null && manager.isHardwareDetected() && manager.hasEnrolledFingerprints()) {
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
// This file was originally taken from https://github.com/googlesamples/android-FingerprintDialog/blob/2feb02945ae220ebd1bc2c2b620a1d43e30daea8/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintUiHelper.java
|
||||
// It has been modified to suit Aegis' needs
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/**
|
||||
* Small helper class to manage text/icon around fingerprint authentication UI.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
|
||||
|
||||
private static final long ERROR_TIMEOUT_MILLIS = 1600;
|
||||
private static final long SUCCESS_DELAY_MILLIS = 100;
|
||||
|
||||
private final FingerprintManager mFingerprintManager;
|
||||
private final SwirlView mIcon;
|
||||
private final TextView mErrorTextView;
|
||||
private final Callback mCallback;
|
||||
private CancellationSignal mCancellationSignal;
|
||||
|
||||
private boolean mSelfCancelled;
|
||||
|
||||
/**
|
||||
* Constructor for {@link FingerprintUiHelper}.
|
||||
*/
|
||||
public FingerprintUiHelper(FingerprintManager fingerprintManager,
|
||||
SwirlView icon, TextView errorTextView, Callback callback) {
|
||||
mFingerprintManager = fingerprintManager;
|
||||
mIcon = icon;
|
||||
mErrorTextView = errorTextView;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public boolean isFingerprintAuthAvailable() {
|
||||
// The line below prevents the false positive inspection from Android Studio
|
||||
// noinspection ResourceType
|
||||
return mFingerprintManager.isHardwareDetected()
|
||||
&& mFingerprintManager.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
|
||||
if (!isFingerprintAuthAvailable()) {
|
||||
return;
|
||||
}
|
||||
mCancellationSignal = new CancellationSignal();
|
||||
mSelfCancelled = false;
|
||||
// The line below prevents the false positive inspection from Android Studio
|
||||
// noinspection ResourceType
|
||||
mFingerprintManager
|
||||
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
|
||||
mIcon.setState(SwirlView.State.ON);
|
||||
}
|
||||
|
||||
public void stopListening() {
|
||||
if (mCancellationSignal != null) {
|
||||
mSelfCancelled = true;
|
||||
mCancellationSignal.cancel();
|
||||
mCancellationSignal = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
||||
if (!mSelfCancelled) {
|
||||
showError(errString);
|
||||
mIcon.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCallback.onError();
|
||||
}
|
||||
}, ERROR_TIMEOUT_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
|
||||
showError(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
showError(mIcon.getResources().getString(
|
||||
R.string.fingerprint_not_recognized));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
|
||||
mIcon.setState(SwirlView.State.OFF);
|
||||
mErrorTextView.setText(
|
||||
mErrorTextView.getResources().getString(R.string.fingerprint_success));
|
||||
mIcon.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCallback.onAuthenticated();
|
||||
}
|
||||
}, SUCCESS_DELAY_MILLIS);
|
||||
|
||||
// ugly hack to keep the fingerprint icon visible while also giving visual feedback of success to the user
|
||||
mIcon.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mIcon.setState(SwirlView.State.ON);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private void showError(CharSequence error) {
|
||||
mIcon.setState(SwirlView.State.ERROR);
|
||||
mErrorTextView.setText(error);
|
||||
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
|
||||
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
private Runnable mResetErrorTextRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mErrorTextView.setText(
|
||||
mErrorTextView.getResources().getString(R.string.fingerprint_hint));
|
||||
mIcon.setState(SwirlView.State.ON);
|
||||
}
|
||||
};
|
||||
|
||||
public interface Callback {
|
||||
|
||||
void onAuthenticated();
|
||||
|
||||
void onError();
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -3,14 +3,12 @@ package com.beemdevelopment.aegis.helpers;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.ui.views.EntryAdapter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
|
||||
private DatabaseEntry _selectedEntry;
|
||||
private VaultEntry _selectedEntry;
|
||||
|
||||
private final ItemTouchHelperAdapter _adapter;
|
||||
private boolean _positionChanged = false;
|
||||
@@ -29,7 +27,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
_isLongPressDragEnabled = enabled;
|
||||
}
|
||||
|
||||
public void setSelectedEntry(DatabaseEntry entry) {
|
||||
public void setSelectedEntry(VaultEntry entry) {
|
||||
_selectedEntry = entry;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class UiThreadExecutor implements Executor {
|
||||
private final Handler _handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void execute(@NonNull Runnable command) {
|
||||
_handler.post(command);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
package com.beemdevelopment.aegis.helpers.comparators;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class AccountNameComparator implements Comparator<DatabaseEntry> {
|
||||
public class AccountNameComparator implements Comparator<VaultEntry> {
|
||||
@Override
|
||||
public int compare(DatabaseEntry a, DatabaseEntry b) {
|
||||
public int compare(VaultEntry a, VaultEntry b) {
|
||||
return a.getName().compareToIgnoreCase(b.getName());
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
package com.beemdevelopment.aegis.helpers.comparators;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class IssuerNameComparator implements Comparator<DatabaseEntry> {
|
||||
public class IssuerNameComparator implements Comparator<VaultEntry> {
|
||||
@Override
|
||||
public int compare(DatabaseEntry a, DatabaseEntry b) {
|
||||
public int compare(VaultEntry a, VaultEntry b) {
|
||||
return a.getIssuer().compareToIgnoreCase(b.getIssuer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package com.beemdevelopment.aegis.importers;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFile;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultFile;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
|
||||
@@ -36,20 +36,20 @@ public class AegisImporter extends DatabaseImporter {
|
||||
public State read(FileReader reader) throws DatabaseImporterException {
|
||||
try {
|
||||
byte[] bytes = reader.readAll();
|
||||
DatabaseFile file = DatabaseFile.fromBytes(bytes);
|
||||
VaultFile file = VaultFile.fromBytes(bytes);
|
||||
if (file.isEncrypted()) {
|
||||
return new EncryptedState(file);
|
||||
}
|
||||
return new DecryptedState(file.getContent());
|
||||
} catch (DatabaseFileException | IOException e) {
|
||||
} catch (VaultFileException | IOException e) {
|
||||
throw new DatabaseImporterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class EncryptedState extends State {
|
||||
private DatabaseFile _file;
|
||||
private VaultFile _file;
|
||||
|
||||
private EncryptedState(DatabaseFile file) {
|
||||
private EncryptedState(VaultFile file) {
|
||||
super(true);
|
||||
_file = file;
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class AegisImporter extends DatabaseImporter {
|
||||
return _file.getHeader().getSlots();
|
||||
}
|
||||
|
||||
public State decrypt(DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||
public State decrypt(VaultFileCredentials creds) throws VaultFileException {
|
||||
JSONObject obj = _file.getContent(creds);
|
||||
return new DecryptedState(obj);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class AegisImporter extends DatabaseImporter {
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject entryObj = array.getJSONObject(i);
|
||||
try {
|
||||
DatabaseEntry entry = convertEntry(entryObj);
|
||||
VaultEntry entry = convertEntry(entryObj);
|
||||
result.addEntry(entry);
|
||||
} catch (DatabaseImporterEntryException e) {
|
||||
result.addError(e);
|
||||
@@ -99,9 +99,9 @@ public class AegisImporter extends DatabaseImporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
try {
|
||||
return DatabaseEntry.fromJson(obj);
|
||||
return VaultEntry.fromJson(obj);
|
||||
} catch (JSONException | OtpInfoException | Base64Exception e) {
|
||||
throw new DatabaseImporterEntryException(e, obj.toString());
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package com.beemdevelopment.aegis.importers;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
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.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.encoding.Base32;
|
||||
import com.beemdevelopment.aegis.encoding.Base32Exception;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
@@ -20,11 +23,14 @@ import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
@@ -32,9 +38,18 @@ import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
public class AndOtpImporter extends DatabaseImporter {
|
||||
private static final int INT_SIZE = 4;
|
||||
private static final int NONCE_SIZE = 12;
|
||||
private static final int TAG_SIZE = 16;
|
||||
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);
|
||||
@@ -81,30 +96,48 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public DecryptedState decrypt(char[] password) throws DatabaseImporterException {
|
||||
private DecryptedState decrypt(char[] password, boolean oldFormat) throws DatabaseImporterException {
|
||||
try {
|
||||
// WARNING: DON'T DO THIS IN YOUR OWN CODE
|
||||
// this exists solely to support encrypted andOTP backups
|
||||
// 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));
|
||||
SecretKey key = new SecretKeySpec(keyBytes, "AES");
|
||||
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, 0, CryptoUtils.CRYPTO_AEAD_NONCE_SIZE);
|
||||
byte[] tag = Arrays.copyOfRange(_data, _data.length - CryptoUtils.CRYPTO_AEAD_TAG_SIZE, _data.length);
|
||||
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 offset = CryptoUtils.CRYPTO_AEAD_NONCE_SIZE;
|
||||
int len = _data.length - CryptoUtils.CRYPTO_AEAD_NONCE_SIZE - CryptoUtils.CRYPTO_AEAD_TAG_SIZE;
|
||||
CryptResult result = CryptoUtils.decrypt(_data, offset, len, cipher, params);
|
||||
int len = _data.length - offset - NONCE_SIZE - TAG_SIZE;
|
||||
CryptResult result = CryptoUtils.decrypt(_data, offset + NONCE_SIZE, len, cipher, params);
|
||||
return read(result.getData());
|
||||
} catch (IOException | BadPaddingException | JSONException e) {
|
||||
throw new DatabaseImporterException(e);
|
||||
} catch (NoSuchAlgorithmException
|
||||
| InvalidAlgorithmParameterException
|
||||
| InvalidKeyException
|
||||
| InvalidKeySpecException
|
||||
| NoSuchPaddingException
|
||||
| IllegalBlockSizeException e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -113,14 +146,26 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||
|
||||
@Override
|
||||
public void decrypt(Context context, DecryptListener listener) {
|
||||
Dialogs.showPasswordInputDialog(context, password -> {
|
||||
try {
|
||||
DecryptedState state = decrypt(password);
|
||||
listener.onStateDecrypted(state);
|
||||
} catch (DatabaseImporterException e) {
|
||||
listener.onError(e);
|
||||
}
|
||||
});
|
||||
String[] choices = new String[]{
|
||||
context.getResources().getString(R.string.andotp_new_format),
|
||||
context.getResources().getString(R.string.andotp_old_format)
|
||||
};
|
||||
|
||||
Dialogs.showSecureDialog(new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.choose_andotp_importer)
|
||||
.setSingleChoiceItems(choices, 0, null)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||
Dialogs.showPasswordInputDialog(context, password -> {
|
||||
try {
|
||||
DecryptedState state = decrypt(password, i != 0);
|
||||
listener.onStateDecrypted(state);
|
||||
} catch (DatabaseImporterException e) {
|
||||
listener.onError(e);
|
||||
}
|
||||
});
|
||||
})
|
||||
.create());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +184,7 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||
for (int i = 0; i < _obj.length(); i++) {
|
||||
try {
|
||||
JSONObject obj = _obj.getJSONObject(i);
|
||||
DatabaseEntry entry = convertEntry(obj);
|
||||
VaultEntry entry = convertEntry(obj);
|
||||
result.addEntry(entry);
|
||||
} catch (JSONException e) {
|
||||
throw new DatabaseImporterException(e);
|
||||
@@ -151,7 +196,7 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
try {
|
||||
String type = obj.getString("type").toLowerCase();
|
||||
String algo = obj.getString("algorithm");
|
||||
@@ -184,7 +229,7 @@ public class AndOtpImporter extends DatabaseImporter {
|
||||
name = parts[0];
|
||||
}
|
||||
|
||||
return new DatabaseEntry(info, name, issuer);
|
||||
return new VaultEntry(info, name, issuer);
|
||||
} catch (DatabaseImporterException | Base32Exception | OtpInfoException | JSONException e) {
|
||||
throw new DatabaseImporterEntryException(e, obj.toString());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.beemdevelopment.aegis.importers;
|
||||
import android.content.Context;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.encoding.Base32;
|
||||
import com.beemdevelopment.aegis.encoding.Base32Exception;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
@@ -74,7 +74,7 @@ public class AuthyImporter extends DatabaseImporter {
|
||||
for (int i = 0; i < _obj.length(); i++) {
|
||||
JSONObject entryObj = _obj.getJSONObject(i);
|
||||
try {
|
||||
DatabaseEntry entry = convertEntry(entryObj);
|
||||
VaultEntry entry = convertEntry(entryObj);
|
||||
result.addEntry(entry);
|
||||
} catch (DatabaseImporterEntryException e) {
|
||||
result.addError(e);
|
||||
@@ -87,7 +87,7 @@ public class AuthyImporter extends DatabaseImporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DatabaseEntry convertEntry(JSONObject entry) throws DatabaseImporterEntryException {
|
||||
private static VaultEntry convertEntry(JSONObject entry) throws DatabaseImporterEntryException {
|
||||
try {
|
||||
AuthyEntryInfo authyEntryInfo = new AuthyEntryInfo();
|
||||
authyEntryInfo.OriginalName = entry.getString("originalName");
|
||||
@@ -101,7 +101,7 @@ public class AuthyImporter extends DatabaseImporter {
|
||||
|
||||
OtpInfo info = new TotpInfo(secret, "SHA1", digits, 30);
|
||||
|
||||
return new DatabaseEntry(info, authyEntryInfo.Name, authyEntryInfo.Issuer);
|
||||
return new VaultEntry(info, authyEntryInfo.Name, authyEntryInfo.Issuer);
|
||||
} catch (OtpInfoException | JSONException | Base32Exception e) {
|
||||
throw new DatabaseImporterEntryException(e, entry.toString());
|
||||
}
|
||||
|
||||
@@ -2,17 +2,12 @@ package com.beemdevelopment.aegis.importers;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.util.ByteInputStream;
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -38,6 +33,7 @@ public abstract class DatabaseImporter {
|
||||
_importers.put("FreeOTP+", FreeOtpPlusImporter.class);
|
||||
_importers.put("Google Authenticator", GoogleAuthImporter.class);
|
||||
_importers.put("Steam", SteamImporter.class);
|
||||
_importers.put("WinAuth", WinAuthImporter.class);
|
||||
|
||||
_appImporters = new LinkedHashMap<>();
|
||||
_appImporters.put("Authy", AuthyImporter.class);
|
||||
@@ -116,10 +112,10 @@ public abstract class DatabaseImporter {
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
private UUIDMap<DatabaseEntry> _entries = new UUIDMap<>();
|
||||
private UUIDMap<VaultEntry> _entries = new UUIDMap<>();
|
||||
private List<DatabaseImporterEntryException> _errors = new ArrayList<>();
|
||||
|
||||
public void addEntry(DatabaseEntry entry) {
|
||||
public void addEntry(VaultEntry entry) {
|
||||
_entries.add(entry);
|
||||
}
|
||||
|
||||
@@ -127,7 +123,7 @@ public abstract class DatabaseImporter {
|
||||
_errors.add(error);
|
||||
}
|
||||
|
||||
public UUIDMap<DatabaseEntry> getEntries() {
|
||||
public UUIDMap<VaultEntry> getEntries() {
|
||||
return _entries;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.beemdevelopment.aegis.importers;
|
||||
import android.content.Context;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
@@ -73,7 +73,7 @@ public class FreeOtpImporter extends DatabaseImporter {
|
||||
|
||||
for (JSONObject obj : _entries) {
|
||||
try {
|
||||
DatabaseEntry entry = convertEntry(obj);
|
||||
VaultEntry entry = convertEntry(obj);
|
||||
result.addEntry(entry);
|
||||
} catch (DatabaseImporterEntryException e) {
|
||||
result.addError(e);
|
||||
@@ -83,7 +83,7 @@ public class FreeOtpImporter extends DatabaseImporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
try {
|
||||
String type = obj.getString("type").toLowerCase();
|
||||
String algo = obj.getString("algo");
|
||||
@@ -110,7 +110,7 @@ public class FreeOtpImporter extends DatabaseImporter {
|
||||
throw new DatabaseImporterException("unsupported otp type: " + type);
|
||||
}
|
||||
|
||||
return new DatabaseEntry(info, name, issuer);
|
||||
return new VaultEntry(info, name, issuer);
|
||||
} catch (DatabaseImporterException | OtpInfoException | JSONException e) {
|
||||
throw new DatabaseImporterEntryException(e, obj.toString());
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.encoding.Base32;
|
||||
import com.beemdevelopment.aegis.encoding.Base32Exception;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
@@ -92,7 +92,7 @@ public class GoogleAuthImporter extends DatabaseImporter {
|
||||
|
||||
for (Entry sqlEntry : _entries) {
|
||||
try {
|
||||
DatabaseEntry entry = convertEntry(sqlEntry);
|
||||
VaultEntry entry = convertEntry(sqlEntry);
|
||||
result.addEntry(entry);
|
||||
} catch (DatabaseImporterEntryException e) {
|
||||
result.addError(e);
|
||||
@@ -102,7 +102,7 @@ public class GoogleAuthImporter extends DatabaseImporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DatabaseEntry convertEntry(Entry entry) throws DatabaseImporterEntryException {
|
||||
private static VaultEntry convertEntry(Entry entry) throws DatabaseImporterEntryException {
|
||||
try {
|
||||
byte[] secret = Base32.decode(entry.getSecret().toCharArray());
|
||||
|
||||
@@ -124,7 +124,7 @@ public class GoogleAuthImporter extends DatabaseImporter {
|
||||
name = parts[1];
|
||||
}
|
||||
|
||||
return new DatabaseEntry(info, name, entry.getIssuer());
|
||||
return new VaultEntry(info, name, entry.getIssuer());
|
||||
} catch (Base32Exception | OtpInfoException | DatabaseImporterException e) {
|
||||
throw new DatabaseImporterEntryException(e, entry.toString());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.beemdevelopment.aegis.importers;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.encoding.Base64;
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
@@ -66,7 +66,7 @@ public class SteamImporter extends DatabaseImporter {
|
||||
Result result = new Result();
|
||||
|
||||
try {
|
||||
DatabaseEntry entry = convertEntry(_obj);
|
||||
VaultEntry entry = convertEntry(_obj);
|
||||
result.addEntry(entry);
|
||||
} catch (DatabaseImporterEntryException e) {
|
||||
result.addError(e);
|
||||
@@ -75,13 +75,13 @@ public class SteamImporter extends DatabaseImporter {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
|
||||
try {
|
||||
byte[] secret = Base64.decode(obj.getString("shared_secret"));
|
||||
SteamInfo info = new SteamInfo(secret);
|
||||
|
||||
String account = obj.getString("account_name");
|
||||
return new DatabaseEntry(info, account, "Steam");
|
||||
return new VaultEntry(info, account, "Steam");
|
||||
} catch (JSONException | Base64Exception | OtpInfoException e) {
|
||||
throw new DatabaseImporterEntryException(e, obj.toString());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.beemdevelopment.aegis.importers;
|
||||
|
||||
import android.content.Context;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class WinAuthImporter extends DatabaseImporter {
|
||||
public WinAuthImporter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAppPkgName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getAppSubPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WinAuthImporter.State read(FileReader reader) throws DatabaseImporterException {
|
||||
ArrayList<String> lines = new ArrayList<>();
|
||||
|
||||
try {
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(reader.getStream()));
|
||||
String line;
|
||||
|
||||
while ((line = bufferedReader.readLine()) != null) {
|
||||
lines.add(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DatabaseImporterException(e);
|
||||
}
|
||||
|
||||
return new State(lines);
|
||||
}
|
||||
|
||||
public static class State extends DatabaseImporter.State {
|
||||
private ArrayList<String> _lines;
|
||||
|
||||
private State(ArrayList<String> lines) {
|
||||
super(false);
|
||||
_lines = lines;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result convert() {
|
||||
Result result = new Result();
|
||||
|
||||
for (String line : _lines) {
|
||||
try {
|
||||
VaultEntry entry = convertEntry(line);
|
||||
result.addEntry(entry);
|
||||
} catch (DatabaseImporterEntryException e) {
|
||||
result.addError(e);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static VaultEntry convertEntry(String line) throws DatabaseImporterEntryException {
|
||||
try {
|
||||
GoogleAuthInfo info = GoogleAuthInfo.parseUri(line);
|
||||
VaultEntry vaultEntry = new VaultEntry(info);
|
||||
vaultEntry.setIssuer(vaultEntry.getName());
|
||||
vaultEntry.setName("WinAuth");
|
||||
|
||||
return vaultEntry;
|
||||
} catch (GoogleAuthInfoException e) {
|
||||
throw new DatabaseImporterEntryException(e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,31 +9,25 @@ import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
import com.beemdevelopment.aegis.AegisApplication;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
|
||||
public class NotificationService extends Service {
|
||||
public static final int DATABASE_UNLOCKED_ID = 1;
|
||||
public static final int VAULT_UNLOCKED_ID = 1;
|
||||
|
||||
private static final String CODE_LOCK_STATUS_ID = "lock_status_channel";
|
||||
private static final String CODE_LOCK_DATABASE_ACTION = "lock_database";
|
||||
|
||||
public NotificationService() {
|
||||
|
||||
}
|
||||
private static final String CODE_LOCK_VAULT_ACTION = "lock_vault";
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent,int flags, int startId){
|
||||
super.onStartCommand(intent, flags, startId);
|
||||
|
||||
serviceMethod();
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
public void serviceMethod() {
|
||||
Intent intentAction = new Intent(CODE_LOCK_DATABASE_ACTION);
|
||||
PendingIntent lockDatabaseIntent = PendingIntent.getBroadcast(this,1,intentAction, 0);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CODE_LOCK_STATUS_ID)
|
||||
Intent intentAction = new Intent(CODE_LOCK_VAULT_ACTION);
|
||||
PendingIntent lockDatabaseIntent = PendingIntent.getBroadcast(this, 1, intentAction, 0);
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CODE_LOCK_STATUS_ID)
|
||||
.setSmallIcon(R.drawable.ic_fingerprint_black_24dp)
|
||||
.setContentTitle(getString(R.string.app_name_full))
|
||||
.setContentText(getString(R.string.vault_unlocked_state))
|
||||
@@ -42,15 +36,20 @@ public class NotificationService extends Service {
|
||||
.setContentIntent(lockDatabaseIntent);
|
||||
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||
notificationManager.notify(DATABASE_UNLOCKED_ID, builder.build());
|
||||
notificationManager.notify(VAULT_UNLOCKED_ID, builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||
notificationManager.cancel(DATABASE_UNLOCKED_ID);
|
||||
notificationManager.cancel(VAULT_UNLOCKED_ID);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskRemoved(Intent rootIntent) {
|
||||
super.onTaskRemoved(rootIntent);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.core.view.LayoutInflaterCompat;
|
||||
|
||||
import com.beemdevelopment.aegis.BuildConfig;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.Theme;
|
||||
import com.beemdevelopment.aegis.helpers.ThemeHelper;
|
||||
import com.beemdevelopment.aegis.ui.glide.GlideLicense;
|
||||
import com.mikepenz.iconics.context.IconicsLayoutInflater2;
|
||||
|
||||
import de.psdev.licensesdialog.LicenseResolver;
|
||||
import de.psdev.licensesdialog.LicensesDialog;
|
||||
|
||||
public class AboutActivity extends AegisActivity {
|
||||
|
||||
private static String GITHUB = "https://github.com/beemdevelopment/Aegis";
|
||||
private static String WEBSITE_ALEXANDER = "https://alexbakker.me";
|
||||
private static String GITHUB_MICHAEL = "https://github.com/michaelschattgen";
|
||||
|
||||
private static String MAIL_BEEMDEVELOPMENT = "beemdevelopment@gmail.com";
|
||||
private static String WEBSITE_BEEMDEVELOPMENT = "https://beem.dev/";
|
||||
private static String PLAYSTORE_BEEMDEVELOPMENT = "https://play.google.com/store/apps/details?id=com.beemdevelopment.aegis";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new IconicsLayoutInflater2(getDelegate()));
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
View btnLicenses = findViewById(R.id.btn_licenses);
|
||||
btnLicenses.setOnClickListener(v -> showLicenseDialog());
|
||||
|
||||
TextView appVersion = findViewById(R.id.app_version);
|
||||
appVersion.setText(getCurrentAppVersion());
|
||||
|
||||
View btnAppVersion = findViewById(R.id.btn_app_version);
|
||||
btnAppVersion.setOnClickListener(v -> {
|
||||
copyToClipboard(getCurrentAppVersion(), R.string.version_copied);
|
||||
});
|
||||
|
||||
View btnGithub = findViewById(R.id.btn_github);
|
||||
btnGithub.setOnClickListener(v -> openUrl(GITHUB));
|
||||
|
||||
View btnAlexander = findViewById(R.id.btn_alexander);
|
||||
btnAlexander.setOnClickListener(v -> openUrl(WEBSITE_ALEXANDER));
|
||||
|
||||
View btnMichael = findViewById(R.id.btn_michael);
|
||||
btnMichael.setOnClickListener(v -> openUrl(GITHUB_MICHAEL));
|
||||
|
||||
View btnMail = findViewById(R.id.btn_email);
|
||||
btnMail.setOnClickListener(v -> openMail(MAIL_BEEMDEVELOPMENT));
|
||||
|
||||
View btnWebsite = findViewById(R.id.btn_website);
|
||||
btnWebsite.setOnClickListener(v -> openUrl(WEBSITE_BEEMDEVELOPMENT));
|
||||
|
||||
View btnRate = findViewById(R.id.btn_rate);
|
||||
btnRate.setOnClickListener(v -> openUrl(PLAYSTORE_BEEMDEVELOPMENT ));
|
||||
|
||||
View btnChangelog = findViewById(R.id.btn_changelog);
|
||||
btnChangelog.setOnClickListener(v -> {
|
||||
ChangelogDialog.create().setTheme(getCurrentTheme()).show(getSupportFragmentManager(), "CHANGELOG_DIALOG");
|
||||
});
|
||||
}
|
||||
|
||||
private static String getCurrentAppVersion() {
|
||||
if (BuildConfig.DEBUG) {
|
||||
return String.format("%s-%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.GIT_HASH, BuildConfig.GIT_BRANCH);
|
||||
}
|
||||
|
||||
return BuildConfig.VERSION_NAME;
|
||||
}
|
||||
|
||||
private void openUrl(String url) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW);
|
||||
browserIntent.setData(Uri.parse(url));
|
||||
browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
startActivity(browserIntent);
|
||||
}
|
||||
|
||||
private void copyToClipboard(String text, @StringRes int messageId) {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData data = ClipData.newPlainText("text/plain", text);
|
||||
clipboard.setPrimaryClip(data);
|
||||
Toast.makeText(this, messageId, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private void openMail(String mailaddress) {
|
||||
Intent mailIntent = new Intent(Intent.ACTION_SENDTO);
|
||||
mailIntent.setData(Uri.parse("mailto:" + mailaddress));
|
||||
mailIntent.putExtra(Intent.EXTRA_EMAIL, mailaddress);
|
||||
mailIntent.putExtra(Intent.EXTRA_SUBJECT, R.string.app_name_full);
|
||||
|
||||
startActivity(Intent.createChooser(mailIntent, this.getString(R.string.email)));
|
||||
}
|
||||
|
||||
private void showLicenseDialog() {
|
||||
String stylesheet = getString(R.string.custom_notices_format_style);
|
||||
int backgroundColorResource = getCurrentTheme() == Theme.AMOLED ? R.attr.cardBackgroundFocused : R.attr.cardBackground;
|
||||
String backgroundColor = String.format("%06X", (0xFFFFFF & ThemeHelper.getThemeColor(backgroundColorResource, getTheme())));
|
||||
String textColor = String.format("%06X", (0xFFFFFF & ThemeHelper.getThemeColor(R.attr.primaryText, getTheme())));
|
||||
String licenseColor = String.format("%06X", (0xFFFFFF & ThemeHelper.getThemeColor(R.attr.cardBackgroundFocused, getTheme())));
|
||||
|
||||
stylesheet = String.format(stylesheet, backgroundColor, textColor, licenseColor);
|
||||
|
||||
LicenseResolver.registerLicense(new GlideLicense());
|
||||
new LicensesDialog.Builder(this)
|
||||
.setNotices(R.raw.notices)
|
||||
.setTitle(R.string.licenses)
|
||||
.setNoticesCssStyle(stylesheet)
|
||||
.setIncludeOwnLicense(true)
|
||||
.build()
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package com.beemdevelopment.aegis.ui;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.view.WindowManager;
|
||||
@@ -22,6 +21,7 @@ import java.util.Locale;
|
||||
public abstract class AegisActivity extends AppCompatActivity implements AegisApplication.LockListener {
|
||||
private boolean _resumed;
|
||||
private AegisApplication _app;
|
||||
private Theme _currentTheme;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -89,6 +89,8 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||
}
|
||||
|
||||
protected void setPreferredTheme(Theme theme) {
|
||||
_currentTheme = theme;
|
||||
|
||||
switch (theme) {
|
||||
case LIGHT:
|
||||
setTheme(R.style.AppTheme);
|
||||
@@ -108,7 +110,7 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||
Configuration config = new Configuration();
|
||||
config.locale = locale;
|
||||
|
||||
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
|
||||
this.getResources().updateConfiguration(config, this.getResources().getDisplayMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +125,7 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||
* the vault was locked by an external trigger while the Activity was still open.
|
||||
*/
|
||||
private boolean isOrphan() {
|
||||
return !(this instanceof MainActivity) && _app.getDatabaseManager().isLocked();
|
||||
return !(this instanceof MainActivity) && _app.getVaultManager().isLocked();
|
||||
}
|
||||
|
||||
private void setGlobalAnimationDurationScale() {
|
||||
@@ -136,4 +138,8 @@ public abstract class AegisActivity extends AppCompatActivity implements AegisAp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Theme getCurrentTheme() {
|
||||
return _currentTheme;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
@@ -14,44 +13,51 @@ import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
|
||||
import com.beemdevelopment.aegis.CancelAction;
|
||||
import com.beemdevelopment.aegis.Preferences;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
|
||||
import com.beemdevelopment.aegis.helpers.UiThreadExecutor;
|
||||
import com.beemdevelopment.aegis.ui.tasks.SlotListTask;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotListTask.Callback {
|
||||
public class AuthActivity extends AegisActivity implements SlotListTask.Callback {
|
||||
private EditText _textPassword;
|
||||
|
||||
private CancelAction _cancelAction;
|
||||
private SlotList _slots;
|
||||
private FingerprintUiHelper _fingerHelper;
|
||||
private FingerprintManager.CryptoObject _fingerCryptoObj;
|
||||
private BiometricPrompt.CryptoObject _bioCryptoObj;
|
||||
private BiometricPrompt _bioPrompt;
|
||||
|
||||
private Preferences _prefs;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
_prefs = new Preferences(this);
|
||||
setContentView(R.layout.activity_auth);
|
||||
_textPassword = findViewById(R.id.text_password);
|
||||
LinearLayout boxFingerprint = findViewById(R.id.box_fingerprint);
|
||||
LinearLayout boxFingerprintInfo = findViewById(R.id.box_fingerprint_info);
|
||||
TextView textFingerprint = findViewById(R.id.text_fingerprint);
|
||||
LinearLayout boxBiometricInfo = findViewById(R.id.box_biometric_info);
|
||||
Button decryptButton = findViewById(R.id.button_decrypt);
|
||||
Button biometricsButton = findViewById(R.id.button_biometrics);
|
||||
|
||||
_textPassword.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
@@ -60,23 +66,17 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||
return false;
|
||||
});
|
||||
|
||||
SwirlView imgFingerprint = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ViewGroup insertPoint = findViewById(R.id.img_fingerprint_insert);
|
||||
imgFingerprint = new SwirlView(this);
|
||||
insertPoint.addView(imgFingerprint, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
_slots = (SlotList) intent.getSerializableExtra("slots");
|
||||
_cancelAction = (CancelAction) intent.getSerializableExtra("cancelAction");
|
||||
|
||||
// only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found
|
||||
FingerprintManager manager = FingerprintHelper.getManager(this);
|
||||
if (manager != null && _slots.has(FingerprintSlot.class)) {
|
||||
// only show the biometric prompt if the api version is new enough, permission is granted, a scanner is found and a biometric slot is found
|
||||
if (_slots.has(BiometricSlot.class) && BiometricsHelper.isAvailable(this)) {
|
||||
boolean invalidated = false;
|
||||
|
||||
try {
|
||||
// find a fingerprint slot with an id that matches an alias in the keystore
|
||||
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
|
||||
// find a biometric slot with an id that matches an alias in the keystore
|
||||
for (BiometricSlot slot : _slots.findAll(BiometricSlot.class)) {
|
||||
String id = slot.getUUID().toString();
|
||||
KeyStoreHandle handle = new KeyStoreHandle();
|
||||
if (handle.containsKey(id)) {
|
||||
@@ -86,10 +86,11 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||
invalidated = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Cipher cipher = slot.createDecryptCipher(key);
|
||||
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
|
||||
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||
boxFingerprint.setVisibility(View.VISIBLE);
|
||||
_bioCryptoObj = new BiometricPrompt.CryptoObject(cipher);
|
||||
_bioPrompt = new BiometricPrompt(this, new UiThreadExecutor(), new BiometricPromptListener());
|
||||
biometricsButton.setVisibility(View.VISIBLE);
|
||||
invalidated = false;
|
||||
break;
|
||||
}
|
||||
@@ -100,7 +101,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||
|
||||
// display a help message if a matching invalidated keystore entry was found
|
||||
if (invalidated) {
|
||||
boxFingerprintInfo.setVisibility(View.VISIBLE);
|
||||
boxBiometricInfo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,10 +116,9 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||
}
|
||||
});
|
||||
|
||||
if (_fingerHelper == null) {
|
||||
_textPassword.requestFocus();
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
}
|
||||
biometricsButton.setOnClickListener(v -> {
|
||||
showBiometricPrompt();
|
||||
});
|
||||
}
|
||||
|
||||
private void showError() {
|
||||
@@ -138,43 +138,72 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||
private void selectPassword() {
|
||||
_textPassword.selectAll();
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// ignore back button presses
|
||||
switch (_cancelAction) {
|
||||
case KILL:
|
||||
finishAffinity();
|
||||
|
||||
case CLOSE:
|
||||
Intent intent = new Intent();
|
||||
setResult(RESULT_CANCELED, intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (_fingerHelper != null) {
|
||||
_fingerHelper.startListening(_fingerCryptoObj);
|
||||
if (_bioPrompt != null) {
|
||||
if (_prefs.isPasswordReminderNeeded()) {
|
||||
focusPasswordField();
|
||||
showPasswordReminder();
|
||||
} else {
|
||||
showBiometricPrompt();
|
||||
}
|
||||
} else {
|
||||
focusPasswordField();
|
||||
}
|
||||
}
|
||||
|
||||
private void focusPasswordField() {
|
||||
_textPassword.requestFocus();
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
}
|
||||
|
||||
private void showPasswordReminder() {
|
||||
View popupLayout = getLayoutInflater().inflate(R.layout.popup_password, null);
|
||||
PopupWindow popup = new PopupWindow(popupLayout, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
popup.setFocusable(true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
|
||||
popup.setElevation(5.0f);
|
||||
}
|
||||
_textPassword.post(() -> popup.showAsDropDown(_textPassword));
|
||||
_textPassword.postDelayed(popup::dismiss, 5000);
|
||||
}
|
||||
|
||||
public void showBiometricPrompt() {
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.authentication))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
_bioPrompt.authenticate(info, _bioCryptoObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (_fingerHelper != null) {
|
||||
_fingerHelper.stopListening();
|
||||
if (_bioPrompt != null) {
|
||||
_bioPrompt.cancelAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticated() {
|
||||
trySlots(FingerprintSlot.class, _fingerCryptoObj.getCipher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFinished(SlotListTask.Result result) {
|
||||
if (result != null) {
|
||||
@@ -183,9 +212,13 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||
_slots.replace(result.getSlot());
|
||||
}
|
||||
|
||||
if (result.getSlot().getType() == Slot.TYPE_DERIVED) {
|
||||
_prefs.resetPasswordReminderTimestamp();
|
||||
}
|
||||
|
||||
// send the master key back to the main activity
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra("creds", new DatabaseFileCredentials(result.getKey(), _slots));
|
||||
intent.putExtra("creds", new VaultFileCredentials(result.getKey(), _slots));
|
||||
intent.putExtra("repairedSlot", result.isSlotRepaired());
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
@@ -193,4 +226,25 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
||||
showError();
|
||||
}
|
||||
}
|
||||
|
||||
private class BiometricPromptListener extends BiometricPrompt.AuthenticationCallback {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
super.onAuthenticationError(errorCode, errString);
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
Toast.makeText(AuthActivity.this, errString, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
trySlots(BiometricSlot.class, _bioCryptoObj.getCipher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.InflateException;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.Theme;
|
||||
import com.beemdevelopment.aegis.helpers.ThemeHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class ChangelogDialog extends DialogFragment {
|
||||
private Theme _themeStyle;
|
||||
|
||||
public static ChangelogDialog create() {
|
||||
return new ChangelogDialog();
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
final View customView;
|
||||
try {
|
||||
customView = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_web_view, null);
|
||||
} catch (InflateException e) {
|
||||
e.printStackTrace();
|
||||
return new AlertDialog.Builder(getActivity())
|
||||
.setTitle(android.R.string.dialog_alert_title)
|
||||
.setMessage(getString(R.string.webview_error))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
AlertDialog dialog = new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Changelog")
|
||||
.setView(customView)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
|
||||
final WebView webView = customView.findViewById(R.id.web_view);
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
try (InputStream html = getActivity().getAssets().open("changelog.html")) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(html, "UTF-8"));
|
||||
String str;
|
||||
while ((str = in.readLine()) != null)
|
||||
buf.append(str);
|
||||
|
||||
in.close();
|
||||
String changelog = buf.toString();
|
||||
changelog = replaceStylesheet(changelog);
|
||||
webView.loadData(changelog, "text/html", "UTF-8");
|
||||
} catch (IOException e) {
|
||||
webView.loadData("<h1>Unable to load</h1><p>" + e.getLocalizedMessage() + "</p>", "text/html", "UTF-8");
|
||||
}
|
||||
return dialog;
|
||||
}
|
||||
|
||||
private String replaceStylesheet(String changelog) {
|
||||
int backgroundColorResource = _themeStyle == Theme.AMOLED ? R.attr.cardBackgroundFocused : R.attr.cardBackground;
|
||||
String backgroundColor = colorToCSS(ThemeHelper.getThemeColor(backgroundColorResource, getContext().getTheme()));
|
||||
String textColor = colorToCSS(0xFFFFFF & ThemeHelper.getThemeColor(R.attr.primaryText, getContext().getTheme()));
|
||||
|
||||
return String.format(changelog, backgroundColor, textColor);
|
||||
}
|
||||
|
||||
private static String colorToCSS(int color) {
|
||||
return String.format("rgb(%d, %d, %d)", Color.red(color), Color.green(color), Color.blue(color));
|
||||
}
|
||||
|
||||
public ChangelogDialog setTheme(Theme theme) {
|
||||
_themeStyle = theme;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,39 +4,33 @@ import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.beemdevelopment.aegis.Preferences;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
|
||||
import com.beemdevelopment.aegis.ui.tasks.DerivationTask;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class Dialogs {
|
||||
private Dialogs() {
|
||||
@@ -63,6 +57,24 @@ public class Dialogs {
|
||||
.create());
|
||||
}
|
||||
|
||||
public static void showDeleteEntriesDialog(Activity activity, DialogInterface.OnClickListener onDelete, int totalEntries) {
|
||||
String title, message;
|
||||
if (totalEntries > 1) {
|
||||
title = activity.getString(R.string.delete_entries);
|
||||
message = String.format(activity.getString(R.string.delete_entries_description), totalEntries);
|
||||
} else {
|
||||
title = activity.getString(R.string.delete_entry);
|
||||
message = activity.getString(R.string.delete_entry_description);
|
||||
}
|
||||
|
||||
showSecureDialog(new AlertDialog.Builder(activity)
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton(android.R.string.yes, onDelete)
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.create());
|
||||
}
|
||||
|
||||
public static void showDiscardDialog(Activity activity, DialogInterface.OnClickListener onSave, DialogInterface.OnClickListener onDiscard) {
|
||||
showSecureDialog(new AlertDialog.Builder(activity)
|
||||
.setTitle(activity.getString(R.string.discard_changes))
|
||||
@@ -76,6 +88,18 @@ public class Dialogs {
|
||||
View view = activity.getLayoutInflater().inflate(R.layout.dialog_password, null);
|
||||
EditText textPassword = view.findViewById(R.id.text_password);
|
||||
EditText textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
|
||||
CheckBox switchToggleVisibility = view.findViewById(R.id.check_toggle_visibility);
|
||||
|
||||
switchToggleVisibility.setOnCheckedChangeListener((CompoundButton.OnCheckedChangeListener) (buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
textPassword.setTransformationMethod(null);
|
||||
textPassword.clearFocus();
|
||||
textPasswordConfirm.setEnabled(false);
|
||||
} else {
|
||||
textPassword.setTransformationMethod(new PasswordTransformationMethod());
|
||||
textPasswordConfirm.setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.set_password)
|
||||
@@ -92,7 +116,7 @@ public class Dialogs {
|
||||
|
||||
// replace the default listener
|
||||
button.setOnClickListener(v -> {
|
||||
if (!EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm)) {
|
||||
if (!EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm) && !switchToggleVisibility.isChecked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,7 +141,7 @@ public class Dialogs {
|
||||
TextWatcher watcher = new TextWatcher() {
|
||||
public void onTextChanged(CharSequence c, int start, int before, int count) {
|
||||
boolean equal = EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm);
|
||||
buttonOK.get().setEnabled(equal);
|
||||
buttonOK.get().setEnabled(equal || switchToggleVisibility.isChecked());
|
||||
}
|
||||
|
||||
public void beforeTextChanged(CharSequence c, int start, int count, int after) {
|
||||
@@ -178,51 +202,6 @@ public class Dialogs {
|
||||
showSecureDialog(dialog);
|
||||
}
|
||||
|
||||
public static void showFingerprintDialog(Activity activity, Dialogs.SlotListener listener) {
|
||||
View view = activity.getLayoutInflater().inflate(R.layout.dialog_fingerprint, null);
|
||||
TextView textFingerprint = view.findViewById(R.id.text_fingerprint);
|
||||
SwirlView imgFingerprint = view.findViewById(R.id.img_fingerprint);
|
||||
|
||||
FingerprintManager.CryptoObject obj;
|
||||
FingerprintSlot slot;
|
||||
final AtomicReference<FingerprintUiHelper> helper = new AtomicReference<>();
|
||||
FingerprintManager manager = FingerprintHelper.getManager(activity);
|
||||
|
||||
try {
|
||||
slot = new FingerprintSlot();
|
||||
SecretKey key = new KeyStoreHandle().generateKey(slot.getUUID().toString());
|
||||
Cipher cipher = Slot.createEncryptCipher(key);
|
||||
obj = new FingerprintManager.CryptoObject(cipher);
|
||||
} catch (KeyStoreHandleException | SlotException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.register_fingerprint)
|
||||
.setView(view)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setOnDismissListener(d -> {
|
||||
helper.get().stopListening();
|
||||
})
|
||||
.create();
|
||||
|
||||
helper.set(new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, new FingerprintUiHelper.Callback() {
|
||||
@Override
|
||||
public void onAuthenticated() {
|
||||
listener.onSlotResult(slot, obj.getCipher());
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
helper.get().startListening(obj);
|
||||
showSecureDialog(dialog);
|
||||
}
|
||||
|
||||
public interface NumberInputListener {
|
||||
void onNumberInputResult(int number);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.widget.TableRow;
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.avito.android.krop.KropView;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.encoding.Base32;
|
||||
import com.beemdevelopment.aegis.encoding.Base32Exception;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
@@ -60,7 +60,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||
private static final int PICK_IMAGE_REQUEST = 0;
|
||||
|
||||
private boolean _isNew = false;
|
||||
private DatabaseEntry _origEntry;
|
||||
private VaultEntry _origEntry;
|
||||
private TreeSet<String> _groups;
|
||||
private boolean _hasCustomIcon = false;
|
||||
// keep track of icon changes separately as the generated jpeg's are not deterministic
|
||||
@@ -99,7 +99,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||
|
||||
// retrieve info from the calling activity
|
||||
Intent intent = getIntent();
|
||||
_origEntry = (DatabaseEntry) intent.getSerializableExtra("entry");
|
||||
_origEntry = (VaultEntry) intent.getSerializableExtra("entry");
|
||||
_isNew = intent.getBooleanExtra("isNew", false);
|
||||
_groups = new TreeSet<>(Collator.getInstance());
|
||||
_groups.addAll(intent.getStringArrayListExtra("groups"));
|
||||
@@ -133,51 +133,49 @@ public class EditEntryActivity extends AegisActivity {
|
||||
_advancedSettings = findViewById(R.id.expandableLayout);
|
||||
|
||||
// fill the fields with values if possible
|
||||
if (_origEntry != null) {
|
||||
if (_origEntry.hasIcon()) {
|
||||
Glide.with(this)
|
||||
.asDrawable()
|
||||
.load(_origEntry)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.into(_iconView);
|
||||
_hasCustomIcon = true;
|
||||
} else {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
|
||||
_iconView.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
_textName.setText(_origEntry.getName());
|
||||
_textIssuer.setText(_origEntry.getIssuer());
|
||||
|
||||
OtpInfo info = _origEntry.getInfo();
|
||||
if (info instanceof TotpInfo) {
|
||||
_textPeriod.setText(Integer.toString(((TotpInfo) info).getPeriod()));
|
||||
_rowPeriod.setVisibility(View.VISIBLE);
|
||||
} else if (info instanceof HotpInfo) {
|
||||
_textCounter.setText(Long.toString(((HotpInfo) info).getCounter()));
|
||||
_rowCounter.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
_textDigits.setText(Integer.toString(info.getDigits()));
|
||||
|
||||
byte[] secretBytes = _origEntry.getInfo().getSecret();
|
||||
if (secretBytes != null) {
|
||||
char[] secretChars = Base32.encode(secretBytes);
|
||||
_textSecret.setText(secretChars, 0, secretChars.length);
|
||||
}
|
||||
|
||||
String type = _origEntry.getInfo().getType();
|
||||
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type), false);
|
||||
|
||||
String algo = _origEntry.getInfo().getAlgorithm(false);
|
||||
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
|
||||
|
||||
String group = _origEntry.getGroup();
|
||||
setGroup(group);
|
||||
if (_origEntry.hasIcon()) {
|
||||
Glide.with(this)
|
||||
.asDrawable()
|
||||
.load(_origEntry)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(false)
|
||||
.into(_iconView);
|
||||
_hasCustomIcon = true;
|
||||
} else {
|
||||
TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView);
|
||||
_iconView.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
_textName.setText(_origEntry.getName());
|
||||
_textIssuer.setText(_origEntry.getIssuer());
|
||||
|
||||
OtpInfo info = _origEntry.getInfo();
|
||||
if (info instanceof TotpInfo) {
|
||||
_textPeriod.setText(Integer.toString(((TotpInfo) info).getPeriod()));
|
||||
_rowPeriod.setVisibility(View.VISIBLE);
|
||||
} else if (info instanceof HotpInfo) {
|
||||
_textCounter.setText(Long.toString(((HotpInfo) info).getCounter()));
|
||||
_rowCounter.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
_textDigits.setText(Integer.toString(info.getDigits()));
|
||||
|
||||
byte[] secretBytes = _origEntry.getInfo().getSecret();
|
||||
if (secretBytes != null) {
|
||||
char[] secretChars = Base32.encode(secretBytes);
|
||||
_textSecret.setText(secretChars, 0, secretChars.length);
|
||||
}
|
||||
|
||||
String type = _origEntry.getInfo().getType();
|
||||
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type), false);
|
||||
|
||||
String algo = _origEntry.getInfo().getAlgorithm(false);
|
||||
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
|
||||
|
||||
String group = _origEntry.getGroup();
|
||||
setGroup(group);
|
||||
|
||||
// update the icon if the text changed
|
||||
_textIssuer.addTextChangedListener(_iconChangeListener);
|
||||
_textName.addTextChangedListener(_iconChangeListener);
|
||||
@@ -322,7 +320,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
AtomicReference<String> msg = new AtomicReference<>();
|
||||
AtomicReference<DatabaseEntry> entry = new AtomicReference<>();
|
||||
AtomicReference<VaultEntry> entry = new AtomicReference<>();
|
||||
|
||||
try {
|
||||
entry.set(parseEntry());
|
||||
@@ -331,7 +329,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||
}
|
||||
|
||||
// close the activity if the entry has not been changed
|
||||
if (_origEntry != null && !_hasChangedIcon && _origEntry.equals(entry.get())) {
|
||||
if (!_hasChangedIcon && _origEntry.equals(entry.get())) {
|
||||
super.onBackPressed();
|
||||
return;
|
||||
}
|
||||
@@ -390,7 +388,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void finish(DatabaseEntry entry, boolean delete) {
|
||||
private void finish(VaultEntry entry, boolean delete) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra("entry", entry);
|
||||
intent.putExtra("delete", delete);
|
||||
@@ -443,7 +441,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseEntry parseEntry() throws ParseException {
|
||||
private VaultEntry parseEntry() throws ParseException {
|
||||
if (_textSecret.length() == 0) {
|
||||
throw new ParseException("Secret is a required field.");
|
||||
}
|
||||
@@ -465,7 +463,6 @@ public class EditEntryActivity extends AegisActivity {
|
||||
throw new ParseException("Secret is not valid base32.");
|
||||
}
|
||||
|
||||
// set otp info
|
||||
OtpInfo info;
|
||||
try {
|
||||
switch (type.toLowerCase()) {
|
||||
@@ -494,14 +491,8 @@ public class EditEntryActivity extends AegisActivity {
|
||||
throw new ParseException("The entered info is incorrect: " + e.getMessage());
|
||||
}
|
||||
|
||||
// set database entry info
|
||||
DatabaseEntry entry;
|
||||
if (_origEntry == null) {
|
||||
entry = new DatabaseEntry(info);
|
||||
} else {
|
||||
entry = Cloner.clone(_origEntry);
|
||||
entry.setInfo(info);
|
||||
}
|
||||
VaultEntry entry = Cloner.clone(_origEntry);
|
||||
entry.setInfo(info);
|
||||
entry.setIssuer(_textIssuer.getText().toString());
|
||||
entry.setName(_textName.getText().toString());
|
||||
|
||||
@@ -537,7 +528,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||
}
|
||||
|
||||
private boolean onSave() {
|
||||
DatabaseEntry entry;
|
||||
VaultEntry entry;
|
||||
try {
|
||||
entry = parseEntry();
|
||||
} catch (ParseException e) {
|
||||
|
||||
@@ -4,19 +4,20 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.beemdevelopment.aegis.Preferences;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.Database;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFile;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManager;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManagerException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
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.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticatedSlide;
|
||||
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticationSlide;
|
||||
import com.beemdevelopment.aegis.ui.tasks.DerivationTask;
|
||||
@@ -29,8 +30,6 @@ import org.json.JSONObject;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
public class IntroActivity extends AppIntro2 implements DerivationTask.Callback {
|
||||
public static final int RESULT_OK = 0;
|
||||
public static final int RESULT_EXCEPTION = 1;
|
||||
@@ -39,8 +38,8 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||
private CustomAuthenticationSlide _authenticationSlide;
|
||||
private Fragment _endSlide;
|
||||
|
||||
private Database _database;
|
||||
private DatabaseFile _databaseFile;
|
||||
private Vault _vault;
|
||||
private VaultFile _vaultFile;
|
||||
private PasswordSlot _passwordSlot;
|
||||
private Cipher _passwordCipher;
|
||||
|
||||
@@ -65,7 +64,7 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||
|
||||
SliderPage homeSliderPage = new SliderPage();
|
||||
homeSliderPage.setTitle(getString(R.string.welcome));
|
||||
homeSliderPage.setImageDrawable(R.drawable.icon_foreground_white);
|
||||
homeSliderPage.setImageDrawable(R.drawable.app_icon);
|
||||
homeSliderPage.setTitleColor(getResources().getColor(R.color.primary_text_dark));
|
||||
homeSliderPage.setDescription(getString(R.string.app_description));
|
||||
homeSliderPage.setDescColor(getResources().getColor(R.color.primary_text_dark));
|
||||
@@ -83,14 +82,13 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||
SliderPage endSliderPage = new SliderPage();
|
||||
endSliderPage.setTitle(getString(R.string.setup_completed));
|
||||
endSliderPage.setDescription(getString(R.string.setup_completed_description));
|
||||
endSliderPage.setImageDrawable(R.drawable.icon_foreground_white);
|
||||
endSliderPage.setImageDrawable(R.drawable.app_icon);
|
||||
endSliderPage.setBgColor(getResources().getColor(R.color.colorSecondary));
|
||||
_endSlide = AppIntroFragment.newInstance(endSliderPage);
|
||||
addSlide(_endSlide);
|
||||
|
||||
// create the database and database file
|
||||
_database = new Database();
|
||||
_databaseFile = new DatabaseFile();
|
||||
_vault = new Vault();
|
||||
_vaultFile = new VaultFile();
|
||||
}
|
||||
|
||||
private void setException(Exception e) {
|
||||
@@ -130,12 +128,11 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||
}
|
||||
|
||||
// generate the master key
|
||||
DatabaseFileCredentials creds = null;
|
||||
VaultFileCredentials creds = null;
|
||||
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||
creds = new DatabaseFileCredentials();
|
||||
creds = new VaultFileCredentials();
|
||||
}
|
||||
|
||||
SlotList slots = null;
|
||||
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||
// encrypt the master key with a key derived from the user's password
|
||||
// and add it to the list of slots
|
||||
@@ -150,30 +147,26 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
||||
}
|
||||
}
|
||||
|
||||
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_FINGER) {
|
||||
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC) {
|
||||
BiometricSlot slot = _authenticatedSlide.getBiometricSlot();
|
||||
try {
|
||||
// encrypt the master key with the fingerprint key
|
||||
// and add it to the list of slots
|
||||
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
||||
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
||||
slot.setKey(creds.getKey(), cipher);
|
||||
creds.getSlots().add(slot);
|
||||
slot.setKey(creds.getKey(), _authenticatedSlide.getBiometriCipher());
|
||||
} catch (SlotException e) {
|
||||
setException(e);
|
||||
return;
|
||||
}
|
||||
creds.getSlots().add(slot);
|
||||
}
|
||||
|
||||
// finally, save the database
|
||||
// finally, save the vault
|
||||
try {
|
||||
JSONObject obj = _database.toJson();
|
||||
JSONObject obj = _vault.toJson();
|
||||
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||
_databaseFile.setContent(obj);
|
||||
_vaultFile.setContent(obj);
|
||||
} else {
|
||||
_databaseFile.setContent(obj, creds);
|
||||
_vaultFile.setContent(obj, creds);
|
||||
}
|
||||
DatabaseManager.save(getApplicationContext(), _databaseFile);
|
||||
} catch (DatabaseManagerException | DatabaseFileException e) {
|
||||
VaultManager.save(getApplicationContext(), _vaultFile);
|
||||
} catch (VaultManagerException | VaultFileException e) {
|
||||
setException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
@@ -16,29 +15,26 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SubMenu;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.core.view.MenuItemCompat;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
|
||||
import com.beemdevelopment.aegis.AegisApplication;
|
||||
import com.beemdevelopment.aegis.CancelAction;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.SortCategory;
|
||||
import com.beemdevelopment.aegis.ViewMode;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManager;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManagerException;
|
||||
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
||||
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||
import com.beemdevelopment.aegis.ui.views.EntryListView;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.VaultManager;
|
||||
import com.beemdevelopment.aegis.vault.VaultManagerException;
|
||||
import com.getbase.floatingactionbutton.FloatingActionsMenu;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.ChecksumException;
|
||||
import com.google.zxing.FormatException;
|
||||
@@ -72,12 +68,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
private static final int CODE_PERM_READ_STORAGE = 1;
|
||||
|
||||
private AegisApplication _app;
|
||||
private DatabaseManager _db;
|
||||
private VaultManager _vault;
|
||||
private boolean _loaded;
|
||||
private String _selectedGroup;
|
||||
private boolean _searchSubmitted;
|
||||
|
||||
private DatabaseEntry _selectedEntry;
|
||||
private List<VaultEntry> _selectedEntries;
|
||||
private ActionMode _actionMode;
|
||||
|
||||
private Menu _menu;
|
||||
@@ -92,8 +88,9 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
_app = (AegisApplication) getApplication();
|
||||
_db = _app.getDatabaseManager();
|
||||
_vault = _app.getVaultManager();
|
||||
_loaded = false;
|
||||
|
||||
// set up the main view
|
||||
@@ -103,6 +100,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
_entryListView = (EntryListView) getSupportFragmentManager().findFragmentById(R.id.key_profiles);
|
||||
_entryListView.setListener(this);
|
||||
_entryListView.setShowAccountName(getPreferences().isAccountNameVisible());
|
||||
_entryListView.setSearchAccountName(getPreferences().isSearchAccountNameEnabled());
|
||||
_entryListView.setHighlightEntry(getPreferences().isEntryHighlightEnabled());
|
||||
_entryListView.setTapToReveal(getPreferences().isTapToRevealEnabled());
|
||||
_entryListView.setTapToRevealTime(getPreferences().getTapToRevealTime());
|
||||
_entryListView.setSortCategory(getPreferences().getCurrentSortCategory(), false);
|
||||
@@ -124,6 +123,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
});
|
||||
|
||||
_fabScrollHelper = new FabScrollHelper(_fabMenu);
|
||||
_selectedEntries = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,7 +164,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
}
|
||||
|
||||
// don't process any activity results if the vault is locked
|
||||
if (requestCode != CODE_DECRYPT && requestCode != CODE_DO_INTRO && _db.isLocked()) {
|
||||
if (requestCode != CODE_DECRYPT && requestCode != CODE_DO_INTRO && _vault.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -193,6 +193,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
case CODE_SCAN_IMAGE:
|
||||
onScanImageResult(resultCode, data);
|
||||
}
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -218,10 +220,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
recreate();
|
||||
} else if (data.getBooleanExtra("needsRefresh", false)) {
|
||||
boolean showAccountName = getPreferences().isAccountNameVisible();
|
||||
boolean searchAccountName = getPreferences().isSearchAccountNameEnabled();
|
||||
boolean highlightEntry = getPreferences().isEntryHighlightEnabled();
|
||||
boolean tapToReveal = getPreferences().isTapToRevealEnabled();
|
||||
int tapToRevealTime = getPreferences().getTapToRevealTime();
|
||||
ViewMode viewMode = getPreferences().getCurrentViewMode();
|
||||
_entryListView.setShowAccountName(showAccountName);
|
||||
_entryListView.setSearchAccountName(searchAccountName);
|
||||
_entryListView.setHighlightEntry(highlightEntry);
|
||||
_entryListView.setTapToReveal(tapToReveal);
|
||||
_entryListView.setTapToRevealTime(tapToRevealTime);
|
||||
_entryListView.setViewMode(viewMode);
|
||||
@@ -229,52 +235,50 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
}
|
||||
}
|
||||
|
||||
private void startEditProfileActivity(int requestCode, DatabaseEntry entry, boolean isNew) {
|
||||
private void startEditProfileActivity(int requestCode, VaultEntry entry, boolean isNew) {
|
||||
Intent intent = new Intent(this, EditEntryActivity.class);
|
||||
if (entry != null) {
|
||||
intent.putExtra("entry", entry);
|
||||
}
|
||||
intent.putExtra("entry", entry != null ? entry : VaultEntry.getDefault());
|
||||
intent.putExtra("isNew", isNew);
|
||||
intent.putExtra("selectedGroup", _selectedGroup);
|
||||
intent.putExtra("groups", new ArrayList<>(_db.getGroups()));
|
||||
intent.putExtra("groups", new ArrayList<>(_vault.getGroups()));
|
||||
startActivityForResult(intent, requestCode);
|
||||
}
|
||||
|
||||
private void onScanResult(int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
DatabaseEntry entry = (DatabaseEntry) data.getSerializableExtra("entry");
|
||||
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
|
||||
startEditProfileActivity(CODE_ADD_ENTRY, entry, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void onAddEntryResult(int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
DatabaseEntry entry = (DatabaseEntry) data.getSerializableExtra("entry");
|
||||
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
|
||||
addEntry(entry);
|
||||
saveDatabase();
|
||||
saveVault();
|
||||
}
|
||||
}
|
||||
|
||||
private void onEditEntryResult(int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
DatabaseEntry entry = (DatabaseEntry) data.getSerializableExtra("entry");
|
||||
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
|
||||
if (data.getBooleanExtra("delete", false)) {
|
||||
deleteEntry(entry);
|
||||
} else {
|
||||
// this profile has been serialized/deserialized and is no longer the same instance it once was
|
||||
// to deal with this, the replaceEntry functions are used
|
||||
DatabaseEntry oldEntry = _db.replaceEntry(entry);
|
||||
VaultEntry oldEntry = _vault.replaceEntry(entry);
|
||||
_entryListView.replaceEntry(oldEntry, entry);
|
||||
saveDatabase();
|
||||
saveVault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onEnterEntryResult(int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
DatabaseEntry entry = (DatabaseEntry) data.getSerializableExtra("entry");
|
||||
VaultEntry entry = (VaultEntry) data.getSerializableExtra("entry");
|
||||
addEntry(entry);
|
||||
saveDatabase();
|
||||
saveVault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +304,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
Result result = reader.decode(binaryBitmap);
|
||||
|
||||
GoogleAuthInfo info = GoogleAuthInfo.parseUri(result.getText());
|
||||
DatabaseEntry entry = new DatabaseEntry(info);
|
||||
VaultEntry entry = new VaultEntry(info);
|
||||
|
||||
startEditProfileActivity(CODE_ADD_ENTRY, entry, true);
|
||||
} catch (NotFoundException | IOException | ChecksumException | FormatException | GoogleAuthInfoException e) {
|
||||
@@ -321,7 +325,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
}
|
||||
|
||||
// if the group no longer exists, switch back to 'All'
|
||||
TreeSet<String> groups = _db.getGroups();
|
||||
TreeSet<String> groups = _vault.getGroups();
|
||||
if (_selectedGroup != null && !groups.contains(_selectedGroup)) {
|
||||
menu.findItem(R.id.menu_filter_all).setChecked(true);
|
||||
setGroupFilter(null);
|
||||
@@ -334,6 +338,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
}
|
||||
}
|
||||
|
||||
if (groups.size() > 0) {
|
||||
menu.add(R.id.action_filter_group, Menu.NONE, 10, R.string.filter_ungrouped);
|
||||
}
|
||||
|
||||
menu.setGroupCheckable(R.id.action_filter_group, true, true);
|
||||
}
|
||||
|
||||
@@ -348,8 +356,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
_entryListView.setGroupFilter(group, true);
|
||||
}
|
||||
|
||||
private void addEntry(DatabaseEntry entry) {
|
||||
_db.addEntry(entry);
|
||||
private void addEntry(VaultEntry entry) {
|
||||
_vault.addEntry(entry);
|
||||
_entryListView.addEntry(entry);
|
||||
}
|
||||
|
||||
@@ -360,18 +368,18 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||
unlockDatabase(creds);
|
||||
VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds");
|
||||
unlockVault(creds);
|
||||
}
|
||||
|
||||
private void onDecryptResult(int resultCode, Intent intent) {
|
||||
DatabaseFileCredentials creds = (DatabaseFileCredentials) intent.getSerializableExtra("creds");
|
||||
boolean unlocked = unlockDatabase(creds);
|
||||
VaultFileCredentials creds = (VaultFileCredentials) intent.getSerializableExtra("creds");
|
||||
boolean unlocked = unlockVault(creds);
|
||||
|
||||
// save the database in case a slot was repaired
|
||||
// save the vault in case a slot was repaired
|
||||
if (unlocked && intent.getBooleanExtra("repairedSlot", false)) {
|
||||
_db.setCredentials(creds);
|
||||
saveDatabase();
|
||||
_vault.setCredentials(creds);
|
||||
saveVault();
|
||||
}
|
||||
|
||||
doShortcutActions();
|
||||
@@ -387,10 +395,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
}
|
||||
|
||||
private void startScanImageActivity() {
|
||||
if (!PermissionHelper.request(this, CODE_PERM_READ_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
|
||||
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
|
||||
|
||||
@@ -401,7 +405,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
private void doShortcutActions() {
|
||||
Intent intent = getIntent();
|
||||
String action = intent.getStringExtra("action");
|
||||
if (action == null || _db.isLocked()) {
|
||||
if (action == null || _vault.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -415,10 +419,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
}
|
||||
|
||||
private void handleDeeplink() {
|
||||
if (_db.isLocked()) {
|
||||
if (_vault.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
Uri uri = intent.getData();
|
||||
@@ -434,7 +438,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
}
|
||||
|
||||
if (info != null) {
|
||||
DatabaseEntry entry = new DatabaseEntry(info);
|
||||
VaultEntry entry = new VaultEntry(info);
|
||||
startEditProfileActivity(CODE_ADD_ENTRY, entry, true);
|
||||
}
|
||||
}
|
||||
@@ -445,10 +449,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (_db.isLocked()) {
|
||||
// start the intro if the database file doesn't exist
|
||||
if (!_db.isLoaded() && !_db.fileExists()) {
|
||||
// the db doesn't exist, start the intro
|
||||
if (_vault.isLocked()) {
|
||||
// start the intro if the vault file doesn't exist
|
||||
if (!_vault.isLoaded() && !_vault.fileExists()) {
|
||||
// the vault doesn't exist, start the intro
|
||||
if (getPreferences().isIntroDone()) {
|
||||
Toast.makeText(this, getString(R.string.vault_not_found), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
@@ -456,7 +460,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
startActivityForResult(intro, CODE_DO_INTRO);
|
||||
return;
|
||||
} else {
|
||||
unlockDatabase(null);
|
||||
unlockVault(null);
|
||||
}
|
||||
} else if (_loaded) {
|
||||
// update the list of groups in the filter menu
|
||||
@@ -477,7 +481,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!_searchView.isIconified() || _searchSubmitted ) {
|
||||
if (!_searchView.isIconified() || _searchSubmitted) {
|
||||
_searchSubmitted = false;
|
||||
_entryListView.setSearchFilter(null);
|
||||
|
||||
@@ -495,9 +499,18 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
private void deleteEntry(DatabaseEntry entry) {
|
||||
DatabaseEntry oldEntry = _db.removeEntry(entry);
|
||||
saveDatabase();
|
||||
private void deleteEntries(List<VaultEntry> entries) {
|
||||
for (VaultEntry entry: entries) {
|
||||
VaultEntry oldEntry = _vault.removeEntry(entry);
|
||||
_entryListView.removeEntry(oldEntry);
|
||||
}
|
||||
|
||||
saveVault();
|
||||
}
|
||||
|
||||
private void deleteEntry(VaultEntry entry) {
|
||||
VaultEntry oldEntry = _vault.removeEntry(entry);
|
||||
saveVault();
|
||||
|
||||
_entryListView.removeEntry(oldEntry);
|
||||
}
|
||||
@@ -548,10 +561,16 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_settings:
|
||||
case R.id.action_settings: {
|
||||
Intent intent = new Intent(this, PreferencesActivity.class);
|
||||
startActivityForResult(intent, CODE_PREFERENCES);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_about: {
|
||||
Intent intent = new Intent(this, AboutActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_lock:
|
||||
_app.lock();
|
||||
return true;
|
||||
@@ -601,20 +620,20 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
_searchView.setIconified(true);
|
||||
}
|
||||
|
||||
private boolean unlockDatabase(DatabaseFileCredentials creds) {
|
||||
private boolean unlockVault(VaultFileCredentials creds) {
|
||||
try {
|
||||
if (!_db.isLoaded()) {
|
||||
_db.load();
|
||||
if (!_vault.isLoaded()) {
|
||||
_vault.load();
|
||||
}
|
||||
if (_db.isLocked()) {
|
||||
if (_vault.isLocked()) {
|
||||
if (creds == null) {
|
||||
startAuthActivity();
|
||||
return false;
|
||||
} else {
|
||||
_db.unlock(creds);
|
||||
_vault.unlock(creds);
|
||||
}
|
||||
}
|
||||
} catch (DatabaseManagerException e) {
|
||||
} catch (VaultManagerException e) {
|
||||
Toast.makeText(this, getString(R.string.decryption_error), Toast.LENGTH_LONG).show();
|
||||
startAuthActivity();
|
||||
return false;
|
||||
@@ -626,7 +645,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
|
||||
private void loadEntries() {
|
||||
// load all entries
|
||||
List<DatabaseEntry> entries = new ArrayList<DatabaseEntry>(_db.getEntries());
|
||||
List<VaultEntry> entries = new ArrayList<VaultEntry>(_vault.getEntries());
|
||||
_entryListView.addEntries(entries);
|
||||
_entryListView.runEntriesAnimation();
|
||||
_loaded = true;
|
||||
@@ -634,67 +653,83 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
|
||||
private void startAuthActivity() {
|
||||
Intent intent = new Intent(this, AuthActivity.class);
|
||||
intent.putExtra("slots", _db.getFileHeader().getSlots());
|
||||
intent.putExtra("slots", _vault.getFileHeader().getSlots());
|
||||
intent.putExtra("requiresUnlock", false);
|
||||
intent.putExtra("cancelAction", CancelAction.KILL);
|
||||
startActivityForResult(intent, CODE_DECRYPT);
|
||||
}
|
||||
|
||||
private void saveDatabase() {
|
||||
private void saveVault() {
|
||||
try {
|
||||
_db.save();
|
||||
} catch (DatabaseManagerException e) {
|
||||
_vault.save();
|
||||
} catch (VaultManagerException e) {
|
||||
Toast.makeText(this, getString(R.string.saving_error), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLockIcon() {
|
||||
// hide the lock icon if the database is not unlocked
|
||||
if (_menu != null && !_db.isLocked()) {
|
||||
// hide the lock icon if the vault is not unlocked
|
||||
if (_menu != null && !_vault.isLocked()) {
|
||||
MenuItem item = _menu.findItem(R.id.action_lock);
|
||||
item.setVisible(_db.isEncryptionEnabled());
|
||||
item.setVisible(_vault.isEncryptionEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryClick(DatabaseEntry entry) {
|
||||
if (_selectedEntry != null) {
|
||||
if (_selectedEntry == entry) {
|
||||
public void onEntryClick(VaultEntry entry) {
|
||||
if (_actionMode != null) {
|
||||
if (_selectedEntries.isEmpty()) {
|
||||
_actionMode.finish();
|
||||
} else {
|
||||
setIsMultipleSelected(_selectedEntries.size() > 1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("text/plain", entry.getInfo().getOtp());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(this, getString(R.string.code_copied), Toast.LENGTH_SHORT).show();
|
||||
copyEntryCode(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongEntryClick(DatabaseEntry entry) {
|
||||
if (_selectedEntry != null) {
|
||||
public void onSelect(VaultEntry entry) {
|
||||
_selectedEntries.add(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeselect(VaultEntry entry) {
|
||||
_selectedEntries.remove(entry);
|
||||
}
|
||||
|
||||
private void setIsMultipleSelected(boolean multipleSelected) {
|
||||
_entryListView.setIsLongPressDragEnabled(!multipleSelected);
|
||||
_actionMode.getMenu().findItem(R.id.action_edit).setVisible(!multipleSelected);
|
||||
_actionMode.getMenu().findItem(R.id.action_copy).setVisible(!multipleSelected);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongEntryClick(VaultEntry entry) {
|
||||
if (!_selectedEntries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedEntry = entry;
|
||||
_selectedEntries.add(entry);
|
||||
_entryListView.setActionModeState(true, entry);
|
||||
_actionMode = this.startSupportActionMode(_actionModeCallbacks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2) {
|
||||
_db.swapEntries(entry1, entry2);
|
||||
public void onEntryMove(VaultEntry entry1, VaultEntry entry2) {
|
||||
_vault.swapEntries(entry1, entry2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryDrop(DatabaseEntry entry) {
|
||||
saveDatabase();
|
||||
public void onEntryDrop(VaultEntry entry) {
|
||||
saveVault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryChange(DatabaseEntry entry) {
|
||||
saveDatabase();
|
||||
public void onEntryChange(VaultEntry entry) {
|
||||
saveVault();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -718,6 +753,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
super.onLocked();
|
||||
}
|
||||
|
||||
private void copyEntryCode(VaultEntry entry) {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText("text/plain", entry.getInfo().getOtp());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(this, getString(R.string.code_copied), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private class ActionModeCallbacks implements ActionMode.Callback {
|
||||
@Override
|
||||
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
|
||||
@@ -734,22 +776,30 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
@Override
|
||||
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_copy:
|
||||
copyEntryCode(_selectedEntries.get(0));
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_edit:
|
||||
startEditProfileActivity(CODE_EDIT_ENTRY, _selectedEntry, false);
|
||||
startEditProfileActivity(CODE_EDIT_ENTRY, _selectedEntries.get(0), false);
|
||||
mode.finish();
|
||||
return true;
|
||||
|
||||
case R.id.action_delete:
|
||||
Dialogs.showDeleteEntryDialog(MainActivity.this, (d, which) -> {
|
||||
deleteEntry(_selectedEntry);
|
||||
Dialogs.showDeleteEntriesDialog(MainActivity.this, (d, which) -> {
|
||||
deleteEntries(_selectedEntries);
|
||||
|
||||
if (_selectedEntry.getGroup() != null) {
|
||||
if (!_db.getGroups().contains(_selectedEntry.getGroup())) {
|
||||
updateGroupFilterMenu();
|
||||
for (VaultEntry entry : _selectedEntries) {
|
||||
if (entry.getGroup() != null) {
|
||||
if (!_vault.getGroups().contains(entry.getGroup())) {
|
||||
updateGroupFilterMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mode.finish();
|
||||
});
|
||||
}, _selectedEntries.size());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -759,7 +809,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||
@Override
|
||||
public void onDestroyActionMode(ActionMode mode) {
|
||||
_entryListView.setActionModeState(false, null);
|
||||
_selectedEntry = null;
|
||||
_selectedEntries.clear();
|
||||
_actionMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,32 @@
|
||||
package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import com.beemdevelopment.aegis.AegisApplication;
|
||||
import com.beemdevelopment.aegis.BuildConfig;
|
||||
import com.beemdevelopment.aegis.CancelAction;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.Theme;
|
||||
import com.beemdevelopment.aegis.ViewMode;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManager;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManagerException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.importers.AegisImporter;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
|
||||
@@ -39,7 +35,16 @@ import com.beemdevelopment.aegis.services.NotificationService;
|
||||
import com.beemdevelopment.aegis.ui.models.ImportEntry;
|
||||
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
import com.takisoft.preferencex.PreferenceFragmentCompat;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
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.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.io.SuFile;
|
||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
@@ -47,6 +52,7 @@ import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -56,9 +62,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
// activity request codes
|
||||
private static final int CODE_IMPORT = 0;
|
||||
@@ -66,32 +69,31 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
private static final int CODE_SLOTS = 2;
|
||||
private static final int CODE_GROUPS = 3;
|
||||
private static final int CODE_SELECT_ENTRIES = 4;
|
||||
|
||||
// permission request codes
|
||||
private static final int CODE_PERM_IMPORT = 0;
|
||||
private static final int CODE_PERM_EXPORT = 1;
|
||||
private static final int CODE_EXPORT = 5;
|
||||
private static final int CODE_EXPORT_ENCRYPT = 6;
|
||||
|
||||
private Intent _result;
|
||||
private DatabaseManager _db;
|
||||
private VaultManager _vault;
|
||||
|
||||
// keep a reference to the type of database converter the user selected
|
||||
private Class<? extends DatabaseImporter> _importerType;
|
||||
private AegisImporter.State _importerState;
|
||||
private UUIDMap<DatabaseEntry> _importerEntries;
|
||||
private UUIDMap<VaultEntry> _importerEntries;
|
||||
|
||||
private SwitchPreference _encryptionPreference;
|
||||
private SwitchPreference _fingerprintPreference;
|
||||
private SwitchPreference _biometricsPreference;
|
||||
private Preference _autoLockPreference;
|
||||
private Preference _setPasswordPreference;
|
||||
private Preference _slotsPreference;
|
||||
private Preference _groupsPreference;
|
||||
private Preference _passwordReminderPreference;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferencesFix(Bundle savedInstanceState, String rootKey) {
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences);
|
||||
|
||||
AegisApplication app = (AegisApplication) getActivity().getApplication();
|
||||
_db = app.getDatabaseManager();
|
||||
_vault = app.getVaultManager();
|
||||
|
||||
// set the result intent in advance
|
||||
setResult(new Intent());
|
||||
@@ -115,7 +117,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
_result.putExtra("needsRecreate", true);
|
||||
getActivity().recreate();
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create());
|
||||
|
||||
return true;
|
||||
@@ -123,11 +125,16 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
});
|
||||
|
||||
Preference langPreference = findPreference("pref_lang");
|
||||
langPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
_result.putExtra("needsRecreate", true);
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
});
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
langPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
_result.putExtra("needsRecreate", true);
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
// Setting locale doesn't work on Marshmallow or below
|
||||
langPreference.setVisible(false);
|
||||
}
|
||||
|
||||
int currentViewMode = app.getPreferences().getCurrentViewMode().ordinal();
|
||||
Preference viewModePreference = findPreference("pref_view_mode");
|
||||
@@ -146,7 +153,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
_result.putExtra("needsRefresh", true);
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create());
|
||||
|
||||
return true;
|
||||
@@ -157,7 +164,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
importPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
onImport();
|
||||
startImport();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -171,12 +178,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Preference exportPreference = findPreference("pref_export");
|
||||
exportPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
onExport();
|
||||
startExport();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -200,6 +206,18 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
});
|
||||
|
||||
Preference searchAccountNamePreference = findPreference("pref_search_names");
|
||||
searchAccountNamePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
_result.putExtra("needsRefresh", true);
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference entryHighlightPreference = findPreference("pref_highlight_entry");
|
||||
entryHighlightPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
_result.putExtra("needsRefresh", true);
|
||||
return true;
|
||||
});
|
||||
|
||||
Preference tapToRevealPreference = findPreference("pref_tap_to_reveal");
|
||||
tapToRevealPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
_result.putExtra("needsRefresh", true);
|
||||
@@ -242,7 +260,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
_encryptionPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
if (!_db.isEncryptionEnabled()) {
|
||||
if (!_vault.isEncryptionEnabled()) {
|
||||
Dialogs.showSetPasswordDialog(getActivity(), new EnableEncryptionListener());
|
||||
} else {
|
||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||
@@ -251,8 +269,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try {
|
||||
_db.disableEncryption();
|
||||
} catch (DatabaseManagerException e) {
|
||||
_vault.disableEncryption();
|
||||
} catch (VaultManagerException e) {
|
||||
Toast.makeText(getActivity(), R.string.disable_encryption_error, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
@@ -276,20 +294,27 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
});
|
||||
|
||||
_fingerprintPreference = (SwitchPreference) findPreference("pref_fingerprint");
|
||||
_fingerprintPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
_biometricsPreference = (SwitchPreference) findPreference("pref_biometrics");
|
||||
_biometricsPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
DatabaseFileCredentials creds = _db.getCredentials();
|
||||
VaultFileCredentials creds = _vault.getCredentials();
|
||||
SlotList slots = creds.getSlots();
|
||||
|
||||
if (!slots.has(FingerprintSlot.class)) {
|
||||
Dialogs.showFingerprintDialog(getActivity(), new RegisterFingerprintListener());
|
||||
if (!slots.has(BiometricSlot.class)) {
|
||||
if (BiometricsHelper.isAvailable(getContext())) {
|
||||
BiometricSlotInitializer initializer = new BiometricSlotInitializer(PreferencesFragment.this, new RegisterBiometricsListener());
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.set_up_biometric))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
initializer.authenticate(info);
|
||||
}
|
||||
} else {
|
||||
// remove the fingerprint slot
|
||||
FingerprintSlot slot = slots.find(FingerprintSlot.class);
|
||||
// remove the biometric slot
|
||||
BiometricSlot slot = slots.find(BiometricSlot.class);
|
||||
slots.remove(slot);
|
||||
_db.setCredentials(creds);
|
||||
_vault.setCredentials(creds);
|
||||
|
||||
// remove the KeyStore key
|
||||
try {
|
||||
@@ -299,7 +324,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
saveDatabase();
|
||||
saveVault();
|
||||
updateEncryptionPreferences();
|
||||
}
|
||||
|
||||
@@ -318,7 +343,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(getActivity(), SlotManagerActivity.class);
|
||||
intent.putExtra("creds", _db.getCredentials());
|
||||
intent.putExtra("creds", _vault.getCredentials());
|
||||
startActivityForResult(intent, CODE_SLOTS);
|
||||
return true;
|
||||
}
|
||||
@@ -329,38 +354,22 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(getActivity(), GroupManagerActivity.class);
|
||||
intent.putExtra("groups", new ArrayList<>(_db.getGroups()));
|
||||
intent.putExtra("groups", new ArrayList<>(_vault.getGroups()));
|
||||
startActivityForResult(intent, CODE_GROUPS);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
_autoLockPreference = findPreference("pref_auto_lock");
|
||||
_passwordReminderPreference = findPreference("pref_password_reminder");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated (Bundle savedInstanceState) {
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
updateEncryptionPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (!PermissionHelper.checkResults(grantResults)) {
|
||||
Toast.makeText(getActivity(), R.string.permission_denied, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (requestCode) {
|
||||
case CODE_PERM_IMPORT:
|
||||
onImport();
|
||||
break;
|
||||
case CODE_PERM_EXPORT:
|
||||
onExport();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (data == null) {
|
||||
@@ -383,6 +392,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
case CODE_SELECT_ENTRIES:
|
||||
onSelectEntriesResult(resultCode, data);
|
||||
break;
|
||||
case CODE_EXPORT:
|
||||
// intentional fallthrough
|
||||
case CODE_EXPORT_ENCRYPT:
|
||||
onExportResult(resultCode, data, requestCode == CODE_EXPORT_ENCRYPT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,11 +409,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
getActivity().setResult(Activity.RESULT_OK, _result);
|
||||
}
|
||||
|
||||
private void onImport() {
|
||||
if (!PermissionHelper.request(getActivity(), CODE_PERM_IMPORT, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void startImport() {
|
||||
Map<String, Class<? extends DatabaseImporter>> importers = DatabaseImporter.getImporters();
|
||||
String[] names = importers.keySet().toArray(new String[0]);
|
||||
|
||||
@@ -462,12 +472,13 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
try {
|
||||
DatabaseImporter.State state = importer.read(reader);
|
||||
if (state.isEncrypted()) {
|
||||
// temporary special case for encrypted Aegis databases
|
||||
// temporary special case for encrypted Aegis vaults
|
||||
if (state instanceof AegisImporter.EncryptedState) {
|
||||
_importerState = state;
|
||||
|
||||
Intent intent = new Intent(getActivity(), AuthActivity.class);
|
||||
intent.putExtra("slots", ((AegisImporter.EncryptedState) state).getSlots());
|
||||
intent.putExtra("cancelAction", CancelAction.CLOSE);
|
||||
startActivityForResult(intent, CODE_IMPORT_DECRYPT);
|
||||
} else {
|
||||
state.decrypt(getActivity(), new DatabaseImporter.DecryptListener() {
|
||||
@@ -498,11 +509,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
return;
|
||||
}
|
||||
|
||||
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||
VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds");
|
||||
DatabaseImporter.State state;
|
||||
try {
|
||||
state = ((AegisImporter.EncryptedState) _importerState).decrypt(creds);
|
||||
} catch (DatabaseFileException e) {
|
||||
} catch (VaultFileException e) {
|
||||
e.printStackTrace();
|
||||
Toast.makeText(getActivity(), R.string.decryption_error, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
@@ -543,7 +554,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
|
||||
_importerEntries = result.getEntries();
|
||||
List<ImportEntry> entries = new ArrayList<>();
|
||||
for (DatabaseEntry entry : _importerEntries) {
|
||||
for (VaultEntry entry : _importerEntries) {
|
||||
entries.add(new ImportEntry(entry));
|
||||
}
|
||||
|
||||
@@ -553,32 +564,22 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
startActivityForResult(intent, CODE_SELECT_ENTRIES);
|
||||
}
|
||||
|
||||
private void onExport() {
|
||||
if (!PermissionHelper.request(getActivity(), CODE_PERM_EXPORT, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
private void startExport() {
|
||||
// TODO: create a custom layout to show a message AND a checkbox
|
||||
final AtomicReference<Boolean> checked = new AtomicReference<>(true);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Export the database")
|
||||
.setTitle(R.string.pref_export_summary)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
String filename;
|
||||
try {
|
||||
filename = _db.export(checked.get());
|
||||
} catch (DatabaseManagerException e) {
|
||||
Toast.makeText(getActivity(), R.string.exporting_database_error, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("application/json")
|
||||
.putExtra(Intent.EXTRA_TITLE, checked.get() ? VaultManager.FILENAME_EXPORT : VaultManager.FILENAME_EXPORT_PLAIN);
|
||||
|
||||
// make sure the new file is visible
|
||||
MediaScannerConnection.scanFile(getActivity(), new String[]{filename}, null, null);
|
||||
|
||||
Toast.makeText(getActivity(), getString(R.string.export_database_location) + filename, Toast.LENGTH_SHORT).show();
|
||||
startActivityForResult(intent, checked.get() ? CODE_EXPORT_ENCRYPT : CODE_EXPORT);
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null);
|
||||
if (_db.isEncryptionEnabled()) {
|
||||
final String[] items = {"Keep the database encrypted"};
|
||||
if (_vault.isEncryptionEnabled()) {
|
||||
final String[] items = {"Keep the vault encrypted"};
|
||||
final boolean[] checkedItems = {true};
|
||||
builder.setMultiChoiceItems(items, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
|
||||
@Override
|
||||
@@ -597,9 +598,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
return;
|
||||
}
|
||||
|
||||
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||
_db.setCredentials(creds);
|
||||
saveDatabase();
|
||||
VaultFileCredentials creds = (VaultFileCredentials) data.getSerializableExtra("creds");
|
||||
_vault.setCredentials(creds);
|
||||
saveVault();
|
||||
updateEncryptionPreferences();
|
||||
}
|
||||
|
||||
@@ -610,7 +611,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
|
||||
HashSet<String> groups = new HashSet<>(data.getStringArrayListExtra("groups"));
|
||||
|
||||
for (DatabaseEntry entry : _db.getEntries()) {
|
||||
for (VaultEntry entry : _vault.getEntries()) {
|
||||
if (!groups.contains(entry.getGroup())) {
|
||||
entry.setGroup(null);
|
||||
}
|
||||
@@ -624,18 +625,18 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
|
||||
List<ImportEntry> selectedEntries = (ArrayList<ImportEntry>) data.getSerializableExtra("entries");
|
||||
for (ImportEntry selectedEntry : selectedEntries) {
|
||||
DatabaseEntry savedEntry = _importerEntries.getByUUID(selectedEntry.getUUID());
|
||||
VaultEntry savedEntry = _importerEntries.getByUUID(selectedEntry.getUUID());
|
||||
|
||||
// temporary: randomize the UUID of duplicate entries and add them anyway
|
||||
if (_db.isEntryDuplicate(savedEntry)) {
|
||||
if (_vault.isEntryDuplicate(savedEntry)) {
|
||||
savedEntry.resetUUID();
|
||||
}
|
||||
|
||||
_db.addEntry(savedEntry);
|
||||
_vault.addEntry(savedEntry);
|
||||
}
|
||||
|
||||
_importerEntries = null;
|
||||
if (!saveDatabase()) {
|
||||
if (!saveVault()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -645,10 +646,26 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
_result.putExtra("needsRecreate", true);
|
||||
}
|
||||
|
||||
private boolean saveDatabase() {
|
||||
private void onExportResult(int resultCode, Intent data, boolean encrypt) {
|
||||
Uri uri = data.getData();
|
||||
if (resultCode != Activity.RESULT_OK || uri == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (OutputStream stream = getContext().getContentResolver().openOutputStream(uri, "w")) {
|
||||
_vault.export(stream, encrypt);
|
||||
} catch (IOException | VaultManagerException e) {
|
||||
Toast.makeText(getActivity(), R.string.exporting_vault_error, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
Toast.makeText(getActivity(), getString(R.string.exported_vault), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private boolean saveVault() {
|
||||
try {
|
||||
_db.save();
|
||||
} catch (DatabaseManagerException e) {
|
||||
_vault.save();
|
||||
} catch (VaultManagerException e) {
|
||||
Toast.makeText(getActivity(), R.string.saving_error, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
@@ -657,34 +674,37 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
|
||||
private void updateEncryptionPreferences() {
|
||||
boolean encrypted = _db.isEncryptionEnabled();
|
||||
boolean encrypted = _vault.isEncryptionEnabled();
|
||||
_encryptionPreference.setChecked(encrypted, true);
|
||||
_setPasswordPreference.setVisible(encrypted);
|
||||
_fingerprintPreference.setVisible(encrypted);
|
||||
_biometricsPreference.setVisible(encrypted);
|
||||
_slotsPreference.setEnabled(encrypted);
|
||||
_autoLockPreference.setVisible(encrypted);
|
||||
|
||||
if (encrypted) {
|
||||
SlotList slots = _db.getCredentials().getSlots();
|
||||
SlotList slots = _vault.getCredentials().getSlots();
|
||||
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
|
||||
boolean multiFinger = slots.findAll(FingerprintSlot.class).size() > 1;
|
||||
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiFinger;
|
||||
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
|
||||
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiBio;
|
||||
boolean canUseBio = BiometricsHelper.isAvailable(getContext());
|
||||
_setPasswordPreference.setEnabled(!multiPassword);
|
||||
_fingerprintPreference.setEnabled(FingerprintHelper.getManager(getContext()) != null && !multiFinger);
|
||||
_fingerprintPreference.setChecked(slots.has(FingerprintSlot.class), true);
|
||||
_biometricsPreference.setEnabled(canUseBio && !multiBio);
|
||||
_biometricsPreference.setChecked(slots.has(BiometricSlot.class), true);
|
||||
_slotsPreference.setVisible(showSlots);
|
||||
_passwordReminderPreference.setVisible(slots.has(BiometricSlot.class));
|
||||
} else {
|
||||
_setPasswordPreference.setEnabled(false);
|
||||
_fingerprintPreference.setEnabled(false);
|
||||
_fingerprintPreference.setChecked(false, true);
|
||||
_biometricsPreference.setEnabled(false);
|
||||
_biometricsPreference.setChecked(false, true);
|
||||
_slotsPreference.setVisible(false);
|
||||
_passwordReminderPreference.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private class SetPasswordListener implements Dialogs.SlotListener {
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
DatabaseFileCredentials creds = _db.getCredentials();
|
||||
VaultFileCredentials creds = _vault.getCredentials();
|
||||
SlotList slots = creds.getSlots();
|
||||
|
||||
try {
|
||||
@@ -702,8 +722,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
return;
|
||||
}
|
||||
|
||||
_db.setCredentials(creds);
|
||||
saveDatabase();
|
||||
_vault.setCredentials(creds);
|
||||
saveVault();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -713,42 +733,41 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private class RegisterFingerprintListener implements Dialogs.SlotListener {
|
||||
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
DatabaseFileCredentials creds = _db.getCredentials();
|
||||
SlotList slots = creds.getSlots();
|
||||
|
||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||
VaultFileCredentials creds = _vault.getCredentials();
|
||||
try {
|
||||
slot.setKey(creds.getKey(), cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
onSlotInitializationFailed(0, e.toString());
|
||||
return;
|
||||
}
|
||||
creds.getSlots().add(slot);
|
||||
_vault.setCredentials(creds);
|
||||
|
||||
slots.add(slot);
|
||||
_db.setCredentials(creds);
|
||||
|
||||
saveDatabase();
|
||||
saveVault();
|
||||
updateEncryptionPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
Toast.makeText(getActivity(), getString(R.string.encryption_enable_fingerprint_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
Toast.makeText(getActivity(), String.format("%s: %s", getString(R.string.encryption_enable_biometrics_error), errString), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EnableEncryptionListener implements Dialogs.SlotListener {
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
DatabaseFileCredentials creds = new DatabaseFileCredentials();
|
||||
VaultFileCredentials creds = new VaultFileCredentials();
|
||||
|
||||
try {
|
||||
slot.setKey(creds.getKey(), cipher);
|
||||
creds.getSlots().add(slot);
|
||||
_db.enableEncryption(creds);
|
||||
} catch (DatabaseManagerException | SlotException e) {
|
||||
_vault.enableEncryption(creds);
|
||||
} catch (VaultManagerException | SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import android.widget.Toast;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.Theme;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.helpers.SquareFinderView;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||
@@ -109,7 +109,7 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
|
||||
try {
|
||||
// parse google auth uri
|
||||
GoogleAuthInfo info = GoogleAuthInfo.parseUri(rawResult.getText());
|
||||
DatabaseEntry entry = new DatabaseEntry(info);
|
||||
VaultEntry entry = new VaultEntry(info);
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra("entry", entry);
|
||||
|
||||
@@ -7,27 +7,30 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.ui.views.SlotAdapter;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, Dialogs.SlotListener {
|
||||
private DatabaseFileCredentials _creds;
|
||||
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener {
|
||||
private VaultFileCredentials _creds;
|
||||
private SlotAdapter _adapter;
|
||||
|
||||
private boolean _edited;
|
||||
@@ -42,12 +45,19 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||
bar.setHomeAsUpIndicator(R.drawable.ic_close);
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
|
||||
Dialogs.showFingerprintDialog(this ,this);
|
||||
findViewById(R.id.button_add_biometric).setOnClickListener(view -> {
|
||||
if (BiometricsHelper.isAvailable(this)) {
|
||||
BiometricSlotInitializer initializer = new BiometricSlotInitializer(SlotManagerActivity.this, new RegisterBiometricsListener());
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.add_biometric_slot))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
initializer.authenticate(info);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.button_add_password).setOnClickListener(view -> {
|
||||
Dialogs.showSetPasswordDialog(this, this);
|
||||
Dialogs.showSetPasswordDialog(this, new PasswordListener());
|
||||
});
|
||||
|
||||
// set up the recycler view
|
||||
@@ -59,22 +69,22 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||
slotsView.setNestedScrollingEnabled(false);
|
||||
|
||||
// load the slots and masterKey
|
||||
_creds = (DatabaseFileCredentials) getIntent().getSerializableExtra("creds");
|
||||
_creds = (VaultFileCredentials) getIntent().getSerializableExtra("creds");
|
||||
for (Slot slot : _creds.getSlots()) {
|
||||
_adapter.addSlot(slot);
|
||||
}
|
||||
|
||||
updateFingerprintButton();
|
||||
updateBiometricsButton();
|
||||
}
|
||||
|
||||
private void updateFingerprintButton() {
|
||||
// only show the fingerprint option if we can get an instance of the fingerprint manager
|
||||
private void updateBiometricsButton() {
|
||||
// only show the biometrics option if we can get an instance of the biometrics manager
|
||||
// and if none of the slots in the collection has a matching alias in the keystore
|
||||
int visibility = View.VISIBLE;
|
||||
if (FingerprintHelper.getManager(this) != null) {
|
||||
if (BiometricsHelper.isAvailable(this)) {
|
||||
try {
|
||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||
for (FingerprintSlot slot : _creds.getSlots().findAll(FingerprintSlot.class)) {
|
||||
for (BiometricSlot slot : _creds.getSlots().findAll(BiometricSlot.class)) {
|
||||
if (keyStore.containsKey(slot.getUUID().toString())) {
|
||||
visibility = View.GONE;
|
||||
break;
|
||||
@@ -86,7 +96,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||
} else {
|
||||
visibility = View.GONE;
|
||||
}
|
||||
findViewById(R.id.button_add_fingerprint).setVisibility(visibility);
|
||||
findViewById(R.id.button_add_biometric).setVisibility(visibility);
|
||||
}
|
||||
|
||||
private void onSave() {
|
||||
@@ -162,29 +172,60 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
||||
slots.remove(slot);
|
||||
_adapter.removeSlot(slot);
|
||||
_edited = true;
|
||||
updateFingerprintButton();
|
||||
updateBiometricsButton();
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
try {
|
||||
slot.setKey(_creds.getKey(), cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
private void addSlot(Slot slot) {
|
||||
_creds.getSlots().add(slot);
|
||||
_adapter.addSlot(slot);
|
||||
_edited = true;
|
||||
updateFingerprintButton();
|
||||
updateBiometricsButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
Toast.makeText(this, getString(R.string.adding_new_slot_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
private void showSlotError(String error) {
|
||||
Toast.makeText(SlotManagerActivity.this, getString(R.string.adding_new_slot_error) + error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
||||
|
||||
@Override
|
||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||
try {
|
||||
slot.setKey(_creds.getKey(), cipher);
|
||||
addSlot(slot);
|
||||
} catch (SlotException e) {
|
||||
onSlotInitializationFailed(0, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
showSlotError(errString.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PasswordListener implements Dialogs.SlotListener {
|
||||
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
try {
|
||||
slot.setKey(_creds.getKey(), cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
addSlot(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
showSlotError(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.Registry;
|
||||
import com.bumptech.glide.annotation.GlideModule;
|
||||
@@ -16,6 +16,6 @@ import java.nio.ByteBuffer;
|
||||
public class AegisGlideModule extends AppGlideModule {
|
||||
@Override
|
||||
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||
registry.prepend(DatabaseEntry.class, ByteBuffer.class, new IconLoader.Factory());
|
||||
registry.prepend(VaultEntry.class, ByteBuffer.class, new IconLoader.Factory());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.beemdevelopment.aegis.ui.glide;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
|
||||
import de.psdev.licensesdialog.licenses.License;
|
||||
|
||||
public class GlideLicense extends License {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Glide License";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readSummaryTextFromResources(Context context) {
|
||||
return getContent(context, R.raw.glide_license);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readFullTextFromResources(Context context) {
|
||||
return getContent(context, R.raw.glide_license);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return "https://github.com/bumptech/glide/blob/master/LICENSE";
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package com.beemdevelopment.aegis.ui.glide;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.Options;
|
||||
@@ -13,21 +13,21 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class IconLoader implements ModelLoader<DatabaseEntry, ByteBuffer> {
|
||||
public class IconLoader implements ModelLoader<VaultEntry, ByteBuffer> {
|
||||
@Override
|
||||
public LoadData<ByteBuffer> buildLoadData(@NonNull DatabaseEntry model, int width, int height, @NonNull Options options) {
|
||||
public LoadData<ByteBuffer> buildLoadData(@NonNull VaultEntry model, int width, int height, @NonNull Options options) {
|
||||
return new LoadData<>(new UUIDKey(model.getUUID()), new Fetcher(model));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handles(@NonNull DatabaseEntry model) {
|
||||
public boolean handles(@NonNull VaultEntry model) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class Fetcher implements DataFetcher<ByteBuffer> {
|
||||
private DatabaseEntry _model;
|
||||
private VaultEntry _model;
|
||||
|
||||
private Fetcher(DatabaseEntry model) {
|
||||
private Fetcher(VaultEntry model) {
|
||||
_model = model;
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ public class IconLoader implements ModelLoader<DatabaseEntry, ByteBuffer> {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements ModelLoaderFactory<DatabaseEntry, ByteBuffer> {
|
||||
public static class Factory implements ModelLoaderFactory<VaultEntry, ByteBuffer> {
|
||||
@NonNull
|
||||
@Override
|
||||
public ModelLoader<DatabaseEntry, ByteBuffer> build(@NonNull MultiModelLoaderFactory unused) {
|
||||
public ModelLoader<VaultEntry, ByteBuffer> build(@NonNull MultiModelLoaderFactory unused) {
|
||||
return new IconLoader();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.beemdevelopment.aegis.ui.models;
|
||||
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
@@ -13,7 +13,7 @@ public class ImportEntry implements Serializable {
|
||||
private transient Listener _listener;
|
||||
private boolean _isChecked = true;
|
||||
|
||||
public ImportEntry(DatabaseEntry entry) {
|
||||
public ImportEntry(VaultEntry entry) {
|
||||
_uuid = entry.getUUID();
|
||||
_name = entry.getName();
|
||||
_issuer = entry.getIssuer();
|
||||
|
||||
+74
-97
@@ -1,63 +1,58 @@
|
||||
package com.beemdevelopment.aegis.ui.slides;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
|
||||
import com.github.paolorotolo.appintro.ISlidePolicy;
|
||||
import com.github.paolorotolo.appintro.ISlideSelectionListener;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiHelper.Callback, ISlidePolicy, ISlideSelectionListener {
|
||||
public class CustomAuthenticatedSlide extends Fragment implements ISlidePolicy, ISlideSelectionListener {
|
||||
private int _cryptType;
|
||||
private EditText _textPassword;
|
||||
private EditText _textPasswordConfirm;
|
||||
private CheckBox _checkPasswordVisibility;
|
||||
private int _bgColor;
|
||||
|
||||
private LinearLayout _boxFingerprint;
|
||||
private SwirlView _imgFingerprint;
|
||||
private TextView _textFingerprint;
|
||||
private FingerprintUiHelper _fingerHelper;
|
||||
private KeyStoreHandle _storeHandle;
|
||||
private FingerprintSlot _fingerSlot;
|
||||
private FingerprintManager.CryptoObject _fingerCryptoObj;
|
||||
private boolean _fingerAuthenticated;
|
||||
private BiometricSlot _bioSlot;
|
||||
private Cipher _bioCipher;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.fragment_authenticated_slide, container, false);
|
||||
_textPassword = view.findViewById(R.id.text_password);
|
||||
_textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
|
||||
_boxFingerprint = view.findViewById(R.id.box_fingerprint);
|
||||
_checkPasswordVisibility = view.findViewById(R.id.check_toggle_visibility);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ViewGroup insertPoint = view.findViewById(R.id.img_fingerprint_insert);
|
||||
_imgFingerprint = new SwirlView(getContext());
|
||||
insertPoint.addView(_imgFingerprint, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
_checkPasswordVisibility.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
_textPassword.setTransformationMethod(null);
|
||||
_textPassword.clearFocus();
|
||||
_textPasswordConfirm.setEnabled(false);
|
||||
} else {
|
||||
_textPassword.setTransformationMethod(new PasswordTransformationMethod());
|
||||
_textPasswordConfirm.setEnabled(true);
|
||||
}
|
||||
});
|
||||
|
||||
_textFingerprint = view.findViewById(R.id.text_fingerprint);
|
||||
view.findViewById(R.id.main).setBackgroundColor(_bgColor);
|
||||
return view;
|
||||
}
|
||||
@@ -66,114 +61,96 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
||||
return _cryptType;
|
||||
}
|
||||
|
||||
public BiometricSlot getBiometricSlot() {
|
||||
return _bioSlot;
|
||||
}
|
||||
|
||||
public Cipher getBiometriCipher() {
|
||||
return _bioCipher;
|
||||
}
|
||||
|
||||
public char[] getPassword() {
|
||||
return EditTextHelper.getEditTextChars(_textPassword);
|
||||
}
|
||||
|
||||
public Cipher getFingerCipher() {
|
||||
return _fingerCryptoObj.getCipher();
|
||||
}
|
||||
|
||||
public FingerprintSlot getFingerSlot() {
|
||||
return _fingerSlot;
|
||||
}
|
||||
|
||||
public void setBgColor(int color) {
|
||||
_bgColor = color;
|
||||
}
|
||||
|
||||
public void showBiometricPrompt() {
|
||||
BiometricSlotInitializer initializer = new BiometricSlotInitializer(this, new BiometricsListener());
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.set_up_biometric))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
initializer.authenticate(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlideSelected() {
|
||||
Intent intent = getActivity().getIntent();
|
||||
_cryptType = intent.getIntExtra("cryptType", CustomAuthenticationSlide.CRYPT_TYPE_INVALID);
|
||||
|
||||
switch(_cryptType) {
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_NONE:
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_PASS:
|
||||
break;
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_FINGER:
|
||||
_boxFingerprint.setVisibility(View.VISIBLE);
|
||||
|
||||
SecretKey key;
|
||||
try {
|
||||
if (_storeHandle == null) {
|
||||
_storeHandle = new KeyStoreHandle();
|
||||
_fingerSlot = new FingerprintSlot();
|
||||
}
|
||||
key = _storeHandle.generateKey(_fingerSlot.getUUID().toString());
|
||||
} catch (KeyStoreHandleException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (_fingerHelper == null) {
|
||||
FingerprintManager fingerManager = (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
_fingerHelper = new FingerprintUiHelper(fingerManager, _imgFingerprint, _textFingerprint, this);
|
||||
}
|
||||
|
||||
try {
|
||||
Cipher cipher = Slot.createEncryptCipher(key);
|
||||
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
_fingerHelper.startListening(_fingerCryptoObj);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
if (_cryptType == CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC) {
|
||||
showBiometricPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlideDeselected() {
|
||||
if (_fingerHelper != null) {
|
||||
_fingerAuthenticated = false;
|
||||
_boxFingerprint.setVisibility(View.INVISIBLE);
|
||||
_fingerHelper.stopListening();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPolicyRespected() {
|
||||
switch(_cryptType) {
|
||||
switch (_cryptType) {
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_NONE:
|
||||
return true;
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_FINGER:
|
||||
if (!_fingerAuthenticated) {
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC:
|
||||
if (_bioSlot == null) {
|
||||
return false;
|
||||
}
|
||||
// intentional fallthrough
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_PASS:
|
||||
return EditTextHelper.areEditTextsEqual(_textPassword, _textPasswordConfirm);
|
||||
if (EditTextHelper.getEditTextChars(_textPassword).length > 0) {
|
||||
return EditTextHelper.areEditTextsEqual(_textPassword, _textPasswordConfirm) || _checkPasswordVisibility.isChecked();
|
||||
}
|
||||
|
||||
return false;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserIllegallyRequestedNextPage() {
|
||||
String message;
|
||||
if (!EditTextHelper.areEditTextsEqual(_textPassword, _textPasswordConfirm)) {
|
||||
if (!EditTextHelper.areEditTextsEqual(_textPassword, _textPasswordConfirm) && !_checkPasswordVisibility.isChecked()) {
|
||||
message = getString(R.string.password_equality_error);
|
||||
} else if (!_fingerAuthenticated) {
|
||||
message = getString(R.string.register_fingerprint);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
|
||||
snackbar.show();
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
|
||||
snackbar.show();
|
||||
}
|
||||
} else if (_bioSlot == null) {
|
||||
showBiometricPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticated() {
|
||||
_fingerAuthenticated = true;
|
||||
}
|
||||
private class BiometricsListener implements BiometricSlotInitializer.Listener {
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
@Override
|
||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||
_bioSlot = slot;
|
||||
_bioCipher = cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
Toast.makeText(CustomAuthenticatedSlide.this.getContext(), errString, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-11
@@ -1,7 +1,6 @@
|
||||
package com.beemdevelopment.aegis.ui.slides;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -10,18 +9,18 @@ import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.github.paolorotolo.appintro.ISlidePolicy;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy, 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;
|
||||
public static final int CRYPT_TYPE_FINGER = 3;
|
||||
public static final int CRYPT_TYPE_BIOMETRIC = 3;
|
||||
|
||||
private RadioGroup _buttonGroup;
|
||||
private int _bgColor;
|
||||
@@ -34,12 +33,12 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
|
||||
onCheckedChanged(_buttonGroup, _buttonGroup.getCheckedRadioButtonId());
|
||||
|
||||
// only enable the fingerprint option if the api version is new enough, permission is granted and a scanner is found
|
||||
FingerprintManager manager = FingerprintHelper.getManager(getContext());
|
||||
if (manager != null) {
|
||||
RadioButton button = view.findViewById(R.id.rb_fingerprint);
|
||||
TextView text = view.findViewById(R.id.text_rb_fingerprint);
|
||||
if (BiometricsHelper.isAvailable(getContext())) {
|
||||
RadioButton button = view.findViewById(R.id.rb_biometrics);
|
||||
TextView text = view.findViewById(R.id.text_rb_biometrics);
|
||||
button.setEnabled(true);
|
||||
text.setEnabled(true);
|
||||
_buttonGroup.check(R.id.rb_biometrics);
|
||||
}
|
||||
|
||||
view.findViewById(R.id.main).setBackgroundColor(_bgColor);
|
||||
@@ -75,8 +74,8 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
|
||||
case R.id.rb_password:
|
||||
id = CRYPT_TYPE_PASS;
|
||||
break;
|
||||
case R.id.rb_fingerprint:
|
||||
id = CRYPT_TYPE_FINGER;
|
||||
case R.id.rb_biometrics:
|
||||
id = CRYPT_TYPE_BIOMETRIC;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.content.Context;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.beemdevelopment.aegis.ui.Dialogs;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
|
||||
public abstract class ProgressDialogTask<Params, Result> extends AsyncTask<Params, Void, Result> {
|
||||
public abstract class ProgressDialogTask<Params, Result> extends AsyncTask<Params, String, Result> {
|
||||
private ProgressDialog _dialog;
|
||||
|
||||
public ProgressDialogTask(Context context, String message) {
|
||||
@@ -33,6 +33,13 @@ public abstract class ProgressDialogTask<Params, Result> extends AsyncTask<Param
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(String... values) {
|
||||
if (values.length == 1) {
|
||||
_dialog.setMessage(values[0]);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setPriority() {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package com.beemdevelopment.aegis.ui.tasks;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotIntegrityException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotIntegrityException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
@@ -38,8 +37,8 @@ public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTas
|
||||
if (slot instanceof PasswordSlot) {
|
||||
char[] password = (char[]) params.getObj();
|
||||
return decryptPasswordSlot((PasswordSlot) slot, password);
|
||||
} else if (slot instanceof FingerprintSlot) {
|
||||
return decryptFingerprintSlot((FingerprintSlot) slot, (Cipher) params.getObj());
|
||||
} else if (slot instanceof BiometricSlot) {
|
||||
return decryptBiometricSlot((BiometricSlot) slot, (Cipher) params.getObj());
|
||||
}
|
||||
} catch (SlotException e) {
|
||||
throw new RuntimeException(e);
|
||||
@@ -51,7 +50,7 @@ public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTas
|
||||
return null;
|
||||
}
|
||||
|
||||
private Result decryptFingerprintSlot(FingerprintSlot slot, Cipher cipher)
|
||||
private Result decryptBiometricSlot(BiometricSlot slot, Cipher cipher)
|
||||
throws SlotException, SlotIntegrityException {
|
||||
MasterKey key = slot.getKey(cipher);
|
||||
return new Result(key, slot);
|
||||
@@ -72,8 +71,7 @@ public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTas
|
||||
throw e;
|
||||
}
|
||||
|
||||
ProgressDialog dialog = getDialog();
|
||||
dialog.setMessage(dialog.getContext().getString(R.string.unlocking_vault_repair));
|
||||
publishProgress(getDialog().getContext().getString(R.string.unlocking_vault_repair));
|
||||
|
||||
// try to decrypt the password slot with the old key
|
||||
SecretKey oldKey = slot.deriveKey(oldPasswordBytes);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
package com.beemdevelopment.aegis.ui.views;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.SortCategory;
|
||||
import com.beemdevelopment.aegis.ViewMode;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.helpers.ItemTouchHelperAdapter;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
@@ -18,14 +22,15 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements ItemTouchHelperAdapter {
|
||||
private EntryListView _view;
|
||||
private List<DatabaseEntry> _entries;
|
||||
private List<DatabaseEntry> _shownEntries;
|
||||
private DatabaseEntry _selectedEntry;
|
||||
private List<VaultEntry> _entries;
|
||||
private List<VaultEntry> _shownEntries;
|
||||
private List<VaultEntry> _selectedEntries;
|
||||
private VaultEntry _focusedEntry;
|
||||
private boolean _showAccountName;
|
||||
private boolean _searchAccountName;
|
||||
private boolean _highlightEntry;
|
||||
private boolean _tapToReveal;
|
||||
private int _tapToRevealTime;
|
||||
private String _groupFilter;
|
||||
@@ -33,6 +38,8 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
private ViewMode _viewMode;
|
||||
private String _searchFilter;
|
||||
private boolean _isPeriodUniform = true;
|
||||
private int _uniformPeriod = -1;
|
||||
private Handler _dimHandler;
|
||||
|
||||
// keeps track of the viewholders that are currently bound
|
||||
private List<EntryHolder> _holders;
|
||||
@@ -40,7 +47,9 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
public EntryAdapter(EntryListView view) {
|
||||
_entries = new ArrayList<>();
|
||||
_shownEntries = new ArrayList<>();
|
||||
_selectedEntries = new ArrayList<>();
|
||||
_holders = new ArrayList<>();
|
||||
_dimHandler = new Handler();
|
||||
_view = view;
|
||||
}
|
||||
|
||||
@@ -63,18 +72,26 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
_tapToRevealTime = number;
|
||||
}
|
||||
|
||||
public DatabaseEntry getEntryAt(int position) {
|
||||
public void setSearchAccountName(boolean searchAccountName) {
|
||||
_searchAccountName = searchAccountName;
|
||||
}
|
||||
|
||||
public void setHighlightEntry(boolean highlightEntry) {
|
||||
_highlightEntry = highlightEntry;
|
||||
}
|
||||
|
||||
public VaultEntry getEntryAt(int position) {
|
||||
return _shownEntries.get(position);
|
||||
}
|
||||
|
||||
public void addEntry(DatabaseEntry entry) {
|
||||
public void addEntry(VaultEntry entry) {
|
||||
_entries.add(entry);
|
||||
if (isEntryFiltered(entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean added = false;
|
||||
Comparator<DatabaseEntry> comparator = _sortCategory.getComparator();
|
||||
Comparator<VaultEntry> comparator = _sortCategory.getComparator();
|
||||
if (comparator != null) {
|
||||
// insert the entry in the correct order
|
||||
// note: this assumes that _shownEntries has already been sorted
|
||||
@@ -102,13 +119,13 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
checkPeriodUniformity();
|
||||
}
|
||||
|
||||
public void addEntries(List<DatabaseEntry> entries) {
|
||||
public void addEntries(List<VaultEntry> entries) {
|
||||
_entries.addAll(entries);
|
||||
updateShownEntries();
|
||||
checkPeriodUniformity(true);
|
||||
}
|
||||
|
||||
public void removeEntry(DatabaseEntry entry) {
|
||||
public void removeEntry(VaultEntry entry) {
|
||||
_entries.remove(entry);
|
||||
|
||||
if (_shownEntries.contains(entry)) {
|
||||
@@ -127,7 +144,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
checkPeriodUniformity();
|
||||
}
|
||||
|
||||
public void replaceEntry(DatabaseEntry oldEntry, DatabaseEntry newEntry) {
|
||||
public void replaceEntry(VaultEntry oldEntry, VaultEntry newEntry) {
|
||||
_entries.set(_entries.indexOf(oldEntry), newEntry);
|
||||
|
||||
if (_shownEntries.contains(oldEntry)) {
|
||||
@@ -150,15 +167,26 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
checkPeriodUniformity();
|
||||
}
|
||||
|
||||
private boolean isEntryFiltered(DatabaseEntry entry) {
|
||||
private boolean isEntryFiltered(VaultEntry entry) {
|
||||
String group = entry.getGroup();
|
||||
String issuer = entry.getIssuer().toLowerCase();
|
||||
String name = entry.getName().toLowerCase();
|
||||
|
||||
if (_groupFilter != null && (group == null || !group.equals(_groupFilter))) {
|
||||
return true;
|
||||
if (_groupFilter != null) {
|
||||
if (group == null && _groupFilter.equals(_view.getContext().getString(R.string.filter_ungrouped))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (group == null || !group.equals(_groupFilter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return _searchFilter != null && !issuer.contains(_searchFilter);
|
||||
if (_searchFilter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !issuer.contains(_searchFilter) && !(_searchAccountName && name.contains(_searchFilter));
|
||||
}
|
||||
|
||||
public void refresh(boolean hard) {
|
||||
@@ -203,14 +231,14 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
_shownEntries.clear();
|
||||
|
||||
// add entries back that are not filtered out
|
||||
for (DatabaseEntry entry : _entries) {
|
||||
for (VaultEntry entry : _entries) {
|
||||
if (!isEntryFiltered(entry)) {
|
||||
_shownEntries.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// sort the remaining list of entries
|
||||
Comparator<DatabaseEntry> comparator = _sortCategory.getComparator();
|
||||
Comparator<VaultEntry> comparator = _sortCategory.getComparator();
|
||||
if (comparator != null) {
|
||||
Collections.sort(_shownEntries, comparator);
|
||||
}
|
||||
@@ -244,7 +272,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
return;
|
||||
}
|
||||
|
||||
// notify the database first
|
||||
// notify the vault first
|
||||
_view.onEntryMove(_entries.get(firstPosition), _entries.get(secondPosition));
|
||||
|
||||
// update our side of things
|
||||
@@ -275,22 +303,43 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final EntryHolder holder, int position) {
|
||||
DatabaseEntry entry = _shownEntries.get(position);
|
||||
holder.setFocused(entry == _selectedEntry);
|
||||
VaultEntry entry = _shownEntries.get(position);
|
||||
holder.setFocused(_selectedEntries.contains(entry));
|
||||
|
||||
boolean hidden = _tapToReveal && entry != _focusedEntry;
|
||||
boolean dimmed = _highlightEntry && _focusedEntry != null && _focusedEntry != entry;
|
||||
boolean showProgress = !isPeriodUniform() && entry.getInfo() instanceof TotpInfo;
|
||||
holder.setData(entry, _showAccountName, showProgress, _tapToReveal);
|
||||
holder.setTapToRevealTime(_tapToRevealTime);
|
||||
holder.setData(entry, _showAccountName, showProgress, hidden, dimmed);
|
||||
holder.loadIcon(_view);
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = holder.getAdapterPosition();
|
||||
if (_tapToReveal && holder.isCodeHidden() && _selectedEntry == null) {
|
||||
holder.revealCode();
|
||||
boolean handled = false;
|
||||
|
||||
if (_selectedEntries.isEmpty()) {
|
||||
if (_highlightEntry || _tapToReveal) {
|
||||
if (_focusedEntry == entry) {
|
||||
resetFocus();
|
||||
handled = true;
|
||||
} else {
|
||||
focusEntry(entry);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_view.onEntryClick(_shownEntries.get(position));
|
||||
if (_selectedEntries.contains(entry)) {
|
||||
_view.onDeselect(entry);
|
||||
removeSelectedEntry(entry);
|
||||
holder.setFocused(false);
|
||||
} else {
|
||||
holder.setFocused(true);
|
||||
addSelectedEntry(entry);
|
||||
_view.onSelect(entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
_view.onEntryClick(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -298,8 +347,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
int position = holder.getAdapterPosition();
|
||||
if (_selectedEntry == null) {
|
||||
setSelectedEntry(_shownEntries.get(position));
|
||||
if (_selectedEntries.isEmpty()) {
|
||||
holder.setFocused(true);
|
||||
}
|
||||
|
||||
@@ -317,7 +365,7 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
}
|
||||
|
||||
// notify the listener that the counter has been incremented
|
||||
// this gives it a chance to save the database
|
||||
// this gives it a chance to save the vault
|
||||
_view.onEntryChange(entry);
|
||||
|
||||
// finally, refresh the code in the UI
|
||||
@@ -333,22 +381,24 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
}
|
||||
|
||||
private void checkPeriodUniformity(boolean force) {
|
||||
int uniformPeriod = getUniformPeriod();
|
||||
boolean uniform = isPeriodUniform();
|
||||
if (!force && uniform == _isPeriodUniform) {
|
||||
if (!force && uniform == _isPeriodUniform && uniformPeriod == _uniformPeriod) {
|
||||
return;
|
||||
}
|
||||
_isPeriodUniform = uniform;
|
||||
_uniformPeriod = uniformPeriod;
|
||||
|
||||
for (EntryHolder holder : _holders) {
|
||||
holder.setShowProgress(!_isPeriodUniform);
|
||||
}
|
||||
|
||||
_view.onPeriodUniformityChanged(_isPeriodUniform);
|
||||
_view.onPeriodUniformityChanged(_isPeriodUniform, _uniformPeriod);
|
||||
}
|
||||
|
||||
public int getUniformPeriod() {
|
||||
List<TotpInfo> infos = new ArrayList<>();
|
||||
for (DatabaseEntry entry : _shownEntries) {
|
||||
for (VaultEntry entry : _shownEntries) {
|
||||
OtpInfo info = entry.getInfo();
|
||||
if (info instanceof TotpInfo) {
|
||||
infos.add((TotpInfo) info);
|
||||
@@ -369,12 +419,61 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
return period;
|
||||
}
|
||||
|
||||
public void setSelectedEntry(DatabaseEntry entry) {
|
||||
if (entry == null) {
|
||||
notifyItemChanged(_shownEntries.indexOf(_selectedEntry));
|
||||
private void focusEntry(VaultEntry entry) {
|
||||
_focusedEntry = entry;
|
||||
_dimHandler.removeCallbacksAndMessages(null);
|
||||
|
||||
for (EntryHolder holder : _holders) {
|
||||
if (holder.getEntry() != _focusedEntry) {
|
||||
if (_highlightEntry) {
|
||||
holder.dim();
|
||||
}
|
||||
if (_tapToReveal) {
|
||||
holder.hideCode();
|
||||
}
|
||||
} else {
|
||||
if (_highlightEntry) {
|
||||
holder.highlight();
|
||||
}
|
||||
if (_tapToReveal) {
|
||||
holder.revealCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_selectedEntry = entry;
|
||||
_dimHandler.postDelayed(this::resetFocus, _tapToRevealTime * 1000);
|
||||
}
|
||||
|
||||
private void resetFocus() {
|
||||
for (EntryHolder holder : _holders) {
|
||||
if (_highlightEntry) {
|
||||
holder.highlight();
|
||||
}
|
||||
if (_tapToReveal) {
|
||||
holder.hideCode();
|
||||
}
|
||||
}
|
||||
|
||||
_focusedEntry = null;
|
||||
}
|
||||
|
||||
public void removeSelectedEntry(VaultEntry entry) {
|
||||
_selectedEntries.remove(entry);
|
||||
}
|
||||
|
||||
public void addSelectedEntry(VaultEntry entry) {
|
||||
if (entry == null) {
|
||||
for (VaultEntry vaultEntry: _selectedEntries) {
|
||||
notifyItemChanged(_shownEntries.indexOf(vaultEntry));
|
||||
}
|
||||
|
||||
_selectedEntries.clear();
|
||||
return;
|
||||
} else if (_highlightEntry) {
|
||||
resetFocus();
|
||||
}
|
||||
|
||||
_selectedEntries.add(entry);
|
||||
}
|
||||
|
||||
public boolean isDragAndDropAllowed() {
|
||||
@@ -382,7 +481,11 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
}
|
||||
|
||||
public boolean isPeriodUniform() {
|
||||
return getUniformPeriod() != -1;
|
||||
return isPeriodUniform(getUniformPeriod());
|
||||
}
|
||||
|
||||
private static boolean isPeriodUniform(int period) {
|
||||
return period != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -391,11 +494,13 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onEntryClick(DatabaseEntry entry);
|
||||
boolean onLongEntryClick(DatabaseEntry entry);
|
||||
void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2);
|
||||
void onEntryDrop(DatabaseEntry entry);
|
||||
void onEntryChange(DatabaseEntry entry);
|
||||
void onPeriodUniformityChanged(boolean uniform);
|
||||
void onEntryClick(VaultEntry entry);
|
||||
boolean onLongEntryClick(VaultEntry entry);
|
||||
void onEntryMove(VaultEntry entry1, VaultEntry entry2);
|
||||
void onEntryDrop(VaultEntry entry);
|
||||
void onEntryChange(VaultEntry entry);
|
||||
void onPeriodUniformityChanged(boolean uniform, int period);
|
||||
void onSelect(VaultEntry entry);
|
||||
void onDeselect(VaultEntry entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package com.beemdevelopment.aegis.ui.views;
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Handler;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.amulyakhare.textdrawable.TextDrawable;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
|
||||
import com.beemdevelopment.aegis.helpers.ThemeHelper;
|
||||
import com.beemdevelopment.aegis.helpers.UiRefresher;
|
||||
@@ -19,25 +21,23 @@ import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
private static final float DEFAULT_ALPHA = 1.0f;
|
||||
private static final float DIMMED_ALPHA = 0.2f;
|
||||
|
||||
private TextView _profileName;
|
||||
private TextView _profileCode;
|
||||
private TextView _profileIssuer;
|
||||
private ImageView _profileDrawable;
|
||||
private DatabaseEntry _entry;
|
||||
private VaultEntry _entry;
|
||||
private ImageView _buttonRefresh;
|
||||
|
||||
private boolean _hidden;
|
||||
private int _tapToRevealTime;
|
||||
|
||||
private PeriodProgressBar _progressBar;
|
||||
private View _view;
|
||||
|
||||
private UiRefresher _refresher;
|
||||
private Handler _hiddenHandler;
|
||||
|
||||
public EntryHolder(final View view) {
|
||||
super(view);
|
||||
@@ -58,7 +58,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
_refresher = new UiRefresher(new UiRefresher.Listener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
if (!isCodeHidden()) {
|
||||
if (!_hidden) {
|
||||
refreshCode();
|
||||
}
|
||||
|
||||
@@ -70,11 +70,9 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
return ((TotpInfo)_entry.getInfo()).getMillisTillNextRotation();
|
||||
}
|
||||
});
|
||||
|
||||
_hiddenHandler = new Handler();
|
||||
}
|
||||
|
||||
public void setData(DatabaseEntry entry, boolean showAccountName, boolean showProgress, boolean hidden) {
|
||||
public void setData(VaultEntry entry, boolean showAccountName, boolean showProgress, boolean hidden, boolean dimmed) {
|
||||
_entry = entry;
|
||||
_hidden = hidden;
|
||||
|
||||
@@ -90,14 +88,17 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
_profileName.setText(" - " + entry.getName());
|
||||
}
|
||||
|
||||
// cancel any scheduled hideCode calls
|
||||
_hiddenHandler.removeCallbacksAndMessages(null);
|
||||
|
||||
if (_hidden) {
|
||||
hideCode();
|
||||
} else {
|
||||
refreshCode();
|
||||
}
|
||||
|
||||
itemView.setAlpha(dimmed ? DIMMED_ALPHA : DEFAULT_ALPHA);
|
||||
}
|
||||
|
||||
public VaultEntry getEntry() {
|
||||
return _entry;
|
||||
}
|
||||
|
||||
public void loadIcon(Fragment fragment) {
|
||||
@@ -118,10 +119,6 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
return _profileDrawable;
|
||||
}
|
||||
|
||||
public void setTapToRevealTime(int number) {
|
||||
_tapToRevealTime = number;
|
||||
}
|
||||
|
||||
public void setOnRefreshClickListener(View.OnClickListener listener) {
|
||||
_buttonRefresh.setOnClickListener(listener);
|
||||
}
|
||||
@@ -162,7 +159,7 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
}
|
||||
|
||||
public void refreshCode() {
|
||||
if (!isCodeHidden()) {
|
||||
if (!_hidden) {
|
||||
updateCode();
|
||||
}
|
||||
}
|
||||
@@ -187,16 +184,23 @@ public class EntryHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public void revealCode() {
|
||||
updateCode();
|
||||
_hiddenHandler.postDelayed(this::hideCode, _tapToRevealTime * 1000);
|
||||
_hidden = false;
|
||||
}
|
||||
|
||||
private void hideCode() {
|
||||
public void hideCode() {
|
||||
_profileCode.setText(R.string.tap_to_reveal);
|
||||
_hidden = true;
|
||||
}
|
||||
|
||||
public boolean isCodeHidden() {
|
||||
return _hidden;
|
||||
public void dim() {
|
||||
animateAlphaTo(DIMMED_ALPHA);
|
||||
}
|
||||
|
||||
public void highlight() {
|
||||
animateAlphaTo(DEFAULT_ALPHA);
|
||||
}
|
||||
|
||||
private void animateAlphaTo(float alpha) {
|
||||
itemView.animate().alpha(alpha).setDuration(200).start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,21 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.LayoutAnimationController;
|
||||
import android.widget.Toast;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.SortCategory;
|
||||
import com.beemdevelopment.aegis.ViewMode;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.helpers.SimpleItemTouchHelperCallback;
|
||||
import com.beemdevelopment.aegis.helpers.UiRefresher;
|
||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||
@@ -27,15 +36,6 @@ import com.bumptech.glide.util.ViewPreloadSizeProvider;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
private EntryAdapter _adapter;
|
||||
private Listener _listener;
|
||||
@@ -43,10 +43,11 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
|
||||
private RecyclerView _recyclerView;
|
||||
private RecyclerView.ItemDecoration _dividerDecoration;
|
||||
private ViewPreloadSizeProvider<DatabaseEntry> _preloadSizeProvider;
|
||||
private ViewPreloadSizeProvider<VaultEntry> _preloadSizeProvider;
|
||||
private PeriodProgressBar _progressBar;
|
||||
private boolean _showProgress;
|
||||
private ViewMode _viewMode;
|
||||
private LinearLayout _emptyStateView;
|
||||
|
||||
private UiRefresher _refresher;
|
||||
|
||||
@@ -81,7 +82,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
// set up icon preloading
|
||||
_preloadSizeProvider = new ViewPreloadSizeProvider<>();
|
||||
IconPreloadProvider modelProvider = new IconPreloadProvider();
|
||||
RecyclerViewPreloader<DatabaseEntry> preloader = new RecyclerViewPreloader<>(Glide.with(this), modelProvider, _preloadSizeProvider, 10);
|
||||
RecyclerViewPreloader<VaultEntry> preloader = new RecyclerViewPreloader<>(Glide.with(this), modelProvider, _preloadSizeProvider, 10);
|
||||
_recyclerView.addOnScrollListener(preloader);
|
||||
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(view.getContext());
|
||||
@@ -107,6 +108,8 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
}
|
||||
});
|
||||
|
||||
_emptyStateView = view.findViewById(R.id.vEmptyList);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -129,10 +132,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
public void setActionModeState(boolean enabled, DatabaseEntry entry) {
|
||||
public void setIsLongPressDragEnabled(boolean enabled) {
|
||||
_touchCallback.setIsLongPressDragEnabled(enabled);
|
||||
}
|
||||
|
||||
public void setActionModeState(boolean enabled, VaultEntry entry) {
|
||||
_touchCallback.setSelectedEntry(entry);
|
||||
_touchCallback.setIsLongPressDragEnabled(enabled && _adapter.isDragAndDropAllowed());
|
||||
_adapter.setSelectedEntry(entry);
|
||||
_adapter.addSelectedEntry(entry);
|
||||
}
|
||||
|
||||
public void setSortCategory(SortCategory sortCategory, boolean apply) {
|
||||
@@ -167,36 +174,43 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryClick(DatabaseEntry entry) {
|
||||
public void onEntryClick(VaultEntry entry) {
|
||||
_listener.onEntryClick(entry);
|
||||
}
|
||||
|
||||
public boolean onLongEntryClick(DatabaseEntry entry) {
|
||||
public boolean onLongEntryClick(VaultEntry entry) {
|
||||
_listener.onLongEntryClick(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2) {
|
||||
public void onEntryMove(VaultEntry entry1, VaultEntry entry2) {
|
||||
_listener.onEntryMove(entry1, entry2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryDrop(DatabaseEntry entry) {
|
||||
public void onEntryDrop(VaultEntry entry) {
|
||||
_listener.onEntryDrop(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEntryChange(DatabaseEntry entry) {
|
||||
public void onEntryChange(VaultEntry entry) {
|
||||
_listener.onEntryChange(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPeriodUniformityChanged(boolean isUniform) {
|
||||
public void onSelect(VaultEntry entry) { _listener.onSelect(entry); }
|
||||
|
||||
@Override
|
||||
public void onDeselect(VaultEntry entry) { _listener.onDeselect(entry); }
|
||||
|
||||
@Override
|
||||
public void onPeriodUniformityChanged(boolean isUniform, int period) {
|
||||
setShowProgress(isUniform);
|
||||
if (_showProgress) {
|
||||
_refresher.stop();
|
||||
_progressBar.setVisibility(View.VISIBLE);
|
||||
_progressBar.setPeriod(_adapter.getUniformPeriod());
|
||||
_progressBar.setPeriod(period);
|
||||
_refresher.start();
|
||||
} else {
|
||||
_progressBar.setVisibility(View.GONE);
|
||||
@@ -208,6 +222,14 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
_adapter.setShowAccountName(showAccountName);
|
||||
}
|
||||
|
||||
public void setSearchAccountName(boolean searchAccountName) {
|
||||
_adapter.setSearchAccountName(searchAccountName);
|
||||
}
|
||||
|
||||
public void setHighlightEntry(boolean highlightEntry) {
|
||||
_adapter.setHighlightEntry(highlightEntry);
|
||||
}
|
||||
|
||||
public void setTapToReveal(boolean tapToReveal) {
|
||||
_adapter.setTapToReveal(tapToReveal);
|
||||
}
|
||||
@@ -216,23 +238,26 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
_adapter.setTapToRevealTime(number);
|
||||
}
|
||||
|
||||
public void addEntry(DatabaseEntry entry) {
|
||||
public void addEntry(VaultEntry entry) {
|
||||
_adapter.addEntry(entry);
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
public void addEntries(List<DatabaseEntry> entries) {
|
||||
public void addEntries(List<VaultEntry> entries) {
|
||||
_adapter.addEntries(entries);
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
public void removeEntry(DatabaseEntry entry) {
|
||||
public void removeEntry(VaultEntry entry) {
|
||||
_adapter.removeEntry(entry);
|
||||
updateEmptyState();
|
||||
}
|
||||
|
||||
public void clearEntries() {
|
||||
_adapter.clearEntries();
|
||||
}
|
||||
|
||||
public void replaceEntry(DatabaseEntry oldEntry, DatabaseEntry newEntry) {
|
||||
public void replaceEntry(VaultEntry oldEntry, VaultEntry newEntry) {
|
||||
_adapter.replaceEntry(oldEntry, newEntry);
|
||||
}
|
||||
|
||||
@@ -267,13 +292,25 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
_recyclerView.addItemDecoration(_dividerDecoration);
|
||||
}
|
||||
|
||||
private void updateEmptyState() {
|
||||
if (_adapter.getItemCount() > 0) {
|
||||
_recyclerView.setVisibility(View.VISIBLE);
|
||||
_emptyStateView.setVisibility(View.GONE);
|
||||
} else {
|
||||
_recyclerView.setVisibility(View.GONE);
|
||||
_emptyStateView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onEntryClick(DatabaseEntry entry);
|
||||
void onEntryMove(DatabaseEntry entry1, DatabaseEntry entry2);
|
||||
void onEntryDrop(DatabaseEntry entry);
|
||||
void onEntryChange(DatabaseEntry entry);
|
||||
void onLongEntryClick(DatabaseEntry entry);
|
||||
void onEntryClick(VaultEntry entry);
|
||||
void onEntryMove(VaultEntry entry1, VaultEntry entry2);
|
||||
void onEntryDrop(VaultEntry entry);
|
||||
void onEntryChange(VaultEntry entry);
|
||||
void onLongEntryClick(VaultEntry entry);
|
||||
void onScroll(int dx, int dy);
|
||||
void onSelect(VaultEntry entry);
|
||||
void onDeselect(VaultEntry entry);
|
||||
}
|
||||
|
||||
private class VerticalSpaceItemDecoration extends RecyclerView.ItemDecoration {
|
||||
@@ -294,11 +331,11 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private class IconPreloadProvider implements ListPreloader.PreloadModelProvider<DatabaseEntry> {
|
||||
private class IconPreloadProvider implements ListPreloader.PreloadModelProvider<VaultEntry> {
|
||||
@NonNull
|
||||
@Override
|
||||
public List<DatabaseEntry> getPreloadItems(int position) {
|
||||
DatabaseEntry entry = _adapter.getEntryAt(position);
|
||||
public List<VaultEntry> getPreloadItems(int position) {
|
||||
VaultEntry entry = _adapter.getEntryAt(position);
|
||||
if (!entry.hasIcon()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -307,7 +344,7 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RequestBuilder getPreloadRequestBuilder(@NonNull DatabaseEntry entry) {
|
||||
public RequestBuilder getPreloadRequestBuilder(@NonNull VaultEntry entry) {
|
||||
return Glide.with(EntryListView.this)
|
||||
.asDrawable()
|
||||
.load(entry)
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.ui.models.ImportEntry;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||
import com.beemdevelopment.aegis.ui.models.ImportEntry;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
@@ -5,7 +5,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
@@ -5,16 +5,16 @@ import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.RawSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.RawSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
|
||||
public class SlotHolder extends RecyclerView.ViewHolder {
|
||||
private TextView _slotUsed;
|
||||
@@ -36,10 +36,10 @@ public class SlotHolder extends RecyclerView.ViewHolder {
|
||||
if (slot instanceof PasswordSlot) {
|
||||
_slotName.setText(R.string.password);
|
||||
_slotImg.setImageResource(R.drawable.ic_create_black_24dp);
|
||||
} else if (slot instanceof FingerprintSlot) {
|
||||
_slotName.setText(R.string.authentication_method_fingerprint);
|
||||
} else if (slot instanceof BiometricSlot) {
|
||||
_slotName.setText(R.string.authentication_method_biometrics);
|
||||
_slotImg.setImageResource(R.drawable.ic_fingerprint_black_24dp);
|
||||
if (FingerprintHelper.isSupported()) {
|
||||
if (BiometricsHelper.isAvailable(itemView.getContext())) {
|
||||
try {
|
||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||
if (keyStore.containsKey(slot.getUUID().toString())) {
|
||||
|
||||
+12
-12
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
@@ -8,14 +8,14 @@ import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public class Database {
|
||||
public class Vault {
|
||||
private static final int VERSION = 1;
|
||||
private UUIDMap<DatabaseEntry> _entries = new UUIDMap<>();
|
||||
private UUIDMap<VaultEntry> _entries = new UUIDMap<>();
|
||||
|
||||
public JSONObject toJson() {
|
||||
try {
|
||||
JSONArray array = new JSONArray();
|
||||
for (DatabaseEntry e : _entries) {
|
||||
for (VaultEntry e : _entries) {
|
||||
array.put(e.toJson());
|
||||
}
|
||||
|
||||
@@ -28,29 +28,29 @@ public class Database {
|
||||
}
|
||||
}
|
||||
|
||||
public static Database fromJson(JSONObject obj) throws DatabaseException {
|
||||
Database db = new Database();
|
||||
UUIDMap<DatabaseEntry> entries = db.getEntries();
|
||||
public static Vault fromJson(JSONObject obj) throws VaultException {
|
||||
Vault vault = new Vault();
|
||||
UUIDMap<VaultEntry> entries = vault.getEntries();
|
||||
|
||||
try {
|
||||
int ver = obj.getInt("version");
|
||||
if (ver != VERSION) {
|
||||
throw new DatabaseException("Unsupported version");
|
||||
throw new VaultException("Unsupported version");
|
||||
}
|
||||
|
||||
JSONArray array = obj.getJSONArray("entries");
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
DatabaseEntry entry = DatabaseEntry.fromJson(array.getJSONObject(i));
|
||||
VaultEntry entry = VaultEntry.fromJson(array.getJSONObject(i));
|
||||
entries.add(entry);
|
||||
}
|
||||
} catch (Base64Exception | OtpInfoException | JSONException e) {
|
||||
throw new DatabaseException(e);
|
||||
throw new VaultException(e);
|
||||
}
|
||||
|
||||
return db;
|
||||
return vault;
|
||||
}
|
||||
|
||||
public UUIDMap<DatabaseEntry> getEntries() {
|
||||
public UUIDMap<VaultEntry> getEntries() {
|
||||
return _entries;
|
||||
}
|
||||
}
|
||||
+36
-12
@@ -1,10 +1,11 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base64;
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
|
||||
import org.json.JSONException;
|
||||
@@ -14,30 +15,30 @@ import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DatabaseEntry extends UUIDMap.Value {
|
||||
public class VaultEntry extends UUIDMap.Value {
|
||||
private String _name = "";
|
||||
private String _issuer = "";
|
||||
private String _group;
|
||||
private OtpInfo _info;
|
||||
private byte[] _icon;
|
||||
|
||||
private DatabaseEntry(UUID uuid, OtpInfo info) {
|
||||
private VaultEntry(UUID uuid, OtpInfo info) {
|
||||
super(uuid);
|
||||
_info = info;
|
||||
}
|
||||
|
||||
public DatabaseEntry(OtpInfo info) {
|
||||
public VaultEntry(OtpInfo info) {
|
||||
super();
|
||||
_info = info;
|
||||
}
|
||||
|
||||
public DatabaseEntry(OtpInfo info, String name, String issuer) {
|
||||
public VaultEntry(OtpInfo info, String name, String issuer) {
|
||||
this(info);
|
||||
setName(name);
|
||||
setIssuer(issuer);
|
||||
}
|
||||
|
||||
public DatabaseEntry(GoogleAuthInfo info) {
|
||||
public VaultEntry(GoogleAuthInfo info) {
|
||||
this(info.getOtpInfo(), info.getAccountName(), info.getIssuer());
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ public class DatabaseEntry extends UUIDMap.Value {
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static DatabaseEntry fromJson(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
|
||||
public static VaultEntry fromJson(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
|
||||
// if there is no uuid, generate a new one
|
||||
UUID uuid;
|
||||
if (!obj.has("uuid")) {
|
||||
@@ -69,7 +70,7 @@ public class DatabaseEntry extends UUIDMap.Value {
|
||||
}
|
||||
|
||||
OtpInfo info = OtpInfo.fromJson(obj.getString("type"), obj.getJSONObject("info"));
|
||||
DatabaseEntry entry = new DatabaseEntry(uuid, info);
|
||||
VaultEntry entry = new VaultEntry(uuid, info);
|
||||
entry.setName(obj.getString("name"));
|
||||
entry.setIssuer(obj.getString("issuer"));
|
||||
entry.setGroup(obj.optString("group", null));
|
||||
@@ -128,16 +129,39 @@ public class DatabaseEntry extends UUIDMap.Value {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof DatabaseEntry)) {
|
||||
if (!(o instanceof VaultEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DatabaseEntry entry = (DatabaseEntry) o;
|
||||
return super.equals(entry)
|
||||
&& getName().equals(entry.getName())
|
||||
VaultEntry entry = (VaultEntry) o;
|
||||
return super.equals(entry) && equivalates(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether this entry is equivalent to the given entry. The UUIDs of these
|
||||
* entries are ignored during the comparison, so they are not necessarily the same
|
||||
* instance.
|
||||
*/
|
||||
public boolean equivalates(VaultEntry entry) {
|
||||
return getName().equals(entry.getName())
|
||||
&& getIssuer().equals(entry.getIssuer())
|
||||
&& Objects.equals(getGroup(), entry.getGroup())
|
||||
&& getInfo().equals(entry.getInfo())
|
||||
&& Arrays.equals(getIcon(), entry.getIcon());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether this entry has its values set to the defaults.
|
||||
*/
|
||||
public boolean isDefault() {
|
||||
return equivalates(getDefault());
|
||||
}
|
||||
|
||||
public static VaultEntry getDefault() {
|
||||
try {
|
||||
return new VaultEntry(new TotpInfo(null));
|
||||
} catch (OtpInfoException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
public class VaultException extends Exception {
|
||||
public VaultException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public VaultException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
+22
-22
@@ -1,10 +1,10 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptResult;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKeyException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotListException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotListException;
|
||||
import com.beemdevelopment.aegis.encoding.Base64;
|
||||
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||
import com.beemdevelopment.aegis.encoding.HexException;
|
||||
@@ -14,17 +14,17 @@ import org.json.JSONObject;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class DatabaseFile {
|
||||
public class VaultFile {
|
||||
public static final byte VERSION = 1;
|
||||
|
||||
private Object _content;
|
||||
private Header _header;
|
||||
|
||||
public DatabaseFile() {
|
||||
public VaultFile() {
|
||||
|
||||
}
|
||||
|
||||
private DatabaseFile(Object content, Header header) {
|
||||
private VaultFile(Object content, Header header) {
|
||||
_content = content;
|
||||
_header = header;
|
||||
}
|
||||
@@ -60,29 +60,29 @@ public class DatabaseFile {
|
||||
}
|
||||
}
|
||||
|
||||
public static DatabaseFile fromJson(JSONObject obj) throws DatabaseFileException {
|
||||
public static VaultFile fromJson(JSONObject obj) throws VaultFileException {
|
||||
try {
|
||||
if (obj.getInt("version") > VERSION) {
|
||||
throw new DatabaseFileException("unsupported version");
|
||||
throw new VaultFileException("unsupported version");
|
||||
}
|
||||
|
||||
Header header = Header.fromJson(obj.getJSONObject("header"));
|
||||
if (!header.isEmpty()) {
|
||||
return new DatabaseFile(obj.getString("db"), header);
|
||||
return new VaultFile(obj.getString("db"), header);
|
||||
}
|
||||
|
||||
return new DatabaseFile(obj.getJSONObject("db"), header);
|
||||
return new VaultFile(obj.getJSONObject("db"), header);
|
||||
} catch (JSONException e) {
|
||||
throw new DatabaseFileException(e);
|
||||
throw new VaultFileException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static DatabaseFile fromBytes(byte[] data) throws DatabaseFileException {
|
||||
public static VaultFile fromBytes(byte[] data) throws VaultFileException {
|
||||
try {
|
||||
JSONObject obj = new JSONObject(new String(data, StandardCharsets.UTF_8));
|
||||
return DatabaseFile.fromJson(obj);
|
||||
return VaultFile.fromJson(obj);
|
||||
} catch (JSONException e) {
|
||||
throw new DatabaseFileException(e);
|
||||
throw new VaultFileException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,13 +90,13 @@ public class DatabaseFile {
|
||||
return (JSONObject) _content;
|
||||
}
|
||||
|
||||
public JSONObject getContent(DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||
public JSONObject getContent(VaultFileCredentials creds) throws VaultFileException {
|
||||
try {
|
||||
byte[] bytes = Base64.decode((String) _content);
|
||||
CryptResult result = creds.decrypt(bytes, _header.getParams());
|
||||
return new JSONObject(new String(result.getData(), StandardCharsets.UTF_8));
|
||||
} catch (MasterKeyException | JSONException | Base64Exception e) {
|
||||
throw new DatabaseFileException(e);
|
||||
throw new VaultFileException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,16 +105,16 @@ public class DatabaseFile {
|
||||
_header = new Header(null, null);
|
||||
}
|
||||
|
||||
public void setContent(JSONObject obj, DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||
public void setContent(JSONObject obj, VaultFileCredentials creds) throws VaultFileException {
|
||||
try {
|
||||
String string = obj.toString(4);
|
||||
byte[] dbBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] vaultBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
CryptResult result = creds.encrypt(dbBytes);
|
||||
CryptResult result = creds.encrypt(vaultBytes);
|
||||
_content = Base64.encode(result.getData());
|
||||
_header = new Header(creds.getSlots(), result.getParams());
|
||||
} catch (MasterKeyException | JSONException e) {
|
||||
throw new DatabaseFileException(e);
|
||||
throw new VaultFileException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public class DatabaseFile {
|
||||
_params = params;
|
||||
}
|
||||
|
||||
public static Header fromJson(JSONObject obj) throws DatabaseFileException {
|
||||
public static Header fromJson(JSONObject obj) throws VaultFileException {
|
||||
if (obj.isNull("slots") && obj.isNull("params")) {
|
||||
return new Header(null, null);
|
||||
}
|
||||
@@ -137,7 +137,7 @@ public class DatabaseFile {
|
||||
CryptParameters params = CryptParameters.fromJson(obj.getJSONObject("params"));
|
||||
return new Header(slots, params);
|
||||
} catch (SlotListException | JSONException | HexException e) {
|
||||
throw new DatabaseFileException(e);
|
||||
throw new VaultFileException(e);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -1,23 +1,23 @@
|
||||
package com.beemdevelopment.aegis.db;
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptResult;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKeyException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class DatabaseFileCredentials implements Serializable {
|
||||
public class VaultFileCredentials implements Serializable {
|
||||
private MasterKey _key;
|
||||
private SlotList _slots;
|
||||
|
||||
public DatabaseFileCredentials() {
|
||||
public VaultFileCredentials() {
|
||||
_key = MasterKey.generate();
|
||||
_slots = new SlotList();
|
||||
}
|
||||
|
||||
public DatabaseFileCredentials(MasterKey key, SlotList slots) {
|
||||
public VaultFileCredentials(MasterKey key, SlotList slots) {
|
||||
_key = key;
|
||||
_slots = slots;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
public class VaultFileException extends Exception {
|
||||
public VaultFileException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public VaultFileException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.beemdevelopment.aegis.services.NotificationService;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.Collator;
|
||||
import java.util.Collection;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class VaultManager {
|
||||
private static final String FILENAME = "aegis.json";
|
||||
public static final String FILENAME_EXPORT = "aegis_export.json";
|
||||
public static final String FILENAME_EXPORT_PLAIN = "aegis_export_plain.json";
|
||||
|
||||
private Vault _vault;
|
||||
private VaultFile _file;
|
||||
private VaultFileCredentials _creds;
|
||||
private boolean _encrypt;
|
||||
|
||||
private Context _context;
|
||||
|
||||
public VaultManager(Context context) {
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public boolean fileExists() {
|
||||
File file = new File(_context.getFilesDir(), FILENAME);
|
||||
return file.exists() && file.isFile();
|
||||
}
|
||||
|
||||
public void load() throws VaultManagerException {
|
||||
assertState(true, false);
|
||||
|
||||
try (FileInputStream file = _context.openFileInput(FILENAME)) {
|
||||
byte[] fileBytes = new byte[(int) file.getChannel().size()];
|
||||
DataInputStream stream = new DataInputStream(file);
|
||||
stream.readFully(fileBytes);
|
||||
stream.close();
|
||||
|
||||
_file = VaultFile.fromBytes(fileBytes);
|
||||
_encrypt = _file.isEncrypted();
|
||||
if (!isEncryptionEnabled()) {
|
||||
JSONObject obj = _file.getContent();
|
||||
_vault = Vault.fromJson(obj);
|
||||
}
|
||||
} catch (IOException | VaultFileException | VaultException e) {
|
||||
throw new VaultManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void lock() {
|
||||
assertState(false, true);
|
||||
_creds = null;
|
||||
_vault = null;
|
||||
}
|
||||
|
||||
public void unlock(VaultFileCredentials creds) throws VaultManagerException {
|
||||
assertState(true, true);
|
||||
|
||||
try {
|
||||
JSONObject obj = _file.getContent(creds);
|
||||
_vault = Vault.fromJson(obj);
|
||||
_creds = creds;
|
||||
_context.startService(new Intent(_context, NotificationService.class));
|
||||
} catch (VaultFileException | VaultException e) {
|
||||
throw new VaultManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void save(Context context, VaultFile file) throws VaultManagerException {
|
||||
byte[] bytes = file.toBytes();
|
||||
try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
|
||||
stream.write(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new VaultManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() throws VaultManagerException {
|
||||
assertState(false, true);
|
||||
|
||||
try {
|
||||
JSONObject obj = _vault.toJson();
|
||||
if (isEncryptionEnabled()) {
|
||||
_file.setContent(obj, _creds);
|
||||
} else {
|
||||
_file.setContent(obj);
|
||||
}
|
||||
save(_context, _file);
|
||||
} catch (VaultFileException e) {
|
||||
throw new VaultManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void export(OutputStream stream, boolean encrypt) throws VaultManagerException {
|
||||
assertState(false, true);
|
||||
|
||||
try {
|
||||
VaultFile vaultFile = new VaultFile();
|
||||
if (encrypt && isEncryptionEnabled()) {
|
||||
vaultFile.setContent(_vault.toJson(), _creds);
|
||||
} else {
|
||||
vaultFile.setContent(_vault.toJson());
|
||||
}
|
||||
|
||||
byte[] bytes = vaultFile.toBytes();
|
||||
stream.write(bytes);
|
||||
} catch (IOException | VaultFileException e) {
|
||||
throw new VaultManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void addEntry(VaultEntry entry) {
|
||||
assertState(false, true);
|
||||
_vault.getEntries().add(entry);
|
||||
}
|
||||
|
||||
public VaultEntry removeEntry(VaultEntry entry) {
|
||||
assertState(false, true);
|
||||
return _vault.getEntries().remove(entry);
|
||||
}
|
||||
|
||||
public VaultEntry replaceEntry(VaultEntry entry) {
|
||||
assertState(false, true);
|
||||
return _vault.getEntries().replace(entry);
|
||||
}
|
||||
|
||||
public void swapEntries(VaultEntry entry1, VaultEntry entry2) {
|
||||
assertState(false, true);
|
||||
_vault.getEntries().swap(entry1, entry2);
|
||||
}
|
||||
|
||||
public boolean isEntryDuplicate(VaultEntry entry) {
|
||||
assertState(false, true);
|
||||
return _vault.getEntries().has(entry);
|
||||
}
|
||||
|
||||
public Collection<VaultEntry> getEntries() {
|
||||
assertState(false, true);
|
||||
return _vault.getEntries().getValues();
|
||||
}
|
||||
|
||||
public TreeSet<String> getGroups() {
|
||||
assertState(false, true);
|
||||
|
||||
TreeSet<String> groups = new TreeSet<>(Collator.getInstance());
|
||||
for (VaultEntry entry : getEntries()) {
|
||||
String group = entry.getGroup();
|
||||
if (group != null) {
|
||||
groups.add(group);
|
||||
}
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
public VaultFileCredentials getCredentials() {
|
||||
assertState(false, true);
|
||||
return _creds;
|
||||
}
|
||||
|
||||
public void setCredentials(VaultFileCredentials creds) {
|
||||
assertState(false, true);
|
||||
_creds = creds;
|
||||
}
|
||||
|
||||
public VaultFile.Header getFileHeader() {
|
||||
assertLoaded(true);
|
||||
return _file.getHeader();
|
||||
}
|
||||
|
||||
public boolean isEncryptionEnabled() {
|
||||
assertLoaded(true);
|
||||
return _encrypt;
|
||||
}
|
||||
|
||||
public void enableEncryption(VaultFileCredentials creds) throws VaultManagerException {
|
||||
assertState(false, true);
|
||||
_creds = creds;
|
||||
_encrypt = true;
|
||||
save();
|
||||
}
|
||||
|
||||
public void disableEncryption() throws VaultManagerException {
|
||||
assertState(false, true);
|
||||
_creds = null;
|
||||
_encrypt = false;
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean isLoaded() {
|
||||
return _file != null;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return _vault == null;
|
||||
}
|
||||
|
||||
private void assertState(boolean locked, boolean loaded) {
|
||||
assertLoaded(loaded);
|
||||
|
||||
if (isLocked() && !locked) {
|
||||
throw new AssertionError("vault file has not been unlocked yet");
|
||||
} else if (!isLocked() && locked) {
|
||||
throw new AssertionError("vault file has already been unlocked");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertLoaded(boolean loaded) {
|
||||
if (isLoaded() && !loaded) {
|
||||
throw new AssertionError("vault file has already been loaded");
|
||||
} else if (!isLoaded() && loaded) {
|
||||
throw new AssertionError("vault file has not been loaded yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.beemdevelopment.aegis.vault;
|
||||
|
||||
public class VaultManagerException extends Exception {
|
||||
public VaultManagerException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BiometricSlot extends RawSlot {
|
||||
public BiometricSlot() {
|
||||
super();
|
||||
}
|
||||
|
||||
BiometricSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
|
||||
super(uuid, key, keyParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return TYPE_BIOMETRIC;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptParameters;
|
||||
import com.beemdevelopment.aegis.crypto.CryptResult;
|
||||
@@ -28,7 +28,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
public abstract class Slot extends UUIDMap.Value {
|
||||
public final static byte TYPE_RAW = 0x00;
|
||||
public final static byte TYPE_DERIVED = 0x01;
|
||||
public final static byte TYPE_FINGERPRINT = 0x02;
|
||||
public final static byte TYPE_BIOMETRIC = 0x02;
|
||||
|
||||
private byte[] _encryptedMasterKey;
|
||||
private CryptParameters _encryptedMasterKeyParams;
|
||||
@@ -138,8 +138,8 @@ public abstract class Slot extends UUIDMap.Value {
|
||||
boolean repaired = obj.optBoolean("repaired", false);
|
||||
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired);
|
||||
break;
|
||||
case Slot.TYPE_FINGERPRINT:
|
||||
slot = new FingerprintSlot(uuid, key, keyParams);
|
||||
case Slot.TYPE_BIOMETRIC:
|
||||
slot = new BiometricSlot(uuid, key, keyParams);
|
||||
break;
|
||||
default:
|
||||
throw new SlotException("unrecognized slot type");
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
public class SlotException extends Exception {
|
||||
public SlotException(Throwable cause) {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
public class SlotIntegrityException extends Exception {
|
||||
public SlotIntegrityException() {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.beemdevelopment.aegis.db.slots;
|
||||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
public class SlotListException extends Exception {
|
||||
public SlotListException(Throwable cause) {
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
+1
-1
@@ -4,5 +4,5 @@
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" />
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" />
|
||||
</vector>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB |
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:fitsSystemWindows="true"
|
||||
android:background="?attr/background"
|
||||
tools:context="com.beemdevelopment.aegis.ui.AboutActivity">
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:isScrollContainer="true">
|
||||
|
||||
<include layout="@layout/content_about" />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -49,40 +49,29 @@
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_decrypt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="125dp"
|
||||
android:text="@string/unlock" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/box_fingerprint"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/img_fingerprint_insert"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:orientation="horizontal"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_fingerprint"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:text="@string/fingerprint_hint"
|
||||
android:textColor="?attr/secondaryText"/>
|
||||
android:layout_height="wrap_content">
|
||||
<Button
|
||||
android:id="@+id/button_decrypt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/box_fingerprint_info"
|
||||
android:id="@+id/box_biometric_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
@@ -98,7 +87,7 @@
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/invalidated_fingerprint"/>
|
||||
android:text="@string/invalidated_biometrics"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_add_fingerprint"
|
||||
android:id="@+id/button_add_biometric"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
@@ -75,7 +75,7 @@
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/add_fingerprint"
|
||||
android:text="@string/add_biometric"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?android:attr/textColorSecondary"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardUseCompatPadding="true"
|
||||
android:background="?attr/cardBackground"
|
||||
android:layout_marginBottom="8dp"
|
||||
tools:ignore="MissingPrefix">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:background="?attr/cardBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="72dp"
|
||||
android:orientation="horizontal"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:text="@string/app_name_full"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-info-outline"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_app_version"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/version"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_version"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0.0.0 (0)"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
|
||||
tools:ignore="HardcodedText" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_changelog"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-time-restore"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/changelog"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/whats_new"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_github"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-github"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/github"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/github_description"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_licenses"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-file-text"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/licenses"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/licenses_description"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardUseCompatPadding="true"
|
||||
android:background="?attr/cardBackground"
|
||||
android:layout_marginBottom="8dp"
|
||||
tools:ignore="MissingPrefix">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:background="?attr/cardBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:text="@string/org_name_full"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_alexander"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/list_item_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-account"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/author_alex"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/country_netherlands"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_michael"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/list_item_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-account"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/author_michael"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/country_netherlands"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_email"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/list_item_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-email"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/email_us"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_website"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/list_item_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-globe"
|
||||
app:ico_size="16dp" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/visit_website"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardUseCompatPadding="true"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?attr/cardBackground"
|
||||
tools:ignore="MissingPrefix">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:background="?attr/cardBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:text="@string/about_support"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/btn_rate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/list_item_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
app:ico_color="@color/icon_about"
|
||||
app:ico_icon="gmi-star"
|
||||
app:ico_size="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginLeft="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/support_rate"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/support_rate_description"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="20dp">
|
||||
<com.mattprecious.swirl.SwirlView
|
||||
android:id="@+id/img_fingerprint"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"/>
|
||||
<TextView
|
||||
android:id="@+id/text_fingerprint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="15dp"
|
||||
android:text="@string/fingerprint_hint"
|
||||
android:textColor="?attr/secondaryText"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
</LinearLayout>
|
||||
@@ -4,8 +4,17 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="20dp">
|
||||
android:paddingTop="10dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/authentication_method_password_explanation"
|
||||
android:textColor="#FF0000"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"/>
|
||||
<EditText
|
||||
android:id="@+id/text_password"
|
||||
android:hint="@string/password"
|
||||
@@ -18,4 +27,11 @@
|
||||
android:inputType="textPassword"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_toggle_visibility"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:text="@string/show_password" />
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/web_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -14,11 +14,19 @@
|
||||
android:textColor="@color/primary_text_inverted"
|
||||
android:id="@+id/textView2" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/authentication_method_password_explanation"
|
||||
android:textColor="#FFFF00"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="24dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp">
|
||||
android:layout_marginTop="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
@@ -59,44 +67,12 @@
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/box_fingerprint"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:visibility="invisible">
|
||||
|
||||
<TextView
|
||||
android:text="@string/authentication_method_fingerprint"
|
||||
<CheckBox
|
||||
android:id="@+id/check_toggle_visibility"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="24sp"
|
||||
android:textColor="@color/primary_text_inverted"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/img_fingerprint_insert"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:orientation="horizontal"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_fingerprint"
|
||||
android:textColor="@color/primary_text_inverted"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:text="@string/fingerprint_hint"/>
|
||||
</LinearLayout>
|
||||
android:layout_marginTop="5dp"
|
||||
android:text="@string/show_password" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -20,13 +20,19 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
android:layout_marginTop="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/authentication_method_explanation"/>
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/rg_authenticationMethod"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_none"
|
||||
@@ -60,22 +66,22 @@
|
||||
android:textColor="@color/secondary_text_inverted" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rb_fingerprint"
|
||||
android:id="@+id/rb_biometrics"
|
||||
android:enabled="false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/authentication_method_fingerprint"
|
||||
android:text="@string/authentication_method_biometrics"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_rb_fingerprint"
|
||||
android:id="@+id/text_rb_biometrics"
|
||||
android:enabled="false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="-5dp"
|
||||
|
||||
android:text="@string/authentication_method_fingerprint_description"
|
||||
android:text="@string/authentication_method_biometrics_description"
|
||||
android:textColor="@color/disabled_textview_colors" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
@@ -22,4 +22,47 @@
|
||||
android:id="@+id/rvKeyProfiles"
|
||||
android:layoutAnimation="@anim/layout_animation_fall_down"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center|fill_vertical"
|
||||
android:id="@+id/vEmptyList"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="150dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:src="@drawable/ic_qrcode_scan"
|
||||
android:tint="?attr/primaryText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/empty_list_title"
|
||||
android:paddingTop="17dp"
|
||||
android:textColor="?attr/primaryText"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView5"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:lineSpacingExtra="5dp"
|
||||
android:paddingTop="7dp"
|
||||
android:text="@string/empty_list"
|
||||
android:textAlignment="center" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/cardBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="5dp"
|
||||
android:text="@string/password_reminder"/>
|
||||
</LinearLayout>
|
||||
@@ -1,6 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_copy"
|
||||
android:orderInCategory="80"
|
||||
android:icon="@drawable/ic_content_copy_white_24dp"
|
||||
android:title="@string/copy"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_edit"
|
||||
android:orderInCategory="90"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
android:id="@+id/mi_search"
|
||||
android:title="@string/search"
|
||||
app:showAsAction="always"
|
||||
app:actionViewClass="android.widget.SearchView"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
/>
|
||||
<item
|
||||
android:id="@+id/action_lock"
|
||||
@@ -63,4 +63,9 @@
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_settings"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_about"
|
||||
android:orderInCategory="110"
|
||||
android:title="@string/action_about"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
License for everything not in third_party and not otherwise marked:
|
||||
|
||||
Copyright 2014 Google, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those of the
|
||||
authors and should not be interpreted as representing official policies, either expressed
|
||||
or implied, of Google, Inc.
|
||||
---------------------------------------------------------------------------------------------
|
||||
License for third_party/disklrucache:
|
||||
|
||||
Copyright 2012 Jake Wharton
|
||||
Copyright 2011 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
---------------------------------------------------------------------------------------------
|
||||
License for third_party/gif_decoder:
|
||||
|
||||
Copyright (c) 2013 Xcellent Creations, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
---------------------------------------------------------------------------------------------
|
||||
License for third_party/gif_encoder/AnimatedGifEncoder.java and
|
||||
third_party/gif_encoder/LZWEncoder.java:
|
||||
|
||||
No copyright asserted on the source code of this class. May be used for any
|
||||
purpose, however, refer to the Unisys LZW patent for restrictions on use of
|
||||
the associated LZWEncoder class. Please forward any corrections to
|
||||
kweiner@fmsware.com.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
License for third_party/gif_encoder/NeuQuant.java
|
||||
|
||||
Copyright (c) 1994 Anthony Dekker
|
||||
|
||||
NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
|
||||
"Kohonen neural networks for optimal colour quantization" in "Network:
|
||||
Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of
|
||||
the algorithm.
|
||||
|
||||
Any party obtaining a copy of these files from the author, directly or
|
||||
indirectly, is granted, free of charge, a full and unrestricted irrevocable,
|
||||
world-wide, paid up, royalty-free, nonexclusive right and license to deal in
|
||||
this software and documentation files (the "Software"), including without
|
||||
limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
sublicense, and/or sell copies of the Software, and to permit persons who
|
||||
receive copies from any such party to do so, with the only requirement being
|
||||
that this copyright notice remain intact.
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<notices>
|
||||
<notice>
|
||||
<name>TextDrawable</name>
|
||||
<url>https://github.com/amulyakhare/TextDrawable</url>
|
||||
<copyright>Copyright (C) 2014 Amulya Khare</copyright>
|
||||
<license>MIT License</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Preferencex</name>
|
||||
<url>https://github.com/takisoft/preferencex-android</url>
|
||||
<copyright />
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>FloatingActionButton</name>
|
||||
<url>https://github.com/futuresimple/android-floating-action-button</url>
|
||||
<copyright>Copyright 2014 Jerzy Chalupski</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>AppIntro</name>
|
||||
<url>https://github.com/AppIntro/AppIntro</url>
|
||||
<copyright/>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Krop</name>
|
||||
<url>https://github.com/avito-tech/krop</url>
|
||||
<copyright>Copyright (c) 2017 Avito Technology</copyright>
|
||||
<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>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>CircleImageView</name>
|
||||
<url>https://github.com/hdodenhof/CircleImageView</url>
|
||||
<copyright>Copyright 2014 - 2019 Henning Dodenhof</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Barcodescanner</name>
|
||||
<url>https://github.com/dm77/barcodescanner</url>
|
||||
<copyright>Copyright (c) 2014 Dushyanth Maguluru</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>libsu</name>
|
||||
<url>https://github.com/topjohnwu/libsu</url>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Glide</name>
|
||||
<url>https://github.com/bumptech/glide</url>
|
||||
<license>Glide License</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Iconics</name>
|
||||
<url>https://github.com/mikepenz/Android-Iconics</url>
|
||||
<copyright/>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Android Support Libraries</name>
|
||||
<url>http://developer.android.com/tools/support-library/index.html</url>
|
||||
<copyright>Copyright (C) 2016 The Android Open Source Project</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
</notices>
|
||||
@@ -0,0 +1,193 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="action_settings">Nastavení</string>
|
||||
<string name="action_about">O aplikaci</string>
|
||||
<string name="action_import">Importovat</string>
|
||||
<string name="action_delete">Odstranit</string>
|
||||
<string name="action_default_icon">Obnovit původní ikonu</string>
|
||||
<string name="discard">Zahodit</string>
|
||||
<string name="save">Uložit</string>
|
||||
<string name="issuer">Poskytovatel</string>
|
||||
<string name="settings">Předvolby</string>
|
||||
<string name="pref_appearance_group_title">Vzhled</string>
|
||||
<string name="pref_general_group_title">Obecné</string>
|
||||
<string name="pref_security_group_title">Zabezpečení</string>
|
||||
<string name="pref_tools_group_title">Nástroje</string>
|
||||
<string name="pref_select_theme_title">Vzhled</string>
|
||||
<string name="pref_view_mode_title">Režim zobrazení</string>
|
||||
<string name="pref_lang_title">Jazyk</string>
|
||||
<string name="pref_account_name_title">Zobrazit název účtu</string>
|
||||
<string name="pref_account_name_summary">Zobrazit název účtu vedle poskytovatele</string>
|
||||
<string name="pref_timeout_title">Časový limit</string>
|
||||
<string name="pref_timeout_summary">Automaticky zamknout trezor po %1$s sekundách nečinnosti</string>
|
||||
<string name="pref_slots_title">Sloty klíčů</string>
|
||||
<string name="pref_slots_summary">Správa seznamu klíčů, které mohou dešifrovat trezor</string>
|
||||
<string name="pref_import_file_title">Importovat ze souboru</string>
|
||||
<string name="pref_import_file_summary">Importovat tokeny ze souboru</string>
|
||||
<string name="pref_import_app_title">Importovat z aplikace</string>
|
||||
<string name="pref_import_app_summary">Importovat tokeny z aplikace (vyžaduje root přístup)</string>
|
||||
<string name="pref_export_title">Exportovat</string>
|
||||
<string name="pref_export_summary">Exportovat trezor</string>
|
||||
<string name="pref_secure_screen_title">Zabezpečení obrazovky</string>
|
||||
<string name="pref_secure_screen_summary">Blokovat snímky obrazovky a další pokusy o zachycení obrazovky v rámci aplikace</string>
|
||||
<string name="pref_tap_to_reveal_title">Zobrazit klepnutím</string>
|
||||
<string name="pref_tap_to_reveal_summary">Ve výchozím stavu budou kódy skryté. Kód zobrazíte klepnutím na token.</string>
|
||||
<string name="pref_tap_to_reveal_time_title">Prodleva zobrazení kódu</string>
|
||||
<string name="pref_auto_lock_title">Automatické uzamknutí</string>
|
||||
<string name="pref_auto_lock_summary">Automaticky uzamknout trezor při zavření aplikace, nebo uzamčení zařízení.</string>
|
||||
<string name="pref_encryption_title">Šifrování</string>
|
||||
<string name="pref_encryption_summary">Šifrovat trezor a odemykat jej pomocí hesla, nebo biometrie</string>
|
||||
<string name="pref_biometrics_title">Biometrické odemknutí</string>
|
||||
<string name="pref_biometrics_summary">Povolit biometrické ověření pro odemčení trezoru</string>
|
||||
<string name="pref_set_password_title">Změnit heslo</string>
|
||||
<string name="pref_set_password_summary">Nastavení nového hesla potřebného pro odemčení trezoru</string>
|
||||
<string name="choose_authentication_method">Zabezpečení</string>
|
||||
<string name="authentication_method_none">Žádné</string>
|
||||
<string name="authentication_method_password">Heslo</string>
|
||||
<string name="authentication_method_biometrics">Biometrie</string>
|
||||
<string name="authentication_method_biometrics_description">Kromě hesla půjde k odemčení trezoru použít také biometrické prvky registrované v tomto zařízení jako je otisk prstu nebo tvář.</string>
|
||||
<string name="authentication_method_set_password">Heslo</string>
|
||||
<string name="authentication_enter_password">Zadejte své heslo</string>
|
||||
<string name="authentication">Odemknout trezor</string>
|
||||
<string name="set_password">Zadejte heslo</string>
|
||||
<string name="set_group">Zadejte název skupiny</string>
|
||||
<string name="set_number">Zadejte číslo</string>
|
||||
<string name="set_password_confirm">Potvrďte heslo</string>
|
||||
<string name="invalidated_biometrics">Zjištěna změna nastavení zabezpečení zařízení. Přejděte do „Aegis“ → „Nastavení“ → „Biometrie“ a povolte biometrické odemknutí znovu.</string>
|
||||
<string name="unlock">Odemknout</string>
|
||||
<string name="biometrics">Biometrie</string>
|
||||
<string name="advanced">Pokročilé</string>
|
||||
<string name="seconds">sekund</string>
|
||||
<string name="counter">Počítadlo</string>
|
||||
<string name="digits">Číslice</string>
|
||||
<string name="secret">Tajný klíč</string>
|
||||
<string name="scan">Naskenovat QR kód</string>
|
||||
<string name="scan_image">Skenovat obrázek</string>
|
||||
<string name="enter_manually">Zadat ručně</string>
|
||||
<string name="add_biometric">Přidat biometriku</string>
|
||||
<string name="add_biometric_slot">Přidat biometrický slot</string>
|
||||
<string name="set_up_biometric">Nastavení biometrického odemčení</string>
|
||||
<string name="add_password">Přidat heslo</string>
|
||||
<string name="slots_warning">Trezor je stejně bezpečný jako jeho nejslabší místo. Změníte-li nastavení biometrického ověření ve vašem zařízení, budete muset v Aegis znovu aktivovat biometrické odemknutí.</string>
|
||||
<string name="copy">Kopírovat</string>
|
||||
<string name="edit">Upravit</string>
|
||||
<string name="delete">Odstranit</string>
|
||||
<string name="password">Heslo</string>
|
||||
<string name="confirm_password">Potvrdit heslo</string>
|
||||
<string name="show_password">Zobrazit heslo</string>
|
||||
<string name="new_profile">Nový profil</string>
|
||||
<string name="add_new_profile">Přidat nový profil</string>
|
||||
<string name="unlock_vault_error">Trezor nelze odemknout</string>
|
||||
<string name="unlock_vault_error_description">Nesprávné heslo. Ujistěte se, že je zadané správně.</string>
|
||||
<string name="password_equality_error">Hesla musí být stejná a nesmí být prázdná</string>
|
||||
<string name="snackbar_authentication_method">Zvolte prosím způsob ověření</string>
|
||||
<string name="encrypting_vault">Šifrování trezoru</string>
|
||||
<string name="delete_entry">Odstranit položku</string>
|
||||
<string name="delete_entry_description">Opravdu chcete odstranit tuto položku?</string>
|
||||
<string name="discard_changes">Zahodit změny?</string>
|
||||
<string name="discard_changes_description">Změny nebyly uloženy</string>
|
||||
<string name="folder">Složka</string>
|
||||
<string name="tap_to_select">Vyberte klepnutím</string>
|
||||
<string name="saving_profile_error">Chyba při ukládání profilu</string>
|
||||
<string name="welcome">Vítejte</string>
|
||||
<string name="app_description">Aegis je bezpečná, bezplatná a open source 2FA aplikace</string>
|
||||
<string name="setup_completed">Nastavení dokončeno</string>
|
||||
<string name="setup_completed_description">Aegis je nastaven a připraven k používání.</string>
|
||||
<string name="vault_not_found">Trezor nebyl nenalezen, spouštím nastavení…</string>
|
||||
<string name="code_copied">Kód zkopírován do schránky</string>
|
||||
<string name="errors_copied">Chyby zkopírovány do schránky</string>
|
||||
<string name="version_copied">Verze zkopírována do schránky</string>
|
||||
<string name="decryption_error">Při odemykání trezoru došlo k chybě</string>
|
||||
<string name="saving_error">Při pokusu o uložení trezoru došlo k chybě</string>
|
||||
<string name="disable_encryption">Vypnout šifrování</string>
|
||||
<string name="disable_encryption_description">Opravdu chcete vypnout šifrování úložiště? Obsah trezoru pak bude uložen jako prostý text.</string>
|
||||
<string name="enable_encryption_error">Při zapínání šifrování došlo k chybě</string>
|
||||
<string name="disable_encryption_error">Při vypínání šifrování došlo k chybě</string>
|
||||
<string name="permission_denied">Přístup zamítnut</string>
|
||||
<string name="andotp_new_format">Nový formát (v0.6.3 nebo novější) </string>
|
||||
<string name="andotp_old_format">Starý formát (v0.6.2 a starší) </string>
|
||||
<string name="choose_andotp_importer">Jaký formát má záložní andOTP soubor?</string>
|
||||
<string name="choose_application">Zvolte aplikaci, ze které chcete importovat údaje</string>
|
||||
<string name="choose_theme">Vyberte si vzhled rozhraní</string>
|
||||
<string name="choose_view_mode">Vyberte si režim zobrazení</string>
|
||||
<string name="parsing_file_error">Při zpracování souboru došlo k chybě</string>
|
||||
<string name="file_not_found">Chyba: Soubor nenalezen</string>
|
||||
<string name="reading_file_error">Při pokusu o čtení souboru došlo k chybě</string>
|
||||
<string name="app_lookup_error">Chyba: Aplikace není nainstalována</string>
|
||||
<string name="root_error">Chyba: Nelze získat root přístup</string>
|
||||
<string name="imported_entries_count">Naimportováno %d záznamů</string>
|
||||
<string name="read_entries_count">Čtení %d záznamů. %d chyb.</string>
|
||||
<string name="import_error_title">Při importu došlo k jedné nebo více chybám</string>
|
||||
<string name="exporting_vault_error">Při pokusu o export trezoru došlo k chybě</string>
|
||||
<string name="export_warning">Trezor bude exportován mimo soukromé úložiště aplikace Aegis.</string>
|
||||
<string name="encryption_set_password_error">Při pokusu o nastavení hesla došlo k chybě: </string>
|
||||
<string name="encryption_enable_biometrics_error">Při pokusu o zapnutí biometrického odemykání došlo k chybě</string>
|
||||
<string name="no_cameras_available">Fotoaparát není dostupný</string>
|
||||
<string name="read_qr_error">Při pokusu o přečtení QR kódu došlo k chybě</string>
|
||||
<string name="authentication_method_raw">Prosté</string>
|
||||
<string name="unlocking_vault">Odemykání trezoru</string>
|
||||
<string name="unlocking_vault_repair">Odemykání trezoru (opravuji)</string>
|
||||
<string name="password_slot_error">Je potřeba alespoň jeden slot hesla</string>
|
||||
<string name="remove_slot">Odstranit slot</string>
|
||||
<string name="remove_slot_description">Opravdu chcete tento slot odstranit?</string>
|
||||
<string name="remove_group">Odstranit skupinu</string>
|
||||
<string name="remove_group_description">Opravdu chcete odstranit tuto skupinu? Záznamy z ní budou automaticky přemístěny do „Žádná skupina“.</string>
|
||||
<string name="adding_new_slot_error">Při pokusu o přidání nového slotu došlo k chybě:</string>
|
||||
<string name="progressbar_error">Měřítko trvání animace nelze resetovat. Ukazatele průběhu nebudou viditelné.</string>
|
||||
<string name="details">Podrobnosti</string>
|
||||
<string name="filter">Filtr</string>
|
||||
<string name="filter_ungrouped">Bez skupiny</string>
|
||||
<string name="lock">Uzamknout</string>
|
||||
<string name="all">Vše</string>
|
||||
<string name="name">Název</string>
|
||||
<string name="no_group">Žádná skupina</string>
|
||||
<string name="sort_alphabetically">Poskytovatel (A → Z)</string>
|
||||
<string name="sort_alphabetically_reverse">Poskytovatel (Z → A)</string>
|
||||
<string name="sort_alphabetically_name">Účet (A → Z)</string>
|
||||
<string name="sort_alphabetically_name_reverse">Účet (Z → A)</string>
|
||||
<string name="sort_custom">Vlastní</string>
|
||||
<string name="new_group">Nová skupina…</string>
|
||||
<string name="enter_group_name">Zadejte název skupiny</string>
|
||||
<string name="group_name_hint">Název skupiny</string>
|
||||
<string name="preference_manage_groups">Upravit skupiny</string>
|
||||
<string name="preference_manage_groups_summary">Správa a odstranění skupin</string>
|
||||
<string name="pref_search_name_title">Hledat v názvech účtů</string>
|
||||
<string name="pref_search_name_summary">Zahrnout shody názvů účtů do výsledků hledání</string>
|
||||
<string name="pref_highlight_entry_title">Zvýraznit tokeny po klepnutí</string>
|
||||
<string name="pref_highlight_entry_summary">Usnadnit vzájemné rozlišení tokenů dočasným zvýrazněním po kleputí</string>
|
||||
<string name="tap_to_reveal">– skryto –</string>
|
||||
<string name="selected">Vybráno</string>
|
||||
<string name="dark_theme_title">Tmavý vzhled</string>
|
||||
<string name="light_theme_title">Světlý vzhled</string>
|
||||
<string name="amoled_theme_title">AMOLED vzhled</string>
|
||||
<string name="normal_viewmode_title">Normální</string>
|
||||
<string name="compact_mode_title">Kompaktní</string>
|
||||
<string name="small_mode_title">Malý</string>
|
||||
<string name="unknown_issuer">Neznámý poskytovatel</string>
|
||||
<string name="unknown_account_name">Neznámý název účtu</string>
|
||||
<string name="import_error_dialog">Nepovedlo se naimportovat %d tokenů a byly přeskočeny. Klepněte na „podrobnosti“ pro více informací o chybách.</string>
|
||||
<string name="unable_to_read_qrcode">QR kód nelze přečíst a zpracovat</string>
|
||||
<string name="select_picture">Vyberte obrázek</string>
|
||||
<string name="toggle_checkboxes">Přepnout zaškrtávací políčka</string>
|
||||
<string name="search">Hledat</string>
|
||||
<string name="channel_name_lock_status">Stav zámku</string>
|
||||
<string name="channel_description_lock_status">Aegis může vytvořit trvalé oznámení, které vás upozorní, když je trezor uzamčen</string>
|
||||
<string name="vault_unlocked_state">Trezor je odemčený. Klepnutím uzamkněte.</string>
|
||||
<string name="title_activity_about">O aplikaci</string>
|
||||
<string name="version">Verze</string>
|
||||
<string name="changelog">Seznam změn</string>
|
||||
<string name="whats_new">Co je nového?</string>
|
||||
<string name="github_description">Zdrojový kód, problémy a informace</string>
|
||||
<string name="licenses">Licence</string>
|
||||
<string name="licenses_description">Licence knihoven použitých v Aegis</string>
|
||||
<string name="country_netherlands">Nizozemsko</string>
|
||||
<string name="email_us">Pošlete nám e-mail</string>
|
||||
<string name="visit_website">Navštivte náš web</string>
|
||||
<string name="about_support">Podpora</string>
|
||||
<string name="support_rate">Ohodnoťte aplikaci</string>
|
||||
<string name="support_rate_description">Podpořte nás napsáním recenze v obchodě Google Play</string>
|
||||
<string name="webview_error">Vaše zařízení nepodporuje WebView, které je nutné pro zobrazení seznamu změn. Tato komponenta zřejmě chybí v systému.</string>
|
||||
<string name="email">E-mail</string>
|
||||
<string name="empty_list">Žádné kódy. Začněte přidáním položek pomocí tlačítka + v pravém dolním rohu.</string>
|
||||
<string name="empty_list_title">Žádné záznamy nenalezeny</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,177 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="action_settings">Einstellungen</string>
|
||||
<string name="action_about">Über uns</string>
|
||||
<string name="action_import">Importieren</string>
|
||||
<string name="action_delete">Löschen</string>
|
||||
<string name="action_default_icon">Standard-Symbol wiederherstellen</string>
|
||||
<string name="discard">Verwerfen</string>
|
||||
<string name="save">Speichern</string>
|
||||
<string name="issuer">Herausgeber</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="pref_appearance_group_title">Aussehen</string>
|
||||
<string name="pref_general_group_title">Allgemein</string>
|
||||
<string name="pref_security_group_title">Sicherheit</string>
|
||||
<string name="pref_tools_group_title">Hilfsmittel</string>
|
||||
<string name="pref_select_theme_title">Thema</string>
|
||||
<string name="pref_view_mode_title">Ansichtsmodus</string>
|
||||
<string name="pref_lang_title">Sprache</string>
|
||||
<string name="pref_account_name_title">Anzeigen des Kontonamens</string>
|
||||
<string name="pref_account_name_summary">Aktiviere dies, um den Kontonamen neben dem Herausgeber anzuzeigen.</string>
|
||||
<string name="pref_timeout_title">Zeitüberschreitung</string>
|
||||
<string name="pref_timeout_summary">Sperrt die Datenbank automatisch nach %1$s Sekunden Inaktivität.</string>
|
||||
<string name="pref_slots_title">Schlüssel Steckplätze</string>
|
||||
<string name="pref_slots_summary">Verwalte die Liste der Schlüssel, die die Datenbank entschlüsseln können.</string>
|
||||
<string name="pref_import_file_title">Aus Datei importieren</string>
|
||||
<string name="pref_import_file_summary">Importieren einer Datenbank aus einer Datei</string>
|
||||
<string name="pref_import_app_title">Aus App importieren</string>
|
||||
<string name="pref_import_app_summary">Importieren einer Datenbank aus einer App (erfordert Root-Zugriff)</string>
|
||||
<string name="pref_export_title">Exportieren</string>
|
||||
<string name="pref_export_summary">Exportieren der Datenbank</string>
|
||||
<string name="pref_secure_screen_title">Bildschirmsicherheit</string>
|
||||
<string name="pref_secure_screen_summary">Blockiere Screenshots und andere Versuche, den Bildschirm innerhalb der App aufzunehmen.</string>
|
||||
<string name="pref_tap_to_reveal_title">Zum Aufdecken tippen</string>
|
||||
<string name="pref_tap_to_reveal_summary">Schlüssel werden standardmäßig ausgeblendet. Tippe auf die Schlüssel, um den Code anzuzeigen.</string>
|
||||
<string name="pref_tap_to_reveal_time_title">Zeitüberschreitung für Tippen zum Anzeigen</string>
|
||||
<string name="pref_auto_lock_title">Automatische Sperre</string>
|
||||
<string name="pref_auto_lock_summary">Automatische Sperre, wenn die App geschlossen oder dein Gerät gesperrt wird.</string>
|
||||
<string name="pref_encryption_title">Verschlüsselung</string>
|
||||
<string name="pref_encryption_summary">Verschlüssele die Datenbank und entsperre mit einem Passwort oder Fingerabdruck.</string>
|
||||
<string name="pref_set_password_title">Passwort ändern</string>
|
||||
<string name="pref_set_password_summary">Legen ein neues Passwort fest, das erforderlich ist, um deinen Tresor freizuschalten.</string>
|
||||
<string name="choose_authentication_method">Sicherheit</string>
|
||||
<string name="authentication_method_none">Keine</string>
|
||||
<string name="authentication_method_none_description">Du brauchst kein Passwort, um den Tresor freizuschalten, und es wird nicht verschlüsselt. Diese Option wird nicht empfohlen.</string>
|
||||
<string name="authentication_method_password">Passwort</string>
|
||||
<string name="authentication_method_password_description">Du benötigst ein Passwort, um den Tresor freizuschalten.</string>
|
||||
<string name="authentication_method_set_password">Passwort</string>
|
||||
<string name="authentication_enter_password">Gib dein Passwort ein</string>
|
||||
<string name="authentication">Entsperre den Tresor.</string>
|
||||
<string name="set_password">Bitte gib ein Passwort ein</string>
|
||||
<string name="set_group">Bitte gib einen Gruppennamen ein</string>
|
||||
<string name="set_number">Bitte gib eine Zahl ein</string>
|
||||
<string name="set_password_confirm">Bitte bestätige das Passwort</string>
|
||||
<string name="unlock">Entsperren</string>
|
||||
<string name="advanced">Fortgeschritten</string>
|
||||
<string name="seconds">Sekunden</string>
|
||||
<string name="counter">Zähler</string>
|
||||
<string name="digits">Ziffern</string>
|
||||
<string name="secret">Schlüssel</string>
|
||||
<string name="scan">QR-Code scannen</string>
|
||||
<string name="scan_image">Bild scannen</string>
|
||||
<string name="enter_manually">Manuell eingeben</string>
|
||||
<string name="add_password">Passwort hinzufügen</string>
|
||||
<string name="slots_warning">Der Tresor ist nur so sicher wie dein schwächster Schlüssel. Wenn ein neuer Fingerabdruck zu deinem Gerät hinzugefügt wird, musst du die Fingerabdruck-Authentifizierung in Aegis reaktivieren.</string>
|
||||
<string name="copy">Kopieren</string>
|
||||
<string name="edit">Bearbeiten</string>
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="password">Passwort</string>
|
||||
<string name="confirm_password">Passwort bestätigen</string>
|
||||
<string name="new_profile">Neues Profil</string>
|
||||
<string name="add_new_profile">Neues Profil hinzufügen</string>
|
||||
<string name="unlock_vault_error">Der Tresor konnte nicht entsperrt werden.</string>
|
||||
<string name="unlock_vault_error_description">Falsches Passwort. Achte darauf, dass du dein Passwort nicht falsch eingibst.</string>
|
||||
<string name="password_equality_error">Passwörter sollten identisch und nicht leer sein.</string>
|
||||
<string name="snackbar_authentication_method">Bitte wähle eine Authentifizierungsmethode aus</string>
|
||||
<string name="encrypting_vault">Verschlüsseln des Tresors</string>
|
||||
<string name="delete_entry">Eintrag löschen</string>
|
||||
<string name="delete_entry_description">Bist du sicher, dass du diesen Eintrag löschen möchtest?</string>
|
||||
<string name="discard_changes">Änderungen verwerfen?</string>
|
||||
<string name="discard_changes_description">Deine Änderungen wurden nicht gespeichert.</string>
|
||||
<string name="folder">Ordner</string>
|
||||
<string name="tap_to_select">Zum Auswählen tippen</string>
|
||||
<string name="saving_profile_error">Fehler beim Speichern des Profils</string>
|
||||
<string name="welcome">Willkommen</string>
|
||||
<string name="app_description">Aegis ist eine kostenlose, sichere und Open Source 2FA App.</string>
|
||||
<string name="setup_completed">Einrichten abgeschlossen</string>
|
||||
<string name="setup_completed_description">Aegis ist eingerichtet und einsatzbereit.</string>
|
||||
<string name="vault_not_found">Tresor nicht gefunden, Starten der Einrichtung…</string>
|
||||
<string name="code_copied">Code in die Zwischenablage kopiert</string>
|
||||
<string name="errors_copied">Fehlermeldungen in die Zwischenablage kopiert</string>
|
||||
<string name="version_copied">Version in die Zwischenablage kopiert</string>
|
||||
<string name="decryption_error">Beim Versuch, den Tresor zu entsperren, ist ein Fehler aufgetreten.</string>
|
||||
<string name="saving_error">Beim Versuch, den Tresor zu speichern, ist ein Fehler aufgetreten.</string>
|
||||
<string name="disable_encryption">Verschlüsselung deaktivieren</string>
|
||||
<string name="disable_encryption_description">Bist du sicher, dass du die Verschlüsselung deaktivieren willst? Dadurch wird der Tresor im Klartext gespeichert.</string>
|
||||
<string name="enable_encryption_error">Bei der Aktivierung der Verschlüsselung ist ein Fehler aufgetreten.</string>
|
||||
<string name="disable_encryption_error">Bei der Deaktivierung der Verschlüsselung ist ein Fehler aufgetreten.</string>
|
||||
<string name="permission_denied">Zugriff verweigert</string>
|
||||
<string name="choose_application">Wähle die Anwendung aus, aus der du eine Datenbank importieren möchtest.</string>
|
||||
<string name="choose_theme">Wähle das gewünschte Thema aus</string>
|
||||
<string name="choose_view_mode">Wähle den gewünschten Ansichtsmodus aus</string>
|
||||
<string name="parsing_file_error">Beim Versuch, die Datei zu analysieren, ist ein Fehler aufgetreten.</string>
|
||||
<string name="file_not_found">Fehler: Datei nicht gefunden</string>
|
||||
<string name="reading_file_error">Beim Versuch, die Datei zu lesen, ist ein Fehler aufgetreten.</string>
|
||||
<string name="app_lookup_error">Fehler: App ist nicht installiert</string>
|
||||
<string name="root_error">Fehler: Es konnte kein Root-Zugriff gewährt werden.</string>
|
||||
<string name="imported_entries_count">Importierte %d Einträge</string>
|
||||
<string name="read_entries_count">%d Einträge gelesen. %d Fehler.</string>
|
||||
<string name="import_error_title">Beim Import sind ein oder mehrere Fehler aufgetreten.</string>
|
||||
<string name="export_warning">Diese Aktion exportiert die Datenbank aus dem privaten Speicher von Aegis.</string>
|
||||
<string name="encryption_set_password_error">Beim Versuch, das Passwort festzulegen, ist ein Fehler aufgetreten: </string>
|
||||
<string name="no_cameras_available">Keine Kameras verfügbar</string>
|
||||
<string name="read_qr_error">Beim Versuch, den QR-Code zu lesen, ist ein Fehler aufgetreten.</string>
|
||||
<string name="authentication_method_raw">Raw</string>
|
||||
<string name="unlocking_vault">Entsperren des Tresors</string>
|
||||
<string name="unlocking_vault_repair">Entsperren des Tresors (Reparieren)</string>
|
||||
<string name="password_slot_error">Du musst mindestens einen Passwort-Slot haben.</string>
|
||||
<string name="remove_slot">Slot entfernen</string>
|
||||
<string name="remove_slot_description">Bist du sicher, dass du diesen Slot entfernen willst?</string>
|
||||
<string name="remove_group">Gruppe entfernen</string>
|
||||
<string name="remove_group_description">Bist du sicher, dass du diese Gruppe entfernen möchtest? Einträge in dieser Gruppe wechseln automatisch zu \'Keine Gruppe\'.</string>
|
||||
<string name="adding_new_slot_error">Beim Versuch, einen neuen Slot hinzuzufügen, ist ein Fehler aufgetreten:</string>
|
||||
<string name="progressbar_error">Die Skala der Animationsdauer kann nicht zurückgesetzt werden. Fortschrittsbalken werden unsichtbar sein.</string>
|
||||
<string name="details">Details</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="lock">Sperren</string>
|
||||
<string name="all">Alle</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="no_group">Keine Gruppe</string>
|
||||
<string name="sort_alphabetically">Herausgeber (A bis Z)</string>
|
||||
<string name="sort_alphabetically_reverse">Herausgeber (Z bis A)</string>
|
||||
<string name="sort_alphabetically_name">Konto (A bis Z)</string>
|
||||
<string name="sort_alphabetically_name_reverse">Konto (Z bis A)</string>
|
||||
<string name="sort_custom">Benutzerdefiniert</string>
|
||||
<string name="new_group">Neue Gruppe…</string>
|
||||
<string name="enter_group_name">Gib einen Gruppennamen ein</string>
|
||||
<string name="group_name_hint">Gruppenname</string>
|
||||
<string name="preference_manage_groups">Gruppen bearbeiten</string>
|
||||
<string name="preference_manage_groups_summary">Verwalte und lösche deine Gruppen hier</string>
|
||||
<string name="pref_search_name_title">In Kontonamen suchen</string>
|
||||
<string name="pref_search_name_summary">Entsprechende Kontonamen in den Suchergebnissen miteinbeziehen</string>
|
||||
<string name="tap_to_reveal">Versteckt</string>
|
||||
<string name="selected">Ausgewählt</string>
|
||||
<string name="dark_theme_title">Dunkles Thema</string>
|
||||
<string name="light_theme_title">Helles Thema</string>
|
||||
<string name="amoled_theme_title">Amoled Thema</string>
|
||||
<string name="normal_viewmode_title">Normal</string>
|
||||
<string name="compact_mode_title">Kompakt</string>
|
||||
<string name="small_mode_title">Klein</string>
|
||||
<string name="unknown_issuer">Unbekannter Herausgeber</string>
|
||||
<string name="unknown_account_name">Unbekannter Kontoname</string>
|
||||
<string name="import_error_dialog">Aegis konnte %d Tokens nicht importieren. Diese Tokens werden übersprungen. Drücke \'details\', um mehr Informationen über die Fehler zu erhalten.</string>
|
||||
<string name="unable_to_read_qrcode">QR-Code kann nicht gelesen und verarbeitet werden.</string>
|
||||
<string name="select_picture">Bild auswählen</string>
|
||||
<string name="toggle_checkboxes">Markierungen umschalten</string>
|
||||
<string name="search">Suche</string>
|
||||
<string name="channel_name_lock_status">Sperrstatus</string>
|
||||
<string name="channel_description_lock_status">Aegis kann eine dauerhafte Benachrichtigung erstellen, um dich zu benachrichtigen, wenn der Tresor gesperrt ist.</string>
|
||||
<string name="vault_unlocked_state">Der Tresor ist freigeschaltet. Hier tippen, um zu sperren.</string>
|
||||
<string name="title_activity_about">Über uns</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="changelog">Liste der Änderungen</string>
|
||||
<string name="whats_new">Was ist neu</string>
|
||||
<string name="github_description">Quellcode, Probleme und Informationen</string>
|
||||
<string name="licenses">Lizenzen</string>
|
||||
<string name="licenses_description">Lizenzen der Bibliotheken, die Aegis verwendet</string>
|
||||
<string name="country_netherlands">Niederlande</string>
|
||||
<string name="email_us">Eine E-Mail schreiben</string>
|
||||
<string name="visit_website">Besuchen Sie unsere Webseite</string>
|
||||
<string name="about_support">Support</string>
|
||||
<string name="support_rate">Bewerten</string>
|
||||
<string name="support_rate_description">Unterstützen Sie uns, indem Sie eine Bewertung im Google Play Store hinterlassen</string>
|
||||
<string name="webview_error">Dieses Gerät unterstützt keine Web-Ansicht, die notwendig ist, um das Changelog anzuzeigen. Es fehlt eine Systemkomponente.</string>
|
||||
<string name="email">E-Mail</string>
|
||||
<string name="empty_list">Es gibt keine zu zeigenden Codes. Fügen Sie Einträge hinzu, indem Sie das Plus-Zeichen in der unteren rechten Ecke antippen</string>
|
||||
<string name="empty_list_title">Keine Einträge gefunden</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="action_settings">Configuración</string>
|
||||
<string name="action_about">Acerca de</string>
|
||||
<string name="action_import">Importar</string>
|
||||
<string name="action_delete">Eliminar</string>
|
||||
<string name="action_default_icon">Restaurar icono predeterminado</string>
|
||||
<string name="discard">Descartar</string>
|
||||
<string name="save">Guardar</string>
|
||||
<string name="issuer">Nombre</string>
|
||||
<string name="settings">Preferencias</string>
|
||||
<string name="pref_appearance_group_title">Apariencia</string>
|
||||
<string name="pref_general_group_title">General</string>
|
||||
<string name="pref_security_group_title">Seguridad</string>
|
||||
<string name="pref_tools_group_title">Herramientas</string>
|
||||
<string name="pref_select_theme_title">Tema</string>
|
||||
<string name="pref_view_mode_title">Modo de visualización</string>
|
||||
<string name="pref_lang_title">Idioma</string>
|
||||
<string name="pref_account_name_title">Mostrar el nombre de la cuenta</string>
|
||||
<string name="pref_account_name_summary">Active esto para mostrar el nombre de la cuenta junto al nombre</string>
|
||||
<string name="pref_timeout_title">Tiempo de espera</string>
|
||||
<string name="pref_timeout_summary">Bloquear automáticamente la caja fuerte después de %1$s segundos de inactividad</string>
|
||||
<string name="pref_slots_title">Claves</string>
|
||||
<string name="pref_slots_summary">Administrar la lista de claves que puede descifrar la caja fuerte</string>
|
||||
<string name="pref_import_file_title">Importar desde un archivo</string>
|
||||
<string name="pref_import_file_summary">Importar tokens desde un archivo</string>
|
||||
<string name="pref_import_app_title">Importar desde una aplicación</string>
|
||||
<string name="pref_import_app_summary">Importar tokens desde una aplicación (requiere acceso root)</string>
|
||||
<string name="pref_export_title">Exportar</string>
|
||||
<string name="pref_export_summary">Exportar la caja fuerte</string>
|
||||
<string name="pref_secure_screen_title">Seguridad en pantalla</string>
|
||||
<string name="pref_secure_screen_summary">Bloquea las capturas de pantalla y otros intentos de captura dentro de la aplicación</string>
|
||||
<string name="pref_tap_to_reveal_title">Pulsar para mostrar</string>
|
||||
<string name="pref_tap_to_reveal_summary">Los tokens se ocultarán por defecto. Pulse sobre ellos para revelar el código.</string>
|
||||
<string name="pref_tap_to_reveal_time_title">Tiempo de espera de pulsar para mostrar</string>
|
||||
<string name="pref_auto_lock_title">Bloqueo automático</string>
|
||||
<string name="pref_auto_lock_summary">Bloquear automáticamente cuando cierre la aplicación o bloquee su dispositivo.</string>
|
||||
<string name="pref_encryption_title">Cifrado</string>
|
||||
<string name="pref_encryption_summary">Cifra la base de datos y la desbloquea mediante una contraseña o huella biométrica</string>
|
||||
<string name="pref_biometrics_title">Desbloqueo biométrico</string>
|
||||
<string name="pref_biometrics_summary">Permite la autenticación biométrica para desbloquear la caja fuerte</string>
|
||||
<string name="pref_set_password_title">Cambiar contraseña</string>
|
||||
<string name="pref_set_password_summary">Establece una nueva contraseña que necesitará para desbloquear su caja fuerte</string>
|
||||
<string name="choose_authentication_method">Seguridad</string>
|
||||
<string name="authentication_method_none">Ninguno</string>
|
||||
<string name="authentication_method_none_description">No necesitará una contraseña para desbloquear la caja fuerte y no se cifrará. No se recomienda esta opción.</string>
|
||||
<string name="authentication_method_password">Contraseña</string>
|
||||
<string name="authentication_method_biometrics">Biometría</string>
|
||||
<string name="authentication_method_biometrics_description">Además de una contraseña, la biometría registrada en este dispositivo, como una huella digital o su cara, pueden ser usados para desbloquear la caja fuerte.</string>
|
||||
<string name="authentication_method_set_password">Contraseña</string>
|
||||
<string name="authentication_enter_password">Introduzca su contraseña</string>
|
||||
<string name="authentication">Desbloquear la caja fuerte</string>
|
||||
<string name="set_password">Por favor introduzca una contraseña</string>
|
||||
<string name="set_group">Por favor introduzca un nombre de grupo</string>
|
||||
<string name="set_number">Por favor introduzca un número</string>
|
||||
<string name="set_password_confirm">Por favor confirme la contraseña</string>
|
||||
<string name="invalidated_biometrics">Se ha detectado un cambio en la configuración de seguridad de su dispositivo. Por favor, vaya a \"Aegis -> Configuración -> Biometría\" y vuelva a activar el desbloqueo biométrico.</string>
|
||||
<string name="unlock">Desbloquear</string>
|
||||
<string name="biometrics">Biometría</string>
|
||||
<string name="advanced">Avanzado</string>
|
||||
<string name="seconds">segundos</string>
|
||||
<string name="counter">Contador</string>
|
||||
<string name="digits">Dígitos</string>
|
||||
<string name="secret">Código secreto</string>
|
||||
<string name="scan">Escanear código QR</string>
|
||||
<string name="scan_image">Escanear imagen</string>
|
||||
<string name="enter_manually">Introducir manualmente</string>
|
||||
<string name="add_biometric">Añadir biometría</string>
|
||||
<string name="add_biometric_slot">Añadir clave biométrica</string>
|
||||
<string name="set_up_biometric">Configurar desbloqueo biométrico</string>
|
||||
<string name="add_password">Añadir contraseña</string>
|
||||
<string name="slots_warning">La caja fuerte es tan segura como su código secreto más débil. Si cambia la configuración de autenticación biométrica de su dispositivo, tendrá que reactivar el desbloqueo biométrico dentro de Aegis.</string>
|
||||
<string name="copy">Copiar</string>
|
||||
<string name="edit">Editar</string>
|
||||
<string name="delete">Eliminar</string>
|
||||
<string name="password">Contraseña</string>
|
||||
<string name="confirm_password">Confirmar contraseña</string>
|
||||
<string name="show_password">Mostrar contraseña</string>
|
||||
<string name="new_profile">Nuevo perfil</string>
|
||||
<string name="add_new_profile">Añadir nuevo perfil</string>
|
||||
<string name="unlock_vault_error">No se pudo desbloquear la caja fuerte</string>
|
||||
<string name="unlock_vault_error_description">Contraseña incorrecta. Asegúrese de no haberla introducido erróneamente.</string>
|
||||
<string name="password_equality_error">Las contraseñas deben ser idénticas y no estar en blanco</string>
|
||||
<string name="snackbar_authentication_method">Por favor seleccione un método de autentificación</string>
|
||||
<string name="encrypting_vault">Cifrando la caja fuerte</string>
|
||||
<string name="delete_entry">Eliminar entrada</string>
|
||||
<string name="delete_entry_description">¿Está seguro de que desea eliminar esta entrada?</string>
|
||||
<string name="discard_changes">¿Descartar cambios?</string>
|
||||
<string name="discard_changes_description">Sus cambios no se han guardado</string>
|
||||
<string name="folder">Carpeta</string>
|
||||
<string name="tap_to_select">Pulsar para seleccionar</string>
|
||||
<string name="saving_profile_error">Error al guardar el perfil</string>
|
||||
<string name="welcome">Bienvenido</string>
|
||||
<string name="app_description">Aegis es una aplicación de 2FA gratuita, segura y de código abierto</string>
|
||||
<string name="setup_completed">Configuración completada</string>
|
||||
<string name="setup_completed_description">Aegis se ha configurado y está listo para funcionar.</string>
|
||||
<string name="vault_not_found">Caja fuerte no encontrada, iniciando la configuración…</string>
|
||||
<string name="code_copied">Código copiado al portapapeles</string>
|
||||
<string name="errors_copied">Errores copiados al portapapeles</string>
|
||||
<string name="version_copied">Versión copiada al portapapeles</string>
|
||||
<string name="decryption_error">Se ha producido un error tratando de desbloquear la caja fuerte</string>
|
||||
<string name="saving_error">Se ha producido un error tratando de guardar la caja fuerte</string>
|
||||
<string name="disable_encryption">Desactivar cifrado</string>
|
||||
<string name="disable_encryption_description">¿Está seguro de que desea desactivar el cifrado? Esto provocará que la caja fuerte se almacene en texto plano.</string>
|
||||
<string name="enable_encryption_error">Se ha producido un error tratando de activar el cifrado</string>
|
||||
<string name="disable_encryption_error">Se ha producido un error tratando de desactivar el cifrado</string>
|
||||
<string name="permission_denied">Permiso denegado</string>
|
||||
<string name="andotp_new_format">Formato nuevo (v0.6.3 o superior) </string>
|
||||
<string name="andotp_old_format">Formato antiguo (v0.6.2 o anterior) </string>
|
||||
<string name="choose_andotp_importer">¿Qué formato tiene el archivo de copia de seguridad de andOTP?</string>
|
||||
<string name="choose_application">Seleccione la aplicación desde la que desea importar</string>
|
||||
<string name="choose_theme">Seleccione su tema deseado</string>
|
||||
<string name="choose_view_mode">Seleccione su modo de visualización deseado</string>
|
||||
<string name="parsing_file_error">Se ha producido un error procesando el archivo</string>
|
||||
<string name="file_not_found">Error: Archivo no encontrado</string>
|
||||
<string name="reading_file_error">Se ha producido un error tratando de leer el archivo</string>
|
||||
<string name="app_lookup_error">Error: La aplicación no está instalada</string>
|
||||
<string name="root_error">Error: No se puede obtener acceso root</string>
|
||||
<string name="imported_entries_count">Importadas %d entradas</string>
|
||||
<string name="read_entries_count">Leídas %d entradas. %d errores.</string>
|
||||
<string name="import_error_title">Se han producido uno o más errores durante la importación</string>
|
||||
<string name="exporting_vault_error">Se ha producido un error tratando de exportar la caja fuerte</string>
|
||||
<string name="export_warning">Esta acción exportará la caja fuerte fuera del almacenamiento privado de Aegis.</string>
|
||||
<string name="encryption_set_password_error">Se ha producido un error tratando de establecer la contraseña: </string>
|
||||
<string name="encryption_enable_biometrics_error">Se ha producido un error tratando de activar el desbloqueo biométrico</string>
|
||||
<string name="no_cameras_available">No hay ninguna cámara disponible</string>
|
||||
<string name="read_qr_error">Se ha producido un error tratando de leer el código QR</string>
|
||||
<string name="authentication_method_raw">Raw</string>
|
||||
<string name="unlocking_vault">Desbloqueando la caja fuerte</string>
|
||||
<string name="unlocking_vault_repair">Desbloqueando la caja fuerte (reparando)</string>
|
||||
<string name="password_slot_error">Debe tener al menos una contraseña</string>
|
||||
<string name="remove_slot">Eliminar clave</string>
|
||||
<string name="remove_slot_description">¿Está seguro de que desea eliminar esta clave?</string>
|
||||
<string name="remove_group">Eliminar grupo</string>
|
||||
<string name="remove_group_description">¿Está seguro de que desea eliminar este grupo? Las entradas se moverán automáticamente a \'Sin grupo\'.</string>
|
||||
<string name="adding_new_slot_error">Se ha producido un error tratando de añadir una nueva clave:</string>
|
||||
<string name="progressbar_error">No se puede restablecer la escala de duración de la animación. Las barras de progreso serán invisibles.</string>
|
||||
<string name="details">Detalles</string>
|
||||
<string name="filter">Filtrar</string>
|
||||
<string name="filter_ungrouped">Sin agrupar</string>
|
||||
<string name="lock">Bloquear</string>
|
||||
<string name="all">Todo</string>
|
||||
<string name="name">Cuenta</string>
|
||||
<string name="no_group">Sin grupo</string>
|
||||
<string name="sort_alphabetically">Nombre (A a Z)</string>
|
||||
<string name="sort_alphabetically_reverse">Nombre (Z a A)</string>
|
||||
<string name="sort_alphabetically_name">Cuenta (A a Z)</string>
|
||||
<string name="sort_alphabetically_name_reverse">Cuenta (Z a A)</string>
|
||||
<string name="sort_custom">Personalizado</string>
|
||||
<string name="new_group">Nuevo grupo…</string>
|
||||
<string name="enter_group_name">Introduzca un nombre de grupo</string>
|
||||
<string name="group_name_hint">Nombre del grupo</string>
|
||||
<string name="preference_manage_groups">Editar grupos</string>
|
||||
<string name="preference_manage_groups_summary">Gestione y elimine sus grupos aquí</string>
|
||||
<string name="pref_search_name_title">Buscar en los nombres de cuenta</string>
|
||||
<string name="pref_search_name_summary">Incluir coincidencias del nombre de la cuenta en los resultados de búsqueda</string>
|
||||
<string name="pref_highlight_entry_title">Resaltar tokens al pulsarlos</string>
|
||||
<string name="pref_highlight_entry_summary">Hace que los tokens sean más fáciles de distinguir entre ellos resaltándolos temporalmente tras ser pulsados</string>
|
||||
<string name="tap_to_reveal">Oculto</string>
|
||||
<string name="selected">Seleccionado</string>
|
||||
<string name="dark_theme_title">Tema oscuro</string>
|
||||
<string name="light_theme_title">Tema claro</string>
|
||||
<string name="amoled_theme_title">Tema Amoled</string>
|
||||
<string name="normal_viewmode_title">Normal</string>
|
||||
<string name="compact_mode_title">Compacto</string>
|
||||
<string name="small_mode_title">Pequeño</string>
|
||||
<string name="unknown_issuer">Nombre desconocido</string>
|
||||
<string name="unknown_account_name">Nombre de cuenta desconocido</string>
|
||||
<string name="import_error_dialog">Aegis no pudo importar %d tokens. Estos tokens se omitirán. Pulse \'detalles\' para ver más información sobre los errores.</string>
|
||||
<string name="unable_to_read_qrcode">No se puede leer y procesar el código QR</string>
|
||||
<string name="select_picture">Seleccionar imagen</string>
|
||||
<string name="toggle_checkboxes">Activar casillas de verificación</string>
|
||||
<string name="search">Buscar</string>
|
||||
<string name="channel_name_lock_status">Estado de bloqueo</string>
|
||||
<string name="channel_description_lock_status">Aegis puede crear una notificación persistente para notificar cuando la caja fuerte está bloqueada</string>
|
||||
<string name="vault_unlocked_state">La caja fuerte está desbloqueada. Pulse aquí para bloquear.</string>
|
||||
<string name="title_activity_about">Acerca de</string>
|
||||
<string name="version">Versión</string>
|
||||
<string name="changelog">Historial de cambios</string>
|
||||
<string name="whats_new">Novedades</string>
|
||||
<string name="github_description">Código fuente, problemas e información</string>
|
||||
<string name="licenses">Licencias</string>
|
||||
<string name="licenses_description">Licencias de las librerías usadas por Aegis</string>
|
||||
<string name="country_netherlands">Países Bajos</string>
|
||||
<string name="email_us">Escríbanos un correo electrónico</string>
|
||||
<string name="visit_website">Visite nuestra página web</string>
|
||||
<string name="about_support">Soporte</string>
|
||||
<string name="support_rate">Valorar</string>
|
||||
<string name="support_rate_description">Ayúdenos dejando una reseña en Google Play Store</string>
|
||||
<string name="webview_error">Este dispositivo no soporta web view, lo cual es necesario para visualizar el registro de cambios. Falta un componente del sistema.</string>
|
||||
<string name="email">Correo electrónico</string>
|
||||
<string name="empty_list">No hay códigos que mostrar. Comience añadiendo entradas pulsando el signo + en la esquina inferior derecha</string>
|
||||
<string name="empty_list_title">No hay entradas</string>
|
||||
</resources>
|
||||
+29
-19
@@ -1,20 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Aegis</string>
|
||||
<string name="app_name_dev">AegisDev</string>
|
||||
<string name="action_settings">Paramètres</string>
|
||||
<string name="action_about">À propos</string>
|
||||
<string name="action_import">Importer</string>
|
||||
<string name="action_delete">Supprimer</string>
|
||||
<string name="action_default_icon">Restaurer l\'icône par défaut</string>
|
||||
<string name="discard">Annuler</string>
|
||||
<string name="save">Enregistrer</string>
|
||||
<string name="issuer">Émetteur</string>
|
||||
|
||||
<string name="settings">Préférences</string>
|
||||
<string name="pref_appearance_group_title">Apparence</string>
|
||||
<string name="pref_general_group_title">Général</string>
|
||||
<string name="pref_security_group_title">Sécurité</string>
|
||||
<string name="pref_tools_group_title">Outils</string>
|
||||
<string name="pref_select_theme_title">Thème</string>
|
||||
<string name="pref_view_mode_title">Mode d\'affichage</string>
|
||||
<string name="pref_lang_title">Langue</string>
|
||||
<string name="pref_account_name_title">Afficher le nom du compte</string>
|
||||
<string name="pref_account_name_summary">Activer pour afficher le nom de compte près de l\'émetteur</string>
|
||||
<string name="pref_timeout_title">Délai</string>
|
||||
@@ -36,22 +37,13 @@
|
||||
<string name="pref_auto_lock_summary">Verrouiller automatiquement lorsque vous fermez l\'application ou verrouillez votre appareil.</string>
|
||||
<string name="pref_encryption_title">Chiffrement</string>
|
||||
<string name="pref_encryption_summary">Chiffrer la base de données et la déverrouiller avec un mot de passe ou une empreinte digitale</string>
|
||||
<string name="pref_fingerprint_title">Empreinte digitale</string>
|
||||
<string name="pref_fingerprint_summary">Autoriser les empreintes digitales enregistrées sur cet appareil à déverrouiller le coffre-fort</string>
|
||||
<string name="pref_set_password_title">Changer le mot de passe</string>
|
||||
<string name="pref_set_password_summary">Définir un mot de passe dont vous aurez besoin pour déverrouiller votre coffre-fort</string>
|
||||
|
||||
<string name="fingerprint_hint">Appuyez sur le capteur</string>
|
||||
<string name="fingerprint_not_recognized">Empreinte digitale non reconnue. Réessayez.</string>
|
||||
<string name="fingerprint_success">Empreinte digitale reconnue</string>
|
||||
|
||||
<string name="choose_authentication_method">Sécurité</string>
|
||||
<string name="authentication_method_none">Aucun</string>
|
||||
<string name="authentication_method_none_description">Vous n\'avez pas besoin d\'un mot de passe pour déverrouiller le coffre-fort et il ne sera pas chiffré. Cette option n\'est pas recommandée.</string>
|
||||
<string name="authentication_method_password">Mot de passe</string>
|
||||
<string name="authentication_method_password_description">Vous avez besoin d\'un mot de passe pour déverrouiller le coffre-fort.</string>
|
||||
<string name="authentication_method_fingerprint">Empreinte digitale</string>
|
||||
<string name="authentication_method_fingerprint_description">En plus d\'un mot de passe, les empreintes digitales enregistrées sur cet appareil peuvent être utilisées pour déverrouiller le coffre-fort.</string>
|
||||
<string name="authentication_method_set_password">Mot de passe</string>
|
||||
<string name="authentication_enter_password">Saisissez votre mot de passe</string>
|
||||
<string name="authentication">Déverrouiller le coffre-fort</string>
|
||||
@@ -59,8 +51,6 @@
|
||||
<string name="set_group">Veuillez saisir un nom de groupe</string>
|
||||
<string name="set_number">Veuillez saisir un nombre</string>
|
||||
<string name="set_password_confirm">Veuillez confirmer le mot de passe</string>
|
||||
<string name="invalidated_fingerprint">Un changement dans les paramètres de sécurité de votre appareil a été détecté. Veuillez aller dans \"Paramètres -> Empreinte digitale\" et ajouter à nouveau votre empreinte digitale.</string>
|
||||
|
||||
<string name="unlock">Déverrouiller</string>
|
||||
<string name="advanced">Avancé</string>
|
||||
<string name="seconds">secondes</string>
|
||||
@@ -70,7 +60,6 @@
|
||||
<string name="scan">Scanner code QR</string>
|
||||
<string name="scan_image">Scanner image</string>
|
||||
<string name="enter_manually">Saisir manuellement</string>
|
||||
<string name="add_fingerprint">Ajouter une empreinte digitale</string>
|
||||
<string name="add_password">Ajouter un mot de passe</string>
|
||||
<string name="slots_warning">Le coffre-fort est seulement aussi sécurisé que votre code secret le plus faible. Quand une nouvelle empreinte digitale est ajoutée à votre appareil, vous devrez réactiver l\'authentification par empreinte digitale dans Aegis.</string>
|
||||
<string name="copy">Copier</string>
|
||||
@@ -83,7 +72,6 @@
|
||||
<string name="unlock_vault_error">Impossible de déverrouiller le coffre-fort</string>
|
||||
<string name="unlock_vault_error_description">Mot de passe incorrect. Assurez-vous de ne pas avoir fait de faute de frappe lors de la saisie de votre mot de passe.</string>
|
||||
<string name="password_equality_error">Les mots de passe doivent être identiques et non vides</string>
|
||||
<string name="register_fingerprint">Enregistrer votre empreinte digitale</string>
|
||||
<string name="snackbar_authentication_method">Veuillez sélectionner une méthode d\'authentification</string>
|
||||
<string name="encrypting_vault">Chiffrement du coffre-fort</string>
|
||||
<string name="delete_entry">Supprimer entrée</string>
|
||||
@@ -100,6 +88,7 @@
|
||||
<string name="vault_not_found">Coffre-fort introuvable, lancement de la configuration…</string>
|
||||
<string name="code_copied">Code copié dans le presse-papier</string>
|
||||
<string name="errors_copied">Erreurs copiées dans le presse-papier</string>
|
||||
<string name="version_copied">Version copiée dans le presse-papier</string>
|
||||
<string name="decryption_error">Une erreur est survenue en essayant de déverrouiller le coffre-fort</string>
|
||||
<string name="saving_error">Une erreur est survenue en essayant d\'enregistrer le coffre-fort</string>
|
||||
<string name="disable_encryption">Désactiver le chiffrement</string>
|
||||
@@ -118,15 +107,13 @@
|
||||
<string name="imported_entries_count">%d entrées importées</string>
|
||||
<string name="read_entries_count">%d entrées lues. %d erreurs.</string>
|
||||
<string name="import_error_title">Une ou plusieurs erreurs sont survenues lors de l\'importation</string>
|
||||
<string name="exporting_database_error">Une erreur est survenue en essayant d\'exporter la base de données</string>
|
||||
<string name="export_database_location">La base de données a été exportée vers :</string>
|
||||
<string name="export_warning">Cette action va exporter la base de données en dehors du stockage privé d\'Aegis.</string>
|
||||
<string name="encryption_set_password_error">Une erreur est survenue en essayant de définir le mot de passe : </string>
|
||||
<string name="encryption_enable_fingerprint_error">Une erreur est survenue en essayant d\'activer le déverrouillage par empreinte digitale : </string>
|
||||
<string name="no_cameras_available">Aucun appareil photo disponible</string>
|
||||
<string name="read_qr_error">Une erreur est survenue en essayant de lire le code QR</string>
|
||||
<string name="authentication_method_raw">Brut</string>
|
||||
<string name="unlocking_vault">Déverrouillage du coffre-fort</string>
|
||||
<string name="unlocking_vault_repair">Déverrouillage du coffre-fort (réparation)</string>
|
||||
<string name="password_slot_error">Vous devez avoir au moins un emplacement de mot de passe</string>
|
||||
<string name="remove_slot">Supprimer emplacement</string>
|
||||
<string name="remove_slot_description">Êtes-vous sûr de vouloir supprimer cet emplacement ?</string>
|
||||
@@ -150,6 +137,8 @@
|
||||
<string name="group_name_hint">Nom de groupe</string>
|
||||
<string name="preference_manage_groups">Modifier les groupes</string>
|
||||
<string name="preference_manage_groups_summary">Gérez et supprimez vos groupes ici</string>
|
||||
<string name="pref_search_name_title">Rechercher dans les noms de comptes</string>
|
||||
<string name="pref_search_name_summary">Inclure les correspondances de noms de comptes dans les résultats de recherche</string>
|
||||
<string name="tap_to_reveal">Masqué</string>
|
||||
<string name="selected">Sélectionné</string>
|
||||
<string name="dark_theme_title">Thème sombre</string>
|
||||
@@ -164,4 +153,25 @@
|
||||
<string name="unable_to_read_qrcode">Impossible de lire et traiter le code QR</string>
|
||||
<string name="select_picture">Sélectionner image</string>
|
||||
<string name="toggle_checkboxes">Basculer les cases</string>
|
||||
<string name="search">Recherche</string>
|
||||
<string name="channel_name_lock_status">Statut du verrouillage</string>
|
||||
<string name="channel_description_lock_status">Aegis peut créer une notification persistante pour vous notifier quand le coffre-fort est verrouillé</string>
|
||||
<string name="vault_unlocked_state">Le coffre-fort est déverrouillé. Appuyer ici pour verrouiller.</string>
|
||||
<string name="title_activity_about">À propos</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="changelog">Historique des modifications</string>
|
||||
<string name="whats_new">Les nouveautés</string>
|
||||
<string name="github_description">Code source, problèmes et informations</string>
|
||||
<string name="licenses">Licences</string>
|
||||
<string name="licenses_description">Licences des bibliothèques utilisées par Aegis</string>
|
||||
<string name="country_netherlands">Pays-Bas</string>
|
||||
<string name="email_us">Écrire un email</string>
|
||||
<string name="visit_website">Visiter notre site internet</string>
|
||||
<string name="about_support">Soutien</string>
|
||||
<string name="support_rate">Noter</string>
|
||||
<string name="support_rate_description">Soutenez-nous en laissant un avis sur le Google Play Store</string>
|
||||
<string name="webview_error">Cet appareil ne supporte pas web view, qui est nécessaire pour voir l\'historique des modifications. C\'est un composant système manquant.</string>
|
||||
<string name="email">Email</string>
|
||||
<string name="empty_list">Il n\'y a pas de code à afficher. Commencez à ajouter des entrées en appuyant sur le signe plus dans le coin inférieur droit</string>
|
||||
<string name="empty_list_title">Aucune entrée trouvée</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="action_settings">Impostazioni</string>
|
||||
<string name="action_about">Informazioni su</string>
|
||||
<string name="action_import">Importare</string>
|
||||
<string name="action_delete">Elimina</string>
|
||||
<string name="action_default_icon">Ripristina icona predefinita</string>
|
||||
<string name="discard">Annulla</string>
|
||||
<string name="save">Salva</string>
|
||||
<string name="issuer">Login</string>
|
||||
<string name="settings">Impostazioni</string>
|
||||
<string name="pref_appearance_group_title">Aspetto</string>
|
||||
<string name="pref_general_group_title">Generale</string>
|
||||
<string name="pref_security_group_title">Sicurezza</string>
|
||||
<string name="pref_tools_group_title">Strumenti</string>
|
||||
<string name="pref_select_theme_title">Tema</string>
|
||||
<string name="pref_view_mode_title">Visualizzazione</string>
|
||||
<string name="pref_lang_title">Seleziona Lingua</string>
|
||||
<string name="pref_account_name_title">Mostra nome account</string>
|
||||
<string name="pref_account_name_summary">Se attivo mostra il nome dell\'account accanto al login</string>
|
||||
<string name="pref_timeout_title">Timeout</string>
|
||||
<string name="pref_timeout_summary">Blocca automaticamente la cassaforte dopo %1$s secondi di inattività</string>
|
||||
<string name="pref_slots_title">Slot chiavi</string>
|
||||
<string name="pref_slots_summary">Gestisci la lista delle chiavi che possono decriptare il database</string>
|
||||
<string name="pref_import_file_title">Importa da file</string>
|
||||
<string name="pref_import_file_summary">Importa token da file</string>
|
||||
<string name="pref_import_app_title">Importa da un\'app</string>
|
||||
<string name="pref_import_app_summary">Importa token da app (richiede accesso root)</string>
|
||||
<string name="pref_export_title">Esporta</string>
|
||||
<string name="pref_export_summary">Esporta la cassaforte</string>
|
||||
<string name="pref_secure_screen_title">Sicurezza schermo</string>
|
||||
<string name="pref_secure_screen_summary">Blocca i tentativi di catturare lo schermo all\'interno dell\'app</string>
|
||||
<string name="pref_tap_to_reveal_title">Tocca per mostrare</string>
|
||||
<string name="pref_tap_to_reveal_summary">I token saranno nascosti. Toccandone uno viene mostrato il codice.</string>
|
||||
<string name="pref_tap_to_reveal_time_title">Timeout visualizzazione codice OTP</string>
|
||||
<string name="pref_auto_lock_title">Blocco automatico</string>
|
||||
<string name="pref_auto_lock_summary">Blocca automaticamente quando chiudi l\'app o blocca il dispositivo.</string>
|
||||
<string name="pref_encryption_title">Crittografia</string>
|
||||
<string name="pref_encryption_summary">Cripta il database e sbloccalo con password o impronta digitale</string>
|
||||
<string name="pref_biometrics_title">Sblocco biometrico</string>
|
||||
<string name="pref_biometrics_summary">Consenti l\'autenticazione biometrica per sbloccare la cassaforte</string>
|
||||
<string name="pref_set_password_title">Cambia password</string>
|
||||
<string name="pref_set_password_summary">Modifica la password della tua cassaforte</string>
|
||||
<string name="choose_authentication_method">Sicurezza</string>
|
||||
<string name="authentication_method_none">Nessuna</string>
|
||||
<string name="authentication_method_password">Password</string>
|
||||
<string name="authentication_method_biometrics">Biometrica</string>
|
||||
<string name="authentication_method_biometrics_description">Oltre a una password, i dati biometrici registrati su questo dispositivo, come un\'impronta digitale o il tuo volto, possono essere utilizzati per sbloccare la cassaforte.</string>
|
||||
<string name="authentication_method_set_password">Password</string>
|
||||
<string name="authentication_enter_password">Inserisci la tua password</string>
|
||||
<string name="authentication">Sblocca la cassaforte</string>
|
||||
<string name="set_password">Inserisci una password</string>
|
||||
<string name="set_group">Inserisci il nome di un gruppo</string>
|
||||
<string name="set_number">Inserisci un numero</string>
|
||||
<string name="set_password_confirm">Conferma la tua password</string>
|
||||
<string name="invalidated_biometrics">È stata rilevata una modifica delle impostazioni di sicurezza del dispositivo. Vai a \"Aegis -> Impostazioni -> Sicurezza\" e riattiva lo sblocco biometrico.</string>
|
||||
<string name="unlock">Sblocca</string>
|
||||
<string name="biometrics">Biometrica</string>
|
||||
<string name="advanced">Avanzate</string>
|
||||
<string name="seconds">secondi</string>
|
||||
<string name="counter">Contatore</string>
|
||||
<string name="digits">Cifre</string>
|
||||
<string name="secret">Segreto</string>
|
||||
<string name="scan">Scansiona codice QR</string>
|
||||
<string name="scan_image">Scansiona immagine</string>
|
||||
<string name="enter_manually">Inserisci manualmente</string>
|
||||
<string name="add_biometric">Aggiungi dati biometrici</string>
|
||||
<string name="set_up_biometric">Imposta sblocco biometrico</string>
|
||||
<string name="add_password">Aggiungi password</string>
|
||||
<string name="copy">Copia</string>
|
||||
<string name="edit">Modifica</string>
|
||||
<string name="delete">Elimina</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="confirm_password">Conferma password</string>
|
||||
<string name="show_password">Mostra password</string>
|
||||
<string name="new_profile">Nuovo profilo</string>
|
||||
<string name="add_new_profile">Aggiungi nuovo profilo</string>
|
||||
<string name="unlock_vault_error">Impossibile sbloccare la cassaforte</string>
|
||||
<string name="unlock_vault_error_description">Password errata. Assicurati di non aver sbagliato a digitare.</string>
|
||||
<string name="password_equality_error">Le password devono essere identiche e non vuote</string>
|
||||
<string name="snackbar_authentication_method">Seleziona un metodo di autenticazione</string>
|
||||
<string name="encrypting_vault">Criptando la cassaforte</string>
|
||||
<string name="delete_entry">Elimina elemento</string>
|
||||
<string name="delete_entry_description">Sei sicuro di voler eliminare questa voce?</string>
|
||||
<string name="discard_changes">Annullare le modifiche?</string>
|
||||
<string name="discard_changes_description">Le modifiche non sono state salvate</string>
|
||||
<string name="folder">Cartella</string>
|
||||
<string name="tap_to_select">Tocca per selezionare</string>
|
||||
<string name="saving_profile_error">Errore nel salvataggio del profilo</string>
|
||||
<string name="welcome">Benvenuto</string>
|
||||
<string name="app_description">Aegis è un\'app 2FA gratuita, sicura e open source</string>
|
||||
<string name="setup_completed">Configurazione completata</string>
|
||||
<string name="setup_completed_description">Aegis è stata configurata, sei pronto a partire.</string>
|
||||
<string name="vault_not_found">Cassaforte non trovata, avvio configurazione…</string>
|
||||
<string name="code_copied">Codice copiato negli appunti</string>
|
||||
<string name="errors_copied">Codice errore copiato negli appunti</string>
|
||||
<string name="version_copied">Versione copiata negli appunti</string>
|
||||
<string name="decryption_error">Si è verificato un errore nello sblocco della cassaforte</string>
|
||||
<string name="saving_error">Si è verificato un errore durante il salvataggio della cassaforte</string>
|
||||
<string name="disable_encryption">Disabilita crittografia</string>
|
||||
<string name="disable_encryption_description">Sei sicuro di voler disabilitare la crittografia? La cassaforte sarà salvata come testo in chiaro.</string>
|
||||
<string name="enable_encryption_error">Errore durante l\'attivazione della crittografia</string>
|
||||
<string name="disable_encryption_error">Errore durante la disabilitazione della crittografia</string>
|
||||
<string name="permission_denied">Autorizzazione negata</string>
|
||||
<string name="andotp_new_format">Nuovo formato (v0.6.3 o più recente) </string>
|
||||
<string name="andotp_old_format">Vecchio formato (v0.6.2 o più vecchio) </string>
|
||||
<string name="choose_andotp_importer">Quale formato ha il file di backup andOTP?</string>
|
||||
<string name="choose_application">Seleziona l\'applicazione da cui vuoi importare un database</string>
|
||||
<string name="choose_theme">Seleziona il tema</string>
|
||||
<string name="choose_view_mode">Seleziona la modalità di visualizzazione</string>
|
||||
<string name="parsing_file_error">Errore durante l\'analisi del file</string>
|
||||
<string name="file_not_found">Errore: File non trovato</string>
|
||||
<string name="reading_file_error">Errore di lettura del file</string>
|
||||
<string name="app_lookup_error">Errore: App non installata</string>
|
||||
<string name="root_error">Errore: impossibile ottenere permessi root</string>
|
||||
<string name="imported_entries_count">Importati %d elementi</string>
|
||||
<string name="read_entries_count">Letti %d elementi. %d errori.</string>
|
||||
<string name="import_error_title">Errori durante l\'importazione</string>
|
||||
<string name="exporting_vault_error">Si è verificato un errore durante il tentativo di esportare la cassaforte</string>
|
||||
<string name="export_warning">Questa azione esporterà la cassaforte dall\'archivio privato di Aegis.</string>
|
||||
<string name="encryption_set_password_error">Errore durante l\'impostazione della password: </string>
|
||||
<string name="encryption_enable_biometrics_error">Si è verificato un errore durante il tentativo di abilitare lo sblocco biometrico</string>
|
||||
<string name="no_cameras_available">Nessuna fotocamera disponibile</string>
|
||||
<string name="read_qr_error">Errore di lettura del codice QR</string>
|
||||
<string name="authentication_method_raw">Raw</string>
|
||||
<string name="unlocking_vault">Sblocco della cassaforte</string>
|
||||
<string name="unlocking_vault_repair">Sblocco della cassaforte (riparazione)</string>
|
||||
<string name="password_slot_error">Devi avere almeno uno slot password</string>
|
||||
<string name="remove_slot">Rimuovi slot</string>
|
||||
<string name="remove_slot_description">Sei sicuro di voler rimuovere questo slot?</string>
|
||||
<string name="remove_group">Rimuovi gruppo</string>
|
||||
<string name="remove_group_description">Sei sicuro di voler rimuovere questo gruppo? Gli elementi in questo gruppo verranno automaticamente spostati in \'Nessun gruppo\'.</string>
|
||||
<string name="adding_new_slot_error">Errore nell\'aggiunta di un nuovo slot:</string>
|
||||
<string name="progressbar_error">Impossibile ripristinare la durata delle animazioni. Le barre di caricamento non saranno visibili.</string>
|
||||
<string name="details">Dettagli</string>
|
||||
<string name="filter">Filtra</string>
|
||||
<string name="filter_ungrouped">Non raggruppati</string>
|
||||
<string name="lock">Blocca</string>
|
||||
<string name="all">Tutti</string>
|
||||
<string name="name">Nome</string>
|
||||
<string name="no_group">Nessun gruppo</string>
|
||||
<string name="sort_alphabetically">Login (A-Z)</string>
|
||||
<string name="sort_alphabetically_reverse">Login (Z-A)</string>
|
||||
<string name="sort_alphabetically_name">Account (A-Z)</string>
|
||||
<string name="sort_alphabetically_name_reverse">Account (Z-A)</string>
|
||||
<string name="sort_custom">Personalizza</string>
|
||||
<string name="new_group">Nuovo gruppo...</string>
|
||||
<string name="enter_group_name">Inserisci un nome di gruppo</string>
|
||||
<string name="group_name_hint">Nome gruppo</string>
|
||||
<string name="preference_manage_groups">Modifica Gruppi</string>
|
||||
<string name="preference_manage_groups_summary">Gestisci ed elimina i tuoi gruppi qui</string>
|
||||
<string name="pref_search_name_title">Cerca nei nomi degli account</string>
|
||||
<string name="pref_search_name_summary">Includi il nome dell\'account nei risultati di ricerca</string>
|
||||
<string name="pref_highlight_entry_title">Evidenzia i token quando premuti</string>
|
||||
<string name="pref_highlight_entry_summary">Rendi i token più facili da distinguere, evidenziandoli temporaneamente quando vengono toccati</string>
|
||||
<string name="tap_to_reveal">Nascosto</string>
|
||||
<string name="selected">Selezionato</string>
|
||||
<string name="dark_theme_title">Tema scuro</string>
|
||||
<string name="light_theme_title">Tema chiaro</string>
|
||||
<string name="amoled_theme_title">Tema Amoled (nero)</string>
|
||||
<string name="normal_viewmode_title">Normale</string>
|
||||
<string name="compact_mode_title">Compatto</string>
|
||||
<string name="small_mode_title">Piccolo</string>
|
||||
<string name="unknown_issuer">Login sconosciuto</string>
|
||||
<string name="unknown_account_name">Nome account sconosciuto</string>
|
||||
<string name="import_error_dialog">Aegis non può importare %d token. Questi token verranno saltati. Premi \'dettagli\' per vedere ulteriori informazioni sugli errori.</string>
|
||||
<string name="unable_to_read_qrcode">Impossibile verificare il codice QR</string>
|
||||
<string name="select_picture">Seleziona immagine</string>
|
||||
<string name="toggle_checkboxes">Spunta</string>
|
||||
<string name="search">Cerca</string>
|
||||
<string name="channel_name_lock_status">Stato blocco</string>
|
||||
<string name="channel_description_lock_status">Aegis può mostrare una notifica persistente quando la cassaforte è sbloccata</string>
|
||||
<string name="vault_unlocked_state">Cassaforte sbloccata. Tocca qui per bloccare.</string>
|
||||
<string name="title_activity_about">Informazioni su</string>
|
||||
<string name="version">Versione</string>
|
||||
<string name="changelog">Changelog</string>
|
||||
<string name="whats_new">Novità</string>
|
||||
<string name="github_description">Codice, problemi e informazioni</string>
|
||||
<string name="licenses">Licenze</string>
|
||||
<string name="licenses_description">Licenze delle librerie usate da Aegis</string>
|
||||
<string name="country_netherlands">Olanda</string>
|
||||
<string name="email_us">Scrivici una email</string>
|
||||
<string name="visit_website">Visita il nostro sito web</string>
|
||||
<string name="about_support">Supporto</string>
|
||||
<string name="support_rate">Vota</string>
|
||||
<string name="support_rate_description">Supportaci lasciando una recensione nel Google Play Store</string>
|
||||
<string name="webview_error">Questo dispositivo non supporta la visualizzazione web, necessaria per visualizzare il changelog. Manca un componente di sistema.</string>
|
||||
<string name="email">Email</string>
|
||||
<string name="empty_list">Non ci sono codici. Aggiungine uno premendo il \'+\' in basso a destra</string>
|
||||
<string name="empty_list_title">Nessun elemento trovato</string>
|
||||
</resources>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user