Compare commits

..

1 Commits

Author SHA1 Message Date
J-Jamet
df33c5feb5 First commit for cancelable loading 2019-10-24 14:53:55 +02:00
551 changed files with 14733 additions and 24218 deletions

View File

@@ -20,6 +20,9 @@ Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
** Keepass Database **
- Created with: [e.g Windows KeePass 2.42]
- Version: [e.g. 2]
@@ -27,7 +30,7 @@ A clear and concise description of what you expected to happen.
- Size: [e.g. 150Mo]
- Contains attachment: [e.g. Yes]
**KeePassDX (please complete the following information):**
**KeePass DX (please complete the following information):**
- Version: [e.g. 2.5.0.0beta23]
- Build: [e.g. Free]
- Language: [e.g. French]
@@ -35,7 +38,7 @@ A clear and concise description of what you expected to happen.
**Android (please complete the following information):**
- Device: [e.g. GalaxyS8]
- Version: [e.g. 8.1]
- Browser: [e.g. Chrome]
**Additional context**
Add any other context about the problem here.
- Browser for Autofill: [e.g. Chrome version X]

7
.gitignore vendored
View File

@@ -38,13 +38,6 @@ proguard/
# Android Studio captures folder
captures/
# Eclipse/VS Code
.project
.settings/*
*/.project
*/.classpath
*/.settings/*
# Intellij
*.iml
.idea/workspace.xml

120
CHANGELOG
View File

@@ -1,72 +1,14 @@
KeePassDX(2.5beta31)
* Add write permission to keep compatibility with old file managers
* Fix autofill for apps
* Auto search for autofill
* New keyfile input
* Icon to hide keyfile input
* New lock button
* Setting to hide lock button in user interface
* Clickable links in notes
* Fix autofill for key-value pairs
KeePassDX(2.5beta30)
* Fix Lock after screen off (wait 1.5 seconds)
* Upgrade autofill algorithm
* Fix ANR during file verifications
KeePassDX(2.5beta29)
* Upgrade autofill algorithm
* Delete registered KeyFile after save new credentials
* Fix title and username entry view refresh after an update
* Fix database lock request (open notification always active)
* Allow empty title in entries
* Add expiration datetime
KeePassDX(2.5beta28)
* Fix read only database
* Upgrade to Android SDK 29
KeePassDX (2.5beta27)
* New setting to hide broken links
* Show URL when title is empty
* Setting to open search field at database opening
* Fix settings for database locations
* Fix error message when database file not writable
* Fix appearance refresh settings
* Sort optimization
KeePassDX (2.5.0.0beta26)
* Download attachments
* Change file size string format
* Prevent screenshot for all screen
* Auto performed "Go" key in Magikeyboard
* Restore and delete entry history
* Setting to hide expired entries
* New Black theme
* Fix crash when clearing clipboard
* Fix attachments compressions
* Fix dates
* Fix UUID message for Database v1
KeePassDX (2.5.0.0beta25)
* Setting for Recycle Bin
* Fix Recycle bin issues
* Fix TOTP
* Fix infinite save
* Fix update group
* Fix OOM
KeePassDX (2.5.0.0beta24)
* Add OTP (HOTP / TOTP)
KeepassDX (2.5.0.0beta24)
* Add settings (Color, Security, Master Key)
* Show history of each entry
* Auto repair database for nodes with same UUID
* Management of expired nodes
* Multi-selection for actions (Cut - Copy - Delete)
* Open/Save database as service / Add persistent notification
* Fix settings / edit group / small bugs
* Fix settings
* Fix edit group
* Fix small bugs
KeePassDX (2.5.0.0beta23)
KeepassDX (2.5.0.0beta23)
* New, more secure database creation workflow
* Recognize more database files
* Add alias for history files (WARNING: history is erased)
@@ -75,14 +17,14 @@ KeePassDX (2.5.0.0beta23)
* Fix OOM with KeyFile
* Fix small issues
KeePassDX (2.5.0.0beta22)
KeepassDX (2.5.0.0beta22)
* Rebuild code for actions
* Add UUID as entry view
* Fix bug with natural order
* Fix number of entries in databaseV1
* New entry views
KeePassDX (2.5.0.0beta21)
KeepassDX (2.5.0.0beta21)
* Fix nested groups no longer visible in V1 databases
* Improved data import algorithm for V1 databases
* Add natural database sort
@@ -90,10 +32,10 @@ KeePassDX (2.5.0.0beta21)
* Fix button disabled with only KeyFile
* Show the number of entries in a group
KeePassDX (2.5.0.0beta20)
KeepassDX (2.5.0.0beta20)
* Fix a major bug that displays an entry history
KeePassDX (2.5.0.0beta19)
KeepassDX (2.5.0.0beta19)
* Add lock button always visible
* New connection workflow
* Code refactored in Kotlin
@@ -104,7 +46,7 @@ KeePassDX (2.5.0.0beta19)
* Fix memory when load database
* Fix small bugs
KeePassDX (2.5.0.0beta18)
KeepassDX (2.5.0.0beta18)
* New recent databases views
* New information dialog
* Custom fields for the Magikeyboard
@@ -113,10 +55,10 @@ KeePassDX (2.5.0.0beta18)
* Fix memory when opening the database
* Memory management for attachments
KeePassDX (2.5.0.0beta17)
KeepassDX (2.5.0.0beta17)
* Fix font and search
KeePassDX (2.5.0.0beta16)
KeepassDX (2.5.0.0beta16)
* New search in a single fragment
* Search suggestions
* Added the display of usernames
@@ -124,20 +66,20 @@ KeePassDX (2.5.0.0beta16)
* Fix read-only mode
* Fix parcelable / toolbar / back
KeePassDX (2.5.0.0beta15)
KeepassDX (2.5.0.0beta15)
* Read only mode
* Best group recovery for the navigation fragment
* Fix copies in notifications
* Fix orientation
* Added translations
KeePassDX (2.5.0.0beta14)
KeepassDX (2.5.0.0beta14)
* Optimize all the memory with parcelables / fix search
KeePassDX (2.5.0.0beta13)
KeepassDX (2.5.0.0beta13)
* Fix memory issue with parcelable (crash in beta12 version)
KeePassDX (2.5.0.0beta12)
KeepassDX (2.5.0.0beta12)
* Added the Magikeyboard to fill the forms (settings still in development)
* Added move and copy for groups and entries
* New navigation in a single screen / new animations between activities
@@ -150,10 +92,10 @@ KeePassDX (2.5.0.0beta12)
* Fix the fingerprint recognition (WARNING : The keystore is reinit, you must delete the old keys)
* Fix small bugs
KeePassDX (2.5.0.0beta11)
KeepassDX (2.5.0.0beta11)
* Fix crash in beta10 version
KeePassDX (2.5.0.0beta10)
KeepassDX (2.5.0.0beta10)
* Dynamically change Algorithm and Key Derivation Function in settings
* Upgrade translations
* New red volcano theme, fix classic dark theme
@@ -161,7 +103,7 @@ KeePassDX (2.5.0.0beta10)
* Update fingerprint state with checkbox
* Fix bugs
KeePassDX (2.5.0.0beta9)
KeepassDX (2.5.0.0beta9)
* Education Screens to learn how to use the app
* New designs
* New custom font for character visibility
@@ -170,9 +112,9 @@ KeePassDX (2.5.0.0beta9)
* Change setting organisation
* Pro version
KeePassDX (2.5.0.0beta8)
KeepassDX (2.5.0.0beta8)
* Hide custom entries protected
* Best management of field references (https://KeePass.info/help/base/fieldrefs.html)
* Best management of field references (https://keepass.info/help/base/fieldrefs.html)
* Change database / default settings
* Add Autofill for search
* Add sorting by last access and by creation time
@@ -180,7 +122,7 @@ KeePassDX (2.5.0.0beta8)
* Refactor old code
* Fix bugs
KeePassDX (2.5.0.0beta7)
KeepassDX (2.5.0.0beta7)
* Rebuild Notifications
* Change links to https
* Add extended Ascii (ñæËÌÂÝÜ...)
@@ -189,10 +131,10 @@ KeePassDX (2.5.0.0beta7)
* Add setting to prevent the password copy
* Fix bugs
KeePassDX (2.5.0.0beta6)
KeepassDX (2.5.0.0beta6)
* Fix crash
KeePassDX (2.5.0.0beta5)
KeepassDX (2.5.0.0beta5)
* Autofill (Android O)
* Deletion for group
* New sorts with (Asc/Dsc, Groups before or after)
@@ -213,7 +155,7 @@ KeePassDX (2.5.0.0beta5)
* Fix many small bugs
* Add recycle bin setting (not yet accessible)
KeePassDX (2.5.0.0beta4)
KeepassDX (2.5.0.0beta4)
* Show only file name
* Setting for full path
* Add information for each database file
@@ -222,7 +164,7 @@ KeePassDX (2.5.0.0beta4)
* Delete view assignment for fingerprint opening
* Merge KeePassDroid 2.2.1
KeePassDX (2.5.0.0beta3)
KeepassDX (2.5.0.0beta3)
* New database workflow with new screens and folder selection
* Settings for default password generation
* Fingerprint dialog for explanations
@@ -233,17 +175,17 @@ KeePassDX (2.5.0.0beta3)
* Merge KeePassDroid 2.2.0.9
* Add corruption fix mode
KeePassDX (2.5.0.0beta2)
KeepassDX (2.5.0.0beta2)
* Remove libs for F-Droid
KeePassDX (2.5.0.0beta1)
* Fork KeePassDroid
KeepassDX (2.5.0.0beta1)
* Fork KeepassDroid
* Add Material Design
* Add Light and Night theme
* Min API is 14
* Solve bug for fingerprint
* Update French translation
* Change donation (see KeePassDroid to contribute on both projects)
* Change donation (see KeepassDroid to contribute on both projects)
KeePassDroid (2.2.1)
* Fix kdbx4 date corruption
@@ -504,7 +446,7 @@ KeePassDroid (1.9.10)
KeePassDroid (1.9.9)
* Go back to explicitly storing blank fields in the database
(works around bug in KeePassx)
(works around bug in keepassx)
* Add support for native code on MIPS architectures
* Adding Vibrate permission. On some devices notifications fail
without the vibrate permission.

View File

@@ -1,6 +1,6 @@
---
KeePassDX is free software: you can redistribute it and/or modify
KeePass DX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
@@ -13,7 +13,7 @@ KeePassDX is free software: you can redistribute it and/or modify
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
The KeePassDX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3.
The KeePass DX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3.
---

View File

@@ -1,6 +1,6 @@
# Android KeepassDX
# Android Keepass DX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeepassDX is a **multi-format KeePass manager for Android devices**. The application allows to create keys and passwords in a secure way by integrating with the Android design standards.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> Keepass DX is a **multi-format KeePass manager for Android devices**. The application allows to create keys and passwords in a secure way by integrating with the Android design standards.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
@@ -8,29 +8,28 @@
* Create database files / entries and groups
* Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePass XC...)
* Allows **fast copy** of fields and opening of URI / URL
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
* **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
* **Fingerprint** for fast unlocking
* Material design with **themes**
* **AutoFill** and Integration
* Field filling **keyboard**
* Precise management of **settings**
* Code written in **native language** *(Kotlin / Java / JNI / C)*
KeepassDX is **open source** and **ad-free**.
Keepass DX is **open source** and **ad-free**.
## What is KeePassDX?
## What is KeePass DX?
Today you need to remember many passwords. You need a password for your e-mail account, your website's FTP password, online passwords (like website member account), etc. etc. etc. The list is endless. Also, you **should use different passwords for each account**. Because if you use only one password everywhere and someone gets this password you have a problem... A serious problem. The thief would have access to your e-mail account, website, etc. Unimaginable.
KeePassDX is a **free open source password manager for Android**, which helps you to **manage your passwords in a secure way**. You can put all your passwords in one database, which is locked with one **master key** or a **key file**. So you **only have to remember one single master password or select the key file** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
KeePass DX is a **free open source password manager for Android**, which helps you to **manage your passwords in a secure way**. You can put all your passwords in one database, which is locked with one **master key** or a **key file**. So you **only have to remember one single master password or select the key file** to unlock the whole database. The databases are encrypted using the best and **most secure encryption algorithms** currently known.
## Is it really free?
Yes, KeePassDX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
*Note : If you access the application from a store, visual styles may not be available to encourage contribution to the work of open source projects. These optional styles are accessible after a contribution (and a congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
## Contributions
@@ -39,7 +38,7 @@ You can contribute in different ways to help us on our work.
* Add features by a **[pull request](https://help.github.com/articles/about-pull-requests/)**.
* Help to **[translate](https://hosted.weblate.org/projects/keepass-dx/strings/)** into your language (By using [Weblate](https://hosted.weblate.org/projects/keepass-dx/) or with a manual [pull request](https://help.github.com/articles/about-pull-requests/))
* **[Donate](https://www.kunzisoft.com/donation)** 人◕ ‿‿ ◕人Y for a better service and a quick development of your features.
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePassDX
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePass DX
## Download
@@ -53,35 +52,33 @@ You can contribute in different ways to help us on our work.
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
## Frequently Asked Questions
## F.A.Q.
Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
## Other devices
- [KeePass](https://keepass.info/) (https://keepass.info/) is the original and official project for desktop, with technical documentation for standardized database files. It is updated regularly with active maintenance (written in C#).
- [KeePass XC](https://keepassxc.org/) (https://keepassxc.org/) works with **GNU/Linux**, **Mac** and **Windows**, is updated regularly and under the terms of the GNU General Public License. This is the recommended version for computers.
- [KeePassXC](https://keepassxc.org/) (https://keepassxc.org/) is an alternative integration to KeePass written in C++.
- [KeeWeb](https://keeweb.info/) (https://keeweb.info/) is a web version also compatible with KeePass files.
- [KeePass](https://keepass.info/) (https://keepass.info/) is the historical project, with good technical documentation for standardized database files but only running on **Windows**.
## License
Copyright (c) 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePassDX.
This file is part of KeePass DX.
[KeePassDX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
[KeePass DX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePassDX is distributed in the hope that it will be useful,
KeePass DX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*

View File

@@ -1 +0,0 @@
theme: jekyll-theme-cayman

View File

@@ -4,28 +4,22 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
compileSdkVersion 28
buildToolsVersion '28.0.3'
ndkVersion "20.0.5594570"
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 29
versionCode = 31
versionName = "2.5RC1"
targetSdkVersion 28
versionCode = 24
versionName = "2.5.0.0beta24"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
kapt {
arguments {
arg("room.incremental", "true")
arg("room.schemaLocation", "$projectDir/schemas".toString())
}
}
}
externalNativeBuild {
@@ -34,6 +28,7 @@ android {
}
}
buildTypes {
release {
minifyEnabled = false
@@ -86,7 +81,7 @@ android {
}
def spongycastleVersion = "1.58.0.0"
def room_version = "2.2.5"
def room_version = "2.2.0"
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@@ -95,10 +90,7 @@ dependencies {
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.biometric:biometric:1.0.1'
implementation "androidx.core:core-ktx:1.2.0"
// To upgrade with style
implementation 'androidx.biometric:biometric:1.0.0-rc01'
implementation 'com.google.android.material:material:1.0.0'
implementation "androidx.room:room-runtime:$room_version"
@@ -115,8 +107,8 @@ dependencies {
// Apache Commons Collections
implementation 'commons-collections:commons-collections:3.2.1'
implementation 'org.apache.commons:commons-io:1.3.2'
// Apache Commons Codec
implementation 'commons-codec:commons-codec:1.11'
// Base64
implementation 'biz.source_code:base64coder:2010-12-19'
// Icon pack
implementation project(path: ':icon-pack-classic')
implementation project(path: ':icon-pack-material')

View File

@@ -1,84 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "56438e5f7372ef3e36e33b782aed245d",
"entities": [
{
"tableName": "file_database_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `database_alias` TEXT NOT NULL, `keyfile_uri` TEXT, `updated` INTEGER NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "databaseAlias",
"columnName": "database_alias",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "keyFileUri",
"columnName": "keyfile_uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "updated",
"columnName": "updated",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cipher_database",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `encrypted_value` TEXT NOT NULL, `specs_parameters` TEXT NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptedValue",
"columnName": "encrypted_value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "specParameters",
"columnName": "specs_parameters",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '56438e5f7372ef3e36e33b782aed245d')"
]
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests
import junit.framework.TestCase
import com.kunzisoft.keepass.database.element.PwDate
import org.junit.Assert
class PwDateTest : TestCase() {
fun testDate() {
val jDate = PwDate(System.currentTimeMillis())
val intermediate = PwDate(jDate)
val cDate = PwDate(intermediate.byteArrayDate!!, 0)
Assert.assertTrue("jDate and intermediate not equal", jDate == intermediate)
Assert.assertTrue("jDate and cDate not equal", cDate == jDate)
}
}

View File

@@ -1,33 +1,38 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.*
import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.*
class StringDatabaseKDBUtilsTest : TestCase() {
import java.io.ByteArrayOutputStream
import java.util.Calendar
import java.util.Random
import junit.framework.TestCase
import com.kunzisoft.keepass.database.element.PwDate
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.Types
class TypesTest : TestCase() {
fun testReadWriteLongZero() {
testReadWriteLong(0.toByte())
@@ -51,9 +56,15 @@ class StringDatabaseKDBUtilsTest : TestCase() {
private fun testReadWriteLong(value: Byte) {
val orig = ByteArray(8)
setArray(orig, value, 8)
val dest = ByteArray(8)
setArray(orig, value, 0, 8)
val one = LEDataInputStream.readLong(orig, 0)
LEDataOutputStream.writeLong(one, dest, 0)
assertArrayEquals(orig, dest)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
}
fun testReadWriteIntZero() {
@@ -70,22 +81,24 @@ class StringDatabaseKDBUtilsTest : TestCase() {
private fun testReadWriteInt(value: Byte) {
val orig = ByteArray(4)
val dest = ByteArray(4)
for (i in 0..3) {
orig[i] = 0
}
setArray(orig, value, 4)
setArray(orig, value, 0, 4)
val one = bytes4ToInt(orig)
val dest = intTo4Bytes(one)
val one = LEDataInputStream.readInt(orig, 0)
LEDataOutputStream.writeInt(one, dest, 0)
assertArrayEquals(orig, dest)
}
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
for (i in 0 until size) {
private fun setArray(buf: ByteArray, value: Byte, offset: Int, size: Int) {
for (i in offset until offset + size) {
buf[i] = value
}
}
@@ -96,10 +109,11 @@ class StringDatabaseKDBUtilsTest : TestCase() {
orig[0] = 0
orig[1] = 1
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
val one = LEDataInputStream.readUShort(orig, 0)
val dest = LEDataOutputStream.writeUShortBuf(one)
assertArrayEquals(orig, dest)
}
fun testReadWriteShortMin() {
@@ -112,12 +126,15 @@ class StringDatabaseKDBUtilsTest : TestCase() {
private fun testReadWriteShort(value: Byte) {
val orig = ByteArray(2)
setArray(orig, value, 2)
val dest = ByteArray(2)
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
setArray(orig, value, 0, 2)
val one = LEDataInputStream.readUShort(orig, 0)
LEDataOutputStream.writeUShort(one, dest, 0)
assertArrayEquals(orig, dest)
}
fun testReadWriteByteZero() {
@@ -133,8 +150,16 @@ class StringDatabaseKDBUtilsTest : TestCase() {
}
private fun testReadWriteByte(value: Byte) {
val dest: Byte = uIntToByte(byteToUInt(value))
assert(value == dest)
val orig = ByteArray(1)
val dest = ByteArray(1)
setArray(orig, value, 0, 1)
val one = Types.readUByte(orig, 0)
Types.writeUByte(one, dest, 0)
assertArrayEquals(orig, dest)
}
fun testDate() {
@@ -143,37 +168,27 @@ class StringDatabaseKDBUtilsTest : TestCase() {
val expected = Calendar.getInstance()
expected.set(2008, 1, 2, 3, 4, 5)
val buf = PwDate.writeTime(expected.time, cal)
val actual = Calendar.getInstance()
dateTo5Bytes(expected.time, cal)?.let { buf ->
actual.time = bytes5ToDate(buf, cal).date
}
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!)
actual.time = PwDate.readTime(buf, 0, cal)
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH))
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
assertTrue("jDate and intermediate not equal", jDate == intermediate)
assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
}
fun testUUID() {
val rnd = Random()
val bUUID = ByteArray(16)
Random().nextBytes(bUUID)
rnd.nextBytes(bUUID)
val uuid = bytes16ToUuid(bUUID)
val eUUID = uuidTo16Bytes(uuid)
val lUUID = bytes16ToUuid(bUUID)
val leUUID = uuidTo16Bytes(lUUID)
val uuid = Types.bytestoUUID(bUUID)
val eUUID = Types.UUIDtoBytes(uuid)
assertArrayEquals("UUID match failed", bUUID, eUUID)
assertArrayEquals("UUID match failed", bUUID, leUUID)
}
@Throws(Exception::class)
@@ -184,8 +199,8 @@ class StringDatabaseKDBUtilsTest : TestCase() {
}
val bos = ByteArrayOutputStream()
val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(ULONG_MAX_VALUE)
val leos = LEDataOutputStream(bos)
leos.writeLong(Types.ULONG_MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto
@@ -39,8 +39,9 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
import com.kunzisoft.keepass.stream.LEDataInputStream
class CipherTest : TestCase() {
private val rand = Random()
@@ -92,7 +93,7 @@ class CipherTest : TestCase() {
val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt)
val lis = LittleEndianDataInputStream(cis)
val lis = LEDataInputStream(cis)
val decrypttext = lis.readBytes(MESSAGE_LENGTH)

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.crypto

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.stream

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests.utils

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass"
android:installLocation="auto">
<supports-screens
@@ -8,14 +7,10 @@
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.USE_BIOMETRIC" />
<uses-permission
android:name="android.permission.VIBRATE"/>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:label="@string/app_name"
@@ -25,10 +20,7 @@
android:allowBackup="true"
android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true"
android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night"
tools:targetApi="n">
android:theme="@style/KeepassDXStyle.Night">
<!-- TODO backup API Key -->
<meta-data
android:name="com.google.android.backup.api_key"
@@ -132,35 +124,28 @@
<activity
android:name="com.kunzisoft.keepass.activities.AboutActivity"
android:launchMode="singleTask"
android:label="@string/about" />
android:label="@string/menu_about" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
android:configChanges="keyboardHidden" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
<activity android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
android:label="@string/keyboard_name"
android:exported="true">
</activity>
<activity android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
android:label="@string/keyboard_setting_label">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<service
android:name="com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name=".notifications.AttachmentFileNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
android:enabled="true"

View File

@@ -1,35 +1,35 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities
import android.content.pm.PackageManager.NameNotFoundException
import android.os.Bundle
import android.text.method.LinkMovementMethod
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.MenuItem
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.core.text.HtmlCompat
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import org.joda.time.DateTime
class AboutActivity : StylishActivity() {
@@ -40,7 +40,7 @@ class AboutActivity : StylishActivity() {
setContentView(R.layout.activity_about)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = getString(R.string.about)
toolbar.title = getString(R.string.menu_about)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
@@ -64,17 +64,9 @@ class AboutActivity : StylishActivity() {
val buildTextView = findViewById<TextView>(R.id.activity_about_build)
buildTextView.text = build
findViewById<TextView>(R.id.activity_about_licence_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_licence, DateTime().year),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
findViewById<TextView>(R.id.activity_about_contribution_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
val disclaimerText = findViewById<TextView>(R.id.disclaimer)
disclaimerText.text = getString(R.string.disclaimer_formal, DateTime().year)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.activities
@@ -22,72 +22,51 @@ import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import com.google.android.material.appbar.CollapsingToolbarLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.createDocument
import com.kunzisoft.keepass.utils.onCreateDocumentResult
import com.kunzisoft.keepass.view.EntryContentsView
import com.kunzisoft.keepass.view.showActionError
import java.util.*
import kotlin.collections.HashMap
class EntryActivity : LockingActivity() {
class EntryActivity : LockingHideActivity() {
private var coordinatorLayout: CoordinatorLayout? = null
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var titleIconView: ImageView? = null
private var historyView: View? = null
private var entryContentsView: EntryContentsView? = null
private var entryProgress: ProgressBar? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null
private var mDatabase: Database? = null
private var mEntry: Entry? = null
private var mEntry: EntryVersioned? = null
private var mIsHistory: Boolean = false
private var mEntryLastVersion: Entry? = null
private var mEntryHistoryPosition: Int = -1
private var mShowPassword: Boolean = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
private var clipboardHelper: ClipboardHelper? = null
private var firstLaunchOfActivity: Boolean = false
@@ -117,62 +96,29 @@ class EntryActivity : LockingActivity() {
invalidateOptionsMenu()
// Get views
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container)
entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryProgress = findViewById(R.id.entry_progress)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
// Init the clipboard helper
clipboardHelper = ClipboardHelper(this)
firstLaunchOfActivity = true
// Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
mProgressDialogThread?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
// Close the current activity after an history action
if (result.isSuccess)
finish()
}
}
coordinatorLayout?.showActionError(result)
}
}
override fun onResume() {
super.onResume()
// Show the lock button
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
// Get Entry from UUID
try {
val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY)
if (keyEntry != null) {
mEntry = mDatabase?.getEntryById(keyEntry)
mEntryLastVersion = mEntry
}
val keyEntry: PwNodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
mEntry = mDatabase?.getEntryById(keyEntry)
} catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key")
}
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
mEntryHistoryPosition = historyPosition
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
if (historyPosition >= 0) {
mIsHistory = true
mEntry = mEntry?.getHistory()?.get(historyPosition)
@@ -206,25 +152,10 @@ class EntryActivity : LockingActivity() {
}
}
mAttachmentFileBinderManager?.apply {
registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
entryContentsView?.updateAttachmentDownloadProgress(attachment)
}
}
}
firstLaunchOfActivity = false
}
override fun onPause() {
mAttachmentFileBinderManager?.unregisterProgressTask()
super.onPause()
}
private fun fillEntryDataInContentsView(entry: Entry) {
private fun fillEntryDataInContentsView(entry: EntryVersioned) {
val database = Database.getInstance()
database.startManageEntry(entry)
@@ -257,7 +188,7 @@ class EntryActivity : LockingActivity() {
"\n\n" +
getString(R.string.clipboard_warning))
.create().apply {
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) { dialog, _ ->
setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) {dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
dialog.dismiss()
fillEntryDataInContentsView(entry)
@@ -289,17 +220,6 @@ class EntryActivity : LockingActivity() {
}
}
//Assign OTP field
entryContentsView?.assignOtp(entry.getOtpElement(), entryProgress,
View.OnClickListener {
entry.getOtpElement()?.let { otpElement ->
clipboardHelper?.timeoutCopyToClipboard(
otpElement.token,
getString(R.string.copy_field, getString(R.string.entry_otp))
)
}
})
entryContentsView?.assignURL(entry.url)
entryContentsView?.assignComment(entry.notes)
@@ -331,27 +251,6 @@ class EntryActivity : LockingActivity() {
}
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
// Manage attachments
val attachments = entry.getAttachments()
val showAttachmentsView = attachments.isNotEmpty()
entryContentsView?.showAttachments(showAttachmentsView)
if (showAttachmentsView) {
entryContentsView?.assignAttachments(attachments)
entryContentsView?.onAttachmentClick { attachmentItem, _ ->
when (attachmentItem.downloadState) {
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
createDocument(this, attachmentItem.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentItem
}
}
else -> {
// TODO Stop download
}
}
}
}
entryContentsView?.refreshAttachments()
// Assign dates
entryContentsView?.assignCreationDate(entry.creationTime)
entryContentsView?.assignModificationDate(entry.lastModificationTime)
@@ -363,6 +262,9 @@ class EntryActivity : LockingActivity() {
entryContentsView?.assignExpiresDate(getString(R.string.never))
}
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
// Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
if (mIsHistory) {
@@ -371,26 +273,21 @@ class EntryActivity : LockingActivity() {
taColorAccent.recycle()
}
val entryHistory = entry.getHistory()
// TODO isMainEntry = not an history
// isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) {
entryContentsView?.assignHistory(entryHistory)
entryContentsView?.onHistoryClick { historyItem, position ->
launch(this, historyItem, mReadOnly, position)
launch(this, historyItem, true, position)
}
}
entryContentsView?.refreshHistory()
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
database.stopManageEntry(entry)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
// Not directly get the entry from intent data but from database
@@ -398,15 +295,6 @@ class EntryActivity : LockingActivity() {
fillEntryDataInContentsView(it)
}
}
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
}
}
}
}
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
@@ -425,12 +313,9 @@ class EntryActivity : LockingActivity() {
val inflater = menuInflater
MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu)
if (mIsHistory && !mReadOnly) {
inflater.inflate(R.menu.entry_history, menu)
}
if (mIsHistory || mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
inflater.inflate(R.menu.database_lock, menu)
if (mReadOnly) {
menu.findItem(R.id.menu_edit)?.isVisible = false
}
@@ -474,7 +359,8 @@ class EntryActivity : LockingActivity() {
getString(R.string.entry_user_name)))
},
{
performedNextEducation(entryActivityEducation, menu)
// Launch autofill settings
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
})
if (!entryCopyEducationPerformed) {
@@ -498,18 +384,21 @@ class EntryActivity : LockingActivity() {
MenuUtil.onContributionItemSelected(this)
return true
}
R.id.menu_toggle_pass -> {
mShowPassword = !mShowPassword
changeShowPasswordIcon(item)
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
return true
}
R.id.menu_edit -> {
mEntry?.let {
EntryEditActivity.launch(this@EntryActivity, it)
}
return true
}
R.id.menu_goto_url -> {
var url: String = mEntry?.url ?: ""
@@ -519,39 +408,30 @@ class EntryActivity : LockingActivity() {
}
UriUtil.gotoUrl(this, url)
return true
}
R.id.menu_restore_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseRestoreEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_delete_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseDeleteEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
R.id.menu_lock -> {
lockAndExit()
return true
}
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item)
}
override fun finish() {
// Transit data in previous Activity after an update
Intent().apply {
putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry)
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, this)
}
/*
TODO Slowdown when add entry as result
Intent intent = new Intent();
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish()
}
@@ -561,7 +441,7 @@ class EntryActivity : LockingActivity() {
const val KEY_ENTRY = "KEY_ENTRY"
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
fun launch(activity: Activity, entry: Entry, readOnly: Boolean, historyPosition: Int? = null) {
fun launch(activity: Activity, entry: EntryVersioned, readOnly: Boolean, historyPosition: Int? = null) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, entry.nodeId)

View File

@@ -1,26 +1,24 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Intent
import android.os.Bundle
import android.os.Handler
@@ -28,60 +26,43 @@ import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.DatePicker
import android.widget.TimePicker
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.ActionMenuView
import android.widget.ScrollView
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.widget.NestedScrollView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.education.EntryEditActivityEducation
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView
import com.kunzisoft.keepass.view.showActionError
import org.joda.time.DateTime
import java.util.*
class EntryEditActivity : LockingActivity(),
class EntryEditActivity : LockingHideActivity(),
IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener,
DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener {
GeneratePasswordDialogFragment.GeneratePasswordListener {
private var mDatabase: Database? = null
// Refs of an entry and group in database, are not modifiable
private var mEntry: Entry? = null
private var mParent: Group? = null
private var mEntry: EntryVersioned? = null
private var mParent: GroupVersioned? = null
// New or copy of mEntry in the database to be modifiable
private var mNewEntry: Entry? = null
private var mNewEntry: EntryVersioned? = null
private var mIsNew: Boolean = false
// Views
private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: NestedScrollView? = null
private var scrollView: ScrollView? = null
private var entryEditContentsView: EntryEditContentsView? = null
private var entryEditAddToolBar: ActionMenuView? = null
private var saveView: View? = null
private var lockView: View? = null
// Dialog thread
private var progressDialogThread: ProgressDialogThread? = null
// Education
private var entryEditActivityEducation: EntryEditActivityEducation? = null
@@ -96,40 +77,19 @@ class EntryEditActivity : LockingActivity(),
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
scrollView = findViewById(R.id.entry_edit_scroll)
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
entryEditContentsView = findViewById(R.id.entry_edit_contents)
entryEditContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
entryEditContentsView?.onDateClickListener = View.OnClickListener {
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
val dateTime = DateTime(expiresDate)
val defaultYear = dateTime.year
val defaultMonth = dateTime.monthOfYear-1
val defaultDay = dateTime.dayOfMonth
DatePickerFragment.getInstance(defaultYear, defaultMonth, defaultDay)
.show(supportFragmentManager, "DatePickerFragment")
}
}
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
// Likely the app has been killed exit the activity
mDatabase = Database.getInstance()
// Entry is retrieve, it's an entry to update
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
intent.getParcelableExtra<PwNodeId<UUID>>(KEY_ENTRY)?.let {
mIsNew = false
// Create an Entry copy to modify from the database entry
mEntry = mDatabase?.getEntryById(it)
@@ -149,7 +109,7 @@ class EntryEditActivity : LockingActivity(),
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
mEntry?.let { entry ->
// Create a copy to modify
mNewEntry = Entry(entry).also { newEntry ->
mNewEntry = EntryVersioned(entry).also { newEntry ->
// WARNING Remove the parent to keep memory with parcelable
newEntry.removeParent()
}
@@ -158,7 +118,7 @@ class EntryEditActivity : LockingActivity(),
}
// Parent is retrieve, it's a new entry to create
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let {
mIsNew = true
// Create an empty new entry
if (savedInstanceState == null
@@ -192,51 +152,20 @@ class EntryEditActivity : LockingActivity(),
// Add listener to the icon
entryEditContentsView?.setOnIconViewClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) }
// Bottom Bar
entryEditAddToolBar = findViewById(R.id.entry_edit_bottom_bar)
entryEditAddToolBar?.apply {
menuInflater.inflate(R.menu.entry_edit, menu)
menu.findItem(R.id.menu_add_field).apply {
val allowCustomField = mNewEntry?.allowCustomFields() == true
isEnabled = allowCustomField
isVisible = allowCustomField
}
menu.findItem(R.id.menu_add_otp).apply {
val allowOTP = mDatabase?.allowOTP == true
isEnabled = allowOTP
isVisible = allowOTP
}
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.menu_generate_password -> {
openPasswordGenerator()
true
}
R.id.menu_add_field -> {
addNewCustomField()
true
}
R.id.menu_add_otp -> {
setupOTP()
true
}
else -> true
}
}
}
// Generate password button
entryEditContentsView?.setOnPasswordGeneratorClickListener { openPasswordGenerator() }
// Save button
saveView = findViewById(R.id.entry_edit_validate)
saveView = findViewById(R.id.entry_edit_save)
saveView?.setOnClickListener { saveEntry() }
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) { addNewCustomField() }
// Verify the education views
entryEditActivityEducation = EntryEditActivityEducation(this)
// Create progress dialog
mProgressDialogThread?.onActionFinish = { actionTask, result ->
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_ENTRY_TASK,
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
@@ -244,21 +173,10 @@ class EntryEditActivity : LockingActivity(),
finish()
}
}
coordinatorLayout?.showActionError(result)
}
}
override fun onResume() {
super.onResume()
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
}
private fun populateViewsWithEntry(newEntry: Entry) {
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
// Don't start the field reference manager, we want to see the raw ref
mDatabase?.stopManageEntry(newEntry)
@@ -271,19 +189,16 @@ class EntryEditActivity : LockingActivity(),
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
url = newEntry.url
password = newEntry.password
expires = newEntry.expires
if (expires)
expiresDate = newEntry.expiryTime
notes = newEntry.notes
for (entry in newEntry.customFields.entries) {
post {
putCustomField(entry.key, entry.value)
addNewCustomField(entry.key, entry.value)
}
}
}
}
private fun populateEntryWithViews(newEntry: Entry) {
private fun populateEntryWithViews(newEntry: EntryVersioned) {
mDatabase?.startManageEntry(newEntry)
@@ -295,13 +210,9 @@ class EntryEditActivity : LockingActivity(),
username = entryView.username
url = entryView.url
password = entryView.password
expires = entryView.expires
if (entryView.expires) {
expiryTime = entryView.expiresDate
}
notes = entryView. notes
notes = entryView.notes
entryView.customFields.forEach { customField ->
putExtraField(customField.name, customField.protectedValue)
addExtraField(customField.name, customField.protectedValue)
}
}
}
@@ -309,7 +220,7 @@ class EntryEditActivity : LockingActivity(),
mDatabase?.stopManageEntry(newEntry)
}
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) {
mNewEntry?.icon = icon
mDatabase?.drawFactory?.let { iconDrawFactory ->
entryEditContentsView?.setIcon(iconDrawFactory, icon)
@@ -327,14 +238,7 @@ class EntryEditActivity : LockingActivity(),
* Add a new customized field view and scroll to bottom
*/
private fun addNewCustomField() {
entryEditContentsView?.addEmptyCustomField()
}
private fun setupOTP() {
// Retrieve the current otpElement if exists
// and open the dialog to set up the OTP
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
.show(supportFragmentManager, "addOTPDialog")
entryEditContentsView?.addNewCustomField()
}
/**
@@ -350,26 +254,26 @@ class EntryEditActivity : LockingActivity(),
// WARNING Add the parent previously deleted
newEntry.parent = mEntry?.parent
// Build info
newEntry.lastAccessTime = DateInstant()
newEntry.lastModificationTime = DateInstant()
newEntry.lastAccessTime = PwDate()
newEntry.lastModificationTime = PwDate()
populateEntryWithViews(newEntry)
// Open a progress dialog and save entry
if (mIsNew) {
mParent?.let { parent ->
mProgressDialogThread?.startDatabaseCreateEntry(
progressDialogThread?.startDatabaseCreateEntry(
newEntry,
parent,
!mReadOnly && mAutoSaveEnable
!mReadOnly
)
}
} else {
mEntry?.let { oldEntry ->
mProgressDialogThread?.startDatabaseUpdateEntry(
progressDialogThread?.startDatabaseUpdateEntry(
oldEntry,
newEntry,
!mReadOnly && mAutoSaveEnable
!mReadOnly
)
}
}
@@ -377,13 +281,23 @@ class EntryEditActivity : LockingActivity(),
}
}
override fun onResume() {
super.onResume()
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
inflater.inflate(R.menu.database, menu)
// Save database not needed here
menu.findItem(R.id.menu_save_database)?.isVisible = false
inflater.inflate(R.menu.database_lock, menu)
MenuUtil.contributionMenuInflater(inflater, menu)
entryEditActivityEducation?.let {
@@ -394,10 +308,12 @@ class EntryEditActivity : LockingActivity(),
}
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
val passwordGeneratorView: View? = entryEditAddToolBar?.findViewById(R.id.menu_generate_password)
val generatePasswordEducationPerformed = passwordGeneratorView != null
val passwordView = entryEditContentsView?.generatePasswordView
val addNewFieldView = entryEditContentsView?.addNewFieldView
val generatePasswordEducationPerformed = passwordView != null
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
passwordGeneratorView,
passwordView,
{
openPasswordGenerator()
},
@@ -406,95 +322,41 @@ class EntryEditActivity : LockingActivity(),
}
)
if (!generatePasswordEducationPerformed) {
val addNewFieldView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_field)
val addNewFieldEducationPerformed = mNewEntry != null
&& mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
// entryNewFieldEducationPerformed
mNewEntry != null && mNewEntry!!.allowCustomFields() && mNewEntry!!.customFields.isEmpty()
&& addNewFieldView != null && addNewFieldView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation(
addNewFieldView,
{
addNewCustomField()
},
{
performedNextEducation(entryEditActivityEducation)
}
)
if (!addNewFieldEducationPerformed) {
val setupOtpView: View? = entryEditAddToolBar?.findViewById(R.id.menu_add_otp)
setupOtpView != null && setupOtpView.visibility == View.VISIBLE
&& entryEditActivityEducation.checkAndPerformedSetUpOTPEducation(
setupOtpView,
{
setupOTP()
})
}
})
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
R.id.menu_lock -> {
lockAndExit()
return true
}
R.id.menu_contribute -> {
MenuUtil.onContributionItemSelected(this)
return true
}
android.R.id.home -> {
onBackPressed()
}
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
override fun onOtpCreated(otpElement: OtpElement) {
// Update the otp field with otpauth:// url
val otpField = OtpEntryFields.buildOtpField(otpElement,
mEntry?.title, mEntry?.username)
entryEditContentsView?.putCustomField(otpField.name, otpField.protectedValue)
mEntry?.putExtraField(otpField.name, otpField.protectedValue)
}
override fun iconPicked(bundle: Bundle) {
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
temporarilySaveAndShowSelectedIcon(icon)
}
}
override fun onDateSet(datePicker: DatePicker?, year: Int, month: Int, day: Int) {
// To fix android 4.4 issue
// https://stackoverflow.com/questions/12436073/datepicker-ondatechangedlistener-called-twice
if (datePicker?.isShown == true) {
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
// Save the date
entryEditContentsView?.expiresDate =
DateInstant(DateTime(expiresDate)
.withYear(year)
.withMonthOfYear(month + 1)
.withDayOfMonth(day)
.toDate())
// Launch the time picker
val dateTime = DateTime(expiresDate)
val defaultHour = dateTime.hourOfDay
val defaultMinute = dateTime.minuteOfHour
TimePickerFragment.getInstance(defaultHour, defaultMinute)
.show(supportFragmentManager, "TimePickerFragment")
}
}
}
override fun onTimeSet(timePicker: TimePicker?, hours: Int, minutes: Int) {
entryEditContentsView?.expiresDate?.date?.let { expiresDate ->
// Save the date
entryEditContentsView?.expiresDate =
DateInstant(DateTime(expiresDate)
.withHourOfDay(hours)
.withMinuteOfHour(minutes)
.toDate())
}
}
override fun onSaveInstanceState(outState: Bundle) {
mNewEntry?.let {
populateEntryWithViews(it)
@@ -518,15 +380,6 @@ class EntryEditActivity : LockingActivity(),
// Do nothing here
}
override fun onBackPressed() {
AlertDialog.Builder(this)
.setMessage(R.string.discard_changes)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.discard) { _, _ ->
super@EntryEditActivity.onBackPressed()
}.create().show()
}
override fun finish() {
// Assign entry callback as a result in all case
try {
@@ -571,7 +424,7 @@ class EntryEditActivity : LockingActivity(),
* @param activity from activity
* @param pwEntry Entry to update
*/
fun launch(activity: Activity, pwEntry: Entry) {
fun launch(activity: Activity, pwEntry: EntryVersioned) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
@@ -585,7 +438,7 @@ class EntryEditActivity : LockingActivity(),
* @param activity from activity
* @param pwGroup Group who will contains new entry
*/
fun launch(activity: Activity, pwGroup: Group) {
fun launch(activity: Activity, pwGroup: GroupVersioned) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryEditActivity::class.java)
intent.putExtra(KEY_PARENT, pwGroup.nodeId)

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities
@@ -26,34 +26,35 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.preference.PreferenceManager
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.*
import java.io.FileNotFoundException
@@ -62,8 +63,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
AssignMasterKeyDialogFragment.AssignPasswordDialogListener {
// Views
private var coordinatorLayout: CoordinatorLayout? = null
private var fileManagerExplanationButton: View? = null
private var fileListContainer: View? = null
private var createButtonView: View? = null
private var openDatabaseButtonView: View? = null
@@ -76,7 +76,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
private var mOpenFileHelper: OpenFileHelper? = null
private var mProgressDialogThread: ProgressDialogThread? = null
private var progressDialogThread: ProgressDialogThread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -84,38 +84,31 @@ class FileDatabaseSelectActivity : StylishActivity(),
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(applicationContext)
setContentView(R.layout.activity_file_selection)
coordinatorLayout = findViewById(R.id.activity_file_selection_coordinator_layout)
fileListContainer = findViewById(R.id.container_file_list)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = ""
setSupportActionBar(toolbar)
fileManagerExplanationButton = findViewById(R.id.file_manager_explanation_button)
fileManagerExplanationButton?.setOnClickListener {
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
}
// Create button
createButtonView = findViewById(R.id.create_database_button)
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE
}
else{
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}.resolveActivity(packageManager) == null) {
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
else{
// There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE
}
createButtonView?.setOnClickListener { createNewFile() }
mOpenFileHelper = OpenFileHelper(this)
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
openDatabaseButtonView?.apply {
mOpenFileHelper?.openFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
openDatabaseButtonView = findViewById(R.id.open_database_button)
openDatabaseButtonView?.setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
// History list
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
@@ -130,6 +123,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
databaseFileUri,
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
}
updateFileListVisibility()
}
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
// Remove from app database
@@ -138,6 +132,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}
}
true
@@ -151,7 +146,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
if (!(savedInstanceState != null
&& savedInstanceState.containsKey(EXTRA_STAY)
&& savedInstanceState.getBoolean(EXTRA_STAY, false))) {
val databasePath = PreferencesUtil.getDefaultDatabasePath(this)
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val databasePath = prefs.getString(PasswordActivity.KEY_DEFAULT_DATABASE_PATH, "")
UriUtil.parse(databasePath)?.let { databaseFileUri ->
launchPasswordActivityWithPath(databaseFileUri)
@@ -167,12 +163,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Attach the dialog thread to this activity
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, _ ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
GroupActivity.launch(this@FileDatabaseSelectActivity)
}
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_CREATE_TASK -> {
// TODO Check
// mAdapterDatabaseHistory?.notifyDataSetChanged()
// updateFileListVisibility()
GroupActivity.launch(this)
}
}
}
@@ -183,15 +180,23 @@ class FileDatabaseSelectActivity : StylishActivity(),
*/
@SuppressLint("InlinedApi")
private fun createNewFile() {
createDocument(this, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass")
try {
startActivityForResult(Intent(
Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default))
},
CREATE_FILE_REQUEST_CODE)
} catch (e: Exception) {
BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
}
}
private fun fileNoFoundAction(e: FileNotFoundException) {
val error = getString(R.string.file_not_found_content)
coordinatorLayout?.let {
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
}
Snackbar.make(activity_file_selection_coordinator_layout, error, Snackbar.LENGTH_LONG).asError().show()
Log.e(TAG, error, e)
}
@@ -219,8 +224,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
try {
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
databaseUri, keyFile,
assistStructure,
intent.getParcelableExtra(KEY_SEARCH_INFO))
assistStructure)
} catch (e: FileNotFoundException) {
fileNoFoundAction(e)
}
@@ -232,21 +236,16 @@ class FileDatabaseSelectActivity : StylishActivity(),
private fun launchGroupActivity(readOnly: Boolean) {
EntrySelectionHelper.doEntrySelectionAction(intent,
{
GroupActivity.launch(this@FileDatabaseSelectActivity,
readOnly)
GroupActivity.launch(this@FileDatabaseSelectActivity, readOnly)
},
{
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity,
readOnly)
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity, readOnly)
// Do not keep history
finish()
},
{ assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
assistStructure,
intent.getParcelableExtra(KEY_SEARCH_INFO),
readOnly)
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity, assistStructure, readOnly)
}
})
}
@@ -258,6 +257,25 @@ class FileDatabaseSelectActivity : StylishActivity(),
overridePendingTransition(0, 0)
}
private fun updateExternalStorageWarning() {
// To show errors
var warning = -1
val state = Environment.getExternalStorageState()
if (state == Environment.MEDIA_MOUNTED_READ_ONLY) {
warning = R.string.read_only_warning
} else if (state != Environment.MEDIA_MOUNTED) {
warning = R.string.warning_unmounted
}
val labelWarningView = findViewById<TextView>(R.id.label_warning)
if (warning != -1) {
labelWarningView.setText(warning)
labelWarningView.visibility = View.VISIBLE
} else {
labelWarningView.visibility = View.INVISIBLE
}
}
override fun onResume() {
val database = Database.getInstance()
if (database.loaded) {
@@ -266,35 +284,24 @@ class FileDatabaseSelectActivity : StylishActivity(),
super.onResume()
updateExternalStorageWarning()
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this)) {
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let { historyList ->
val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity)
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(
// Show only uri accessible
historyList.filter {
if (hideBrokenLocations) {
FileDatabaseInfo(this@FileDatabaseSelectActivity,
it.databaseUri).exists
} else
true
})
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let {
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
updateFileListVisibility()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
}
// Register progress task
mProgressDialogThread?.registerProgressTask()
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
// Unregister progress task
mProgressDialogThread?.unregisterProgressTask()
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
@@ -307,6 +314,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
}
private fun updateFileListVisibility() {
if (mAdapterDatabaseHistory?.itemCount == 0)
fileListContainer?.visibility = View.INVISIBLE
else
fileListContainer?.visibility = View.VISIBLE
}
override fun onAssignKeyDialogPositiveClick(
masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?) {
@@ -315,7 +329,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
mDatabaseFileUri?.let { databaseUri ->
// Create the new database
mProgressDialogThread?.startDatabaseCreate(
progressDialogThread?.startDatabaseCreate(
databaseUri,
masterPasswordChecked,
masterPassword,
@@ -351,18 +365,15 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Retrieve the created URI from the file manager
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
mDatabaseFileUri = data?.data
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
} else {
val error = getString(R.string.error_create_database)
coordinatorLayout?.let {
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
}
Log.e(TAG, error)
}
// else {
// TODO Show error
// }
}
}
@@ -414,6 +425,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
private const val EXTRA_STAY = "EXTRA_STAY"
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
private const val CREATE_FILE_REQUEST_CODE = 3853
/*
* -------------------------
* No Standard Launch, pass by PasswordActivity
@@ -437,13 +450,10 @@ class FileDatabaseSelectActivity : StylishActivity(),
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure,
searchInfo: SearchInfo?) {
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure) {
AutofillHelper.startActivityForAutofillResult(activity,
Intent(activity, FileDatabaseSelectActivity::class.java),
assistStructure,
searchInfo)
assistStructure)
}
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.activities
@@ -42,27 +42,21 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentManager
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.ReadOnlyDialog
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.education.GroupActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
@@ -77,20 +71,17 @@ import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.AddNodeButtonView
import com.kunzisoft.keepass.view.ToolbarAction
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionError
class GroupActivity : LockingActivity(),
GroupEditDialogFragment.EditGroupListener,
IconPickerDialogFragment.IconPickerListener,
ListNodesFragment.NodeClickListener,
ListNodesFragment.NodesActionMenuListener,
DeleteNodesDialogFragment.DeleteNodeListener,
ListNodesFragment.OnScrollListener,
SortDialogFragment.SortSelectionListener {
// Views
private var coordinatorLayout: CoordinatorLayout? = null
private var lockView: View? = null
private var toolbar: Toolbar? = null
private var searchTitleView: View? = null
private var toolbarAction: ToolbarAction? = null
@@ -104,12 +95,15 @@ class GroupActivity : LockingActivity(),
private var mListNodesFragment: ListNodesFragment? = null
private var mCurrentGroupIsASearch: Boolean = false
private var mRequestStartupSearch = true
private var progressDialogThread: ProgressDialogThread? = null
// Nodes
private var mRootGroup: Group? = null
private var mCurrentGroup: Group? = null
private var mOldGroupToUpdate: Group? = null
private var mRootGroup: GroupVersioned? = null
private var mCurrentGroup: GroupVersioned? = null
private var mOldGroupToUpdate: GroupVersioned? = null
// TODO private var mNodeToCopy: NodeVersioned? = null
// TODO private var mNodeToMove: NodeVersioned? = null
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
@@ -136,22 +130,23 @@ class GroupActivity : LockingActivity(),
groupNameView = findViewById(R.id.group_name)
toolbarAction = findViewById(R.id.toolbar_action)
modeTitleView = findViewById(R.id.mode_title_view)
lockView = findViewById(R.id.lock_button)
lockView?.setOnClickListener {
lockAndExit()
}
toolbar?.title = ""
setSupportActionBar(toolbar)
/*
toolbarAction?.setNavigationOnClickListener {
toolbarAction?.collapse()
mNodeToCopy = null
mNodeToMove = null
}
*/
// Focus view to reinitialize timeout
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
// Retrieve elements after an orientation change
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(REQUEST_STARTUP_SEARCH_KEY))
mRequestStartupSearch = savedInstanceState.getBoolean(REQUEST_STARTUP_SEARCH_KEY)
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY)
}
@@ -212,13 +207,13 @@ class GroupActivity : LockingActivity(),
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database)
// Init dialog thread
mProgressDialogThread?.onActionFinish = { actionTask, result ->
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
var oldNodes: List<Node> = ArrayList()
var oldNodes: List<NodeVersioned> = ArrayList()
result.data?.getBundle(OLD_NODES_KEY)?.let { oldNodesBundle ->
oldNodes = getListNodesFromBundle(database, oldNodesBundle)
}
var newNodes: List<Node> = ArrayList()
var newNodes: List<NodeVersioned> = ArrayList()
result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle ->
newNodes = getListNodesFromBundle(database, newNodesBundle)
}
@@ -239,34 +234,39 @@ class GroupActivity : LockingActivity(),
ACTION_DATABASE_DELETE_NODES_TASK -> {
if (result.isSuccess) {
// Rebuild all the list to avoid bug when delete node from sort
mListNodesFragment?.rebuildList()
// Rebuild all the list the avoid bug when delete node from db sort
if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB) {
mListNodesFragment?.rebuildList()
} else {
// Use the old Nodes / entries unchanged with the old parent
mListNodesFragment?.removeNodes(oldNodes)
}
// Add trash in views list if it doesn't exists
if (database.isRecycleBinEnabled) {
val recycleBin = database.recycleBin
val currentGroup = mCurrentGroup
if (currentGroup != null && recycleBin != null
&& currentGroup != recycleBin) {
// Recycle bin already here, simply update it
if (mListNodesFragment?.contains(recycleBin) == true) {
if (mCurrentGroup != null && recycleBin != null
&& mCurrentGroup!!.parent == null
&& mCurrentGroup != recycleBin) {
if (mListNodesFragment?.contains(recycleBin) == true)
mListNodesFragment?.updateNode(recycleBin)
}
// Recycle bin not here, verify if parents are similar to add it
else if (currentGroup == recycleBin.parent) {
else
mListNodesFragment?.addNode(recycleBin)
}
}
}
}
}
}
coordinatorLayout?.showActionError(result)
if (!result.isSuccess) {
result.exception?.errorId?.let { errorId ->
coordinatorLayout?.let { coordinatorLayout ->
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
}
}
}
finishNodeAction()
refreshNumberOfChildren()
}
}
@@ -289,7 +289,7 @@ class GroupActivity : LockingActivity(),
}
}
private fun openSearchGroup(group: Group?) {
private fun openSearchGroup(group: GroupVersioned?) {
// Delete the previous search fragment
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
if (searchFragment != null) {
@@ -301,11 +301,11 @@ class GroupActivity : LockingActivity(),
openGroup(group, true)
}
private fun openChildGroup(group: Group) {
private fun openChildGroup(group: GroupVersioned) {
openGroup(group, false)
}
private fun openGroup(group: Group?, isASearch: Boolean) {
private fun openGroup(group: GroupVersioned?, isASearch: Boolean) {
// Check TimeoutHelper
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
// Open a group in a new fragment
@@ -342,23 +342,21 @@ class GroupActivity : LockingActivity(),
mOldGroupToUpdate?.let {
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, it)
}
outState.putBoolean(REQUEST_STARTUP_SEARCH_KEY, mRequestStartupSearch)
super.onSaveInstanceState(outState)
}
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): Group? {
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): GroupVersioned? {
// Force read only if the database is like that
mReadOnly = mDatabase?.isReadOnly == true || mReadOnly
// If it's a search
if (Intent.ACTION_SEARCH == intent.action) {
val searchString = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
return mDatabase?.createVirtualGroupFromSearch(searchString)
return mDatabase?.search(intent.getStringExtra(SearchManager.QUERY).trim { it <= ' ' })
}
// else a real group
else {
var pwGroupId: NodeId<*>? = null
var pwGroupId: PwNodeId<*>? = null
if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY)
} else {
@@ -367,7 +365,7 @@ class GroupActivity : LockingActivity(),
}
Log.w(TAG, "Creating tree view")
val currentGroup: Group?
val currentGroup: GroupVersioned?
currentGroup = if (pwGroupId == null) {
mRootGroup
} else {
@@ -424,7 +422,14 @@ class GroupActivity : LockingActivity(),
}
// Assign number of children
refreshNumberOfChildren()
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}
// Show selection mode message if needed
if (mSelectionMode) {
@@ -440,24 +445,18 @@ class GroupActivity : LockingActivity(),
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
mCurrentGroup?.let {
val isRoot = it == mRootGroup
if (!it.allowAddEntryIfIsRoot())
addEntryEnabled = it != mRootGroup && addEntryEnabled
addEntryEnabled = !isRoot && addEntryEnabled
if (isRoot) {
showWarnings()
}
}
enableAddGroup(addGroupEnabled)
enableAddEntry(addEntryEnabled)
showButton()
}
}
private fun refreshNumberOfChildren() {
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getNumberOfChildEntries(*Group.ChildFilter.getDefaults(context))?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
if (isEnable)
showButton()
}
}
@@ -465,16 +464,16 @@ class GroupActivity : LockingActivity(),
addNodeButtonView?.hideButtonOnScrollListener(dy)
}
override fun onNodeClick(node: Node) {
override fun onNodeClick(node: NodeVersioned) {
when (node.type) {
Type.GROUP -> try {
openChildGroup(node as Group)
openChildGroup(node as GroupVersioned)
} catch (e: ClassCastException) {
Log.e(TAG, "Node can't be cast in Group")
}
Type.ENTRY -> try {
val entryVersioned = node as Entry
val entryVersioned = node as EntryVersioned
EntrySelectionHelper.doEntrySelectionAction(intent,
{
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
@@ -492,10 +491,8 @@ class GroupActivity : LockingActivity(),
{
// Build response with the entry selected
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
mDatabase?.let { database ->
AutofillHelper.buildResponse(this@GroupActivity,
entryVersioned.getEntryInfo(database))
}
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity,
entryVersioned.getEntryInfo(mDatabase!!))
}
finish()
})
@@ -510,10 +507,9 @@ class GroupActivity : LockingActivity(),
private fun finishNodeAction() {
actionNodeMode?.finish()
actionNodeMode = null
addNodeButtonView?.showButton()
}
override fun onNodeSelected(nodes: List<Node>): Boolean {
override fun onNodeSelected(nodes: List<NodeVersioned>): Boolean {
if (nodes.isNotEmpty()) {
if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) {
mListNodesFragment?.actionNodesCallback(nodes, this)?.let {
@@ -522,41 +518,40 @@ class GroupActivity : LockingActivity(),
} else {
actionNodeMode?.invalidate()
}
addNodeButtonView?.hideButton()
} else {
finishNodeAction()
}
return true
}
override fun onOpenMenuClick(node: Node): Boolean {
override fun onOpenMenuClick(node: NodeVersioned): Boolean {
finishNodeAction()
onNodeClick(node)
return true
}
override fun onEditMenuClick(node: Node): Boolean {
override fun onEditMenuClick(node: NodeVersioned): Boolean {
finishNodeAction()
when (node.type) {
Type.GROUP -> {
mOldGroupToUpdate = node as Group
mOldGroupToUpdate = node as GroupVersioned
GroupEditDialogFragment.build(mOldGroupToUpdate!!)
.show(supportFragmentManager,
GroupEditDialogFragment.TAG_CREATE_GROUP)
}
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as Entry)
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as EntryVersioned)
}
return true
}
override fun onCopyMenuClick(nodes: List<Node>): Boolean {
override fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean {
actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally
return true
}
override fun onMoveMenuClick(nodes: List<Node>): Boolean {
override fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean {
actionNodeMode?.invalidate()
// Nothing here fragment calls onPasteMenuClick internally
@@ -564,95 +559,56 @@ class GroupActivity : LockingActivity(),
}
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
nodes: List<Node>): Boolean {
// Move or copy only if allowed (in root if allowed)
if (mCurrentGroup != mDatabase?.rootGroup
|| mDatabase?.rootCanContainsEntry() == true) {
when (pasteMode) {
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
// Copy
mCurrentGroup?.let { newParent ->
mProgressDialogThread?.startDatabaseCopyNodes(
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
mCurrentGroup?.let { newParent ->
mProgressDialogThread?.startDatabaseMoveNodes(
nodes,
newParent,
!mReadOnly && mAutoSaveEnable
)
}
}
else -> {
nodes: List<NodeVersioned>): Boolean {
when (pasteMode) {
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
// Copy
mCurrentGroup?.let { newParent ->
progressDialogThread?.startDatabaseCopyNodes(
nodes,
newParent,
!mReadOnly
)
}
}
} else {
coordinatorLayout?.let { coordinatorLayout ->
Snackbar.make(coordinatorLayout,
R.string.error_copy_entry_here,
Snackbar.LENGTH_LONG).asError().show()
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
// Move
mCurrentGroup?.let { newParent ->
progressDialogThread?.startDatabaseMoveNodes(
nodes,
newParent,
!mReadOnly
)
}
}
else -> {}
}
finishNodeAction()
return true
}
override fun onDeleteMenuClick(nodes: List<Node>): Boolean {
val database = mDatabase
// If recycle bin enabled, ensure it exists
if (database != null && database.isRecycleBinEnabled) {
database.ensureRecycleBinExists(resources)
}
// If recycle bin enabled and not in recycle bin, move in recycle bin
if (database != null
&& database.isRecycleBinEnabled
&& database.recycleBin != mCurrentGroup) {
mProgressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
)
}
// else open the dialog to confirm deletion
else {
DeleteNodesDialogFragment.getInstance(nodes)
.show(supportFragmentManager, "deleteNodesDialogFragment")
}
finishNodeAction()
return true
}
override fun permanentlyDeleteNodes(nodes: List<Node>) {
mProgressDialogThread?.startDatabaseDeleteNodes(
override fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean {
progressDialogThread?.startDatabaseDeleteNodes(
nodes,
!mReadOnly && mAutoSaveEnable
!mReadOnly
)
finishNodeAction()
return true
}
override fun onResume() {
super.onResume()
// Show the lock button
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
View.VISIBLE
} else {
View.GONE
}
// Refresh the elements
assignGroupViewElements()
// Refresh suggestions to change preferences
mSearchSuggestionAdapter?.reInit(this)
progressDialogThread?.registerProgressTask()
}
override fun onPause() {
progressDialogThread?.unregisterProgressTask()
super.onPause()
finishNodeAction()
@@ -662,32 +618,20 @@ class GroupActivity : LockingActivity(),
val inflater = menuInflater
inflater.inflate(R.menu.search, menu)
inflater.inflate(R.menu.database, menu)
if (mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
}
inflater.inflate(R.menu.database_lock, menu)
if (!mSelectionMode) {
inflater.inflate(R.menu.default_menu, menu)
MenuUtil.contributionMenuInflater(inflater, menu)
}
// Menu for recycle bin
if (!mReadOnly
&& mDatabase?.isRecycleBinEnabled == true
&& mDatabase?.recycleBin == mCurrentGroup) {
inflater.inflate(R.menu.recycle_bin, menu)
}
// Get the SearchView and set the searchable configuration
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
menu.findItem(R.id.menu_search)?.let {
val searchView = it.actionView as SearchView?
searchView?.apply {
(searchManager?.getSearchableInfo(
ComponentName(this@GroupActivity, GroupActivity::class.java)))?.let { searchableInfo ->
setSearchableInfo(searchableInfo)
}
setSearchableInfo(searchManager.getSearchableInfo(
ComponentName(this@GroupActivity, GroupActivity::class.java)))
setIconifiedByDefault(false) // Do not iconify the widget; expand it by default
suggestionsAdapter = mSearchSuggestionAdapter
setOnSuggestionListener(object : SearchView.OnSuggestionListener {
@@ -705,13 +649,6 @@ class GroupActivity : LockingActivity(),
}
})
}
// Expand the search view if defined in settings
if (mRequestStartupSearch
&& PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
// To request search only one time
mRequestStartupSearch = false
it.expandActionView()
}
}
super.onCreateOptionsMenu(menu)
@@ -767,11 +704,12 @@ class GroupActivity : LockingActivity(),
if (!sortMenuEducationPerformed) {
// lockMenuEducationPerformed
val lockButtonView = findViewById<View>(R.id.lock_button_icon)
lockButtonView != null
&& groupActivityEducation.checkAndPerformedLockMenuEducation(lockButtonView,
toolbar != null
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null
&& groupActivityEducation.checkAndPerformedLockMenuEducation(
toolbar!!.findViewById(R.id.menu_lock),
{
lockAndExit()
onOptionsItemSelected(menu.findItem(R.id.menu_lock))
},
{
performedNextEducation(groupActivityEducation, menu)
@@ -790,15 +728,8 @@ class GroupActivity : LockingActivity(),
R.id.menu_search ->
//onSearchRequested();
return true
R.id.menu_save_database -> {
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
return true
}
R.id.menu_empty_recycle_bin -> {
mCurrentGroup?.getChildren()?.let { listChildren ->
// Automatically delete all elements
onDeleteMenuClick(listChildren)
}
R.id.menu_lock -> {
lockAndExit()
return true
}
else -> {
@@ -811,7 +742,7 @@ class GroupActivity : LockingActivity(),
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?,
icon: IconImage?) {
icon: PwIcon?) {
if (name != null && name.isNotEmpty() && icon != null) {
when (action) {
@@ -825,33 +756,28 @@ class GroupActivity : LockingActivity(),
// Not really needed here because added in runnable but safe
newGroup.parent = currentGroup
mProgressDialogThread?.startDatabaseCreateGroup(
newGroup,
currentGroup,
!mReadOnly && mAutoSaveEnable
)
progressDialogThread?.startDatabaseCreateGroup(
newGroup, currentGroup, !mReadOnly)
}
}
}
GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> {
// If update add new elements
mOldGroupToUpdate?.let { oldGroupToUpdate ->
val updateGroup = Group(oldGroupToUpdate).let { updateGroup ->
GroupVersioned(oldGroupToUpdate).let { updateGroup ->
updateGroup.apply {
// WARNING remove parent and children to keep memory
removeParent()
removeChildren()
removeChildren() // TODO concurrent exception
title = name
this.icon = icon // TODO custom icon
}
// If group updated save it in the database
progressDialogThread?.startDatabaseUpdateGroup(
oldGroupToUpdate, updateGroup, !mReadOnly)
}
// If group updated save it in the database
mProgressDialogThread?.startDatabaseUpdateGroup(
oldGroupToUpdate,
updateGroup,
!mReadOnly && mAutoSaveEnable
)
}
}
else -> {}
@@ -861,7 +787,7 @@ class GroupActivity : LockingActivity(),
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
name: String?,
icon: IconImage?) {
icon: PwIcon?) {
// Do nothing here
}
@@ -872,8 +798,16 @@ class GroupActivity : LockingActivity(),
.iconPicked(bundle)
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum, sortNodeParameters: SortNodeEnum.SortNodeParameters) {
mListNodesFragment?.onSortSelected(sortNodeEnum, sortNodeParameters)
private fun showWarnings() {
if (Database.getInstance().isReadOnly) {
if (PreferencesUtil.showReadOnlyWarning(this)) {
ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
}
}
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom)
}
override fun startActivity(intent: Intent) {
@@ -917,8 +851,8 @@ class GroupActivity : LockingActivity(),
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
}
// Directly used the onActivityResult in fragment
mListNodesFragment?.onActivityResult(requestCode, resultCode, data)
// Not directly get the entry from intent data but from database
mListNodesFragment?.rebuildList()
}
private fun removeSearchInIntent(intent: Intent) {
@@ -959,47 +893,24 @@ class GroupActivity : LockingActivity(),
private val TAG = GroupActivity::class.java.name
private const val REQUEST_STARTUP_SEARCH_KEY = "REQUEST_STARTUP_SEARCH_KEY"
private const val GROUP_ID_KEY = "GROUP_ID_KEY"
private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG"
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
private fun buildIntent(context: Context,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(context, GroupActivity::class.java)
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.nodeId)
}
if (searchInfo != null) {
intent.action = Intent.ACTION_SEARCH
val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId
intent.putExtra(SearchManager.QUERY, searchQuery)
}
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intentBuildLauncher.invoke(intent)
}
private fun checkTimeAndBuildIntent(activity: Activity,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
buildIntent(activity, group, searchInfo, readOnly, intentBuildLauncher)
}
}
private fun checkTimeAndBuildIntent(context: Context,
group: Group?,
searchInfo: SearchInfo?,
readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
if (TimeoutHelper.checkTime(context)) {
buildIntent(context, group, searchInfo, readOnly, intentBuildLauncher)
private fun buildAndLaunchIntent(context: Context, group: GroupVersioned?, readOnly: Boolean,
intentBuildLauncher: (Intent) -> Unit) {
val checkTime = if (context is Activity)
TimeoutHelper.checkTimeAndLockIfTimeout(context)
else
TimeoutHelper.checkTime(context)
if (checkTime) {
val intent = Intent(context, GroupActivity::class.java)
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.nodeId)
}
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
intentBuildLauncher.invoke(intent)
}
}
@@ -1008,9 +919,11 @@ class GroupActivity : LockingActivity(),
* Standard Launch
* -------------------------
*/
fun launch(context: Context,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
@JvmOverloads
fun launch(context: Context, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
TimeoutHelper.recordTime(context)
buildAndLaunchIntent(context, null, readOnly) { intent ->
context.startActivity(intent)
}
}
@@ -1021,9 +934,10 @@ class GroupActivity : LockingActivity(),
* -------------------------
*/
// TODO implement pre search to directly open the direct group
fun launchForKeyboardSelection(context: Context,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
fun launchForKeyboardSelection(context: Context, readOnly: Boolean) {
TimeoutHelper.recordTime(context)
buildAndLaunchIntent(context, null, readOnly) { intent ->
EntrySelectionHelper.startActivityForEntrySelection(context, intent)
}
}
@@ -1033,13 +947,13 @@ class GroupActivity : LockingActivity(),
* Autofill Launch
* -------------------------
*/
// TODO implement pre search to directly open the direct group
@RequiresApi(api = Build.VERSION_CODES.O)
fun launchForAutofillResult(activity: Activity,
assistStructure: AssistStructure,
searchInfo: SearchInfo? = null,
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
checkTimeAndBuildIntent(activity, null, searchInfo, readOnly) { intent ->
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure, searchInfo)
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure, readOnly: Boolean) {
TimeoutHelper.recordTime(activity)
buildAndLaunchIntent(activity, null, readOnly) { intent ->
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure)
}
}
}

View File

@@ -1,27 +1,10 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.util.Log
@@ -35,16 +18,16 @@ import androidx.appcompat.view.ActionMode
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.adapters.NodeAdapter
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.activities.stylish.StylishFragment
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.Type
import java.util.*
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
@@ -52,8 +35,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private var nodeClickListener: NodeClickListener? = null
private var onScrollListener: OnScrollListener? = null
private var mNodesRecyclerView: RecyclerView? = null
var mainGroup: Group? = null
private var listView: RecyclerView? = null
var mainGroup: GroupVersioned? = null
private set
private var mAdapter: NodeAdapter? = null
@@ -61,12 +44,14 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private set
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
private set
private val listActionNodes = LinkedList<Node>()
private val listPasteNodes = LinkedList<Node>()
private val listActionNodes = LinkedList<NodeVersioned>()
private val listPasteNodes = LinkedList<NodeVersioned>()
private var notFoundView: View? = null
private var isASearchResult: Boolean = false
// Preferences for sorting
private var prefs: SharedPreferences? = null
private var readOnly: Boolean = false
get() {
@@ -118,7 +103,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
mAdapter = NodeAdapter(context)
mAdapter?.apply {
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
override fun onNodeClick(node: Node) {
override fun onNodeClick(node: NodeVersioned) {
if (nodeActionSelectionMode) {
if (listActionNodes.contains(node)) {
// Remove selected item if already selected
@@ -135,7 +120,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
override fun onNodeLongClick(node: Node): Boolean {
override fun onNodeLongClick(node: NodeVersioned): Boolean {
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
// Select the first item after a long click
if (!listActionNodes.contains(node))
@@ -151,6 +136,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
})
}
}
prefs = PreferenceManager.getDefaultSharedPreferences(context)
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -164,17 +150,11 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// To apply theme
val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_list_nodes, container, false)
mNodesRecyclerView = rootView.findViewById(R.id.nodes_list)
listView = rootView.findViewById(R.id.nodes_list)
notFoundView = rootView.findViewById(R.id.not_found_container)
mNodesRecyclerView?.apply {
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
layoutManager = LinearLayoutManager(context)
adapter = mAdapter
}
onScrollListener?.let { onScrollListener ->
mNodesRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrollListener.onScrolled(dy)
@@ -182,6 +162,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
})
}
rebuildList()
return rootView
}
@@ -193,14 +175,14 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Refresh data
rebuildList()
mAdapter?.notifyDataSetChanged()
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found "
mNodesRecyclerView?.visibility = View.GONE
listView?.visibility = View.GONE
notFoundView?.visibility = View.VISIBLE
} else {
mNodesRecyclerView?.visibility = View.VISIBLE
listView?.visibility = View.VISIBLE
notFoundView?.visibility = View.GONE
}
}
@@ -208,27 +190,30 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
fun rebuildList() {
// Add elements to the list
mainGroup?.let { mainGroup ->
mAdapter?.apply {
rebuildList(mainGroup)
// To visually change the elements
if (PreferencesUtil.APPEARANCE_CHANGED) {
notifyDataSetChanged()
PreferencesUtil.APPEARANCE_CHANGED = false
}
}
mAdapter?.rebuildList(mainGroup)
}
listView?.apply {
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
layoutManager = LinearLayoutManager(context)
adapter = mAdapter
}
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum,
sortNodeParameters: SortNodeEnum.SortNodeParameters) {
// Save setting
context?.let {
PreferencesUtil.saveNodeSort(it, sortNodeEnum, sortNodeParameters)
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
// Toggle setting
prefs?.edit()?.apply {
putString(getString(R.string.sort_node_key), sortNodeEnum.name)
putBoolean(getString(R.string.sort_ascending_key), ascending)
putBoolean(getString(R.string.sort_group_before_key), groupsBefore)
putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom)
apply()
}
// Tell the adapter to refresh it's list
mAdapter?.notifyChangeSort(sortNodeEnum, sortNodeParameters)
rebuildList()
mAdapter?.notifyChangeSort(sortNodeEnum, ascending, groupsBefore)
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -243,7 +228,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
R.id.menu_sort -> {
context?.let { context ->
val sortDialogFragment: SortDialogFragment =
if (Database.getInstance().isRecycleBinEnabled) {
if (Database.getInstance().allowRecycleBin
&& Database.getInstance().isRecycleBinEnabled) {
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(context),
PreferencesUtil.getAscendingSort(context),
@@ -265,7 +251,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
}
fun actionNodesCallback(nodes: List<Node>,
fun actionNodesCallback(nodes: List<NodeVersioned>,
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
return object : ActionMode.Callback {
@@ -290,8 +276,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Open and Edit for a single item
if (nodes.size == 1) {
// Edition
if (readOnly
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
if (readOnly || nodes[0] == database.recycleBin) {
menu?.removeItem(R.id.menu_edit)
}
} else {
@@ -302,6 +287,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// Copy and Move (not for groups)
if (readOnly
|| isASearchResult
|| nodes.any { it == database.recycleBin }
|| nodes.any { it.type == Type.GROUP }) {
// TODO COPY For Group
menu?.removeItem(R.id.menu_copy)
@@ -309,8 +295,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Deletion
if (readOnly
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
if (readOnly || nodes.any { it == database.recycleBin }) {
menu?.removeItem(R.id.menu_delete)
}
}
@@ -369,42 +354,46 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
data?.getParcelableExtra<Node>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { changedNode ->
data?.getParcelableExtra<NodeVersioned>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode ->
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
addNode(changedNode)
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE)
mAdapter?.notifyDataSetChanged()
mAdapter?.addNode(newNode)
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode);
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
}
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
}
}
}
}
fun contains(node: Node): Boolean {
fun contains(node: NodeVersioned): Boolean {
return mAdapter?.contains(node) ?: false
}
fun addNode(newNode: Node) {
fun addNode(newNode: NodeVersioned) {
mAdapter?.addNode(newNode)
}
fun addNodes(newNodes: List<Node>) {
fun addNodes(newNodes: List<NodeVersioned>) {
mAdapter?.addNodes(newNodes)
}
fun updateNode(oldNode: Node, newNode: Node? = null) {
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) {
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
}
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
mAdapter?.updateNodes(oldNodes, newNodes)
}
fun removeNode(pwNode: Node) {
fun removeNode(pwNode: NodeVersioned) {
mAdapter?.removeNode(pwNode)
}
fun removeNodes(nodes: List<Node>) {
fun removeNodes(nodes: List<NodeVersioned>) {
mAdapter?.removeNodes(nodes)
}
@@ -420,20 +409,20 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickListener {
fun onNodeClick(node: Node)
fun onNodeSelected(nodes: List<Node>): Boolean
fun onNodeClick(node: NodeVersioned)
fun onNodeSelected(nodes: List<NodeVersioned>): Boolean
}
/**
* Menu listener to redefine to do an action in menu
*/
interface NodesActionMenuListener {
fun onOpenMenuClick(node: Node): Boolean
fun onEditMenuClick(node: Node): Boolean
fun onCopyMenuClick(nodes: List<Node>): Boolean
fun onMoveMenuClick(nodes: List<Node>): Boolean
fun onDeleteMenuClick(nodes: List<Node>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<Node>): Boolean
fun onOpenMenuClick(node: NodeVersioned): Boolean
fun onEditMenuClick(node: NodeVersioned): Boolean
fun onCopyMenuClick(nodes: List<NodeVersioned>): Boolean
fun onMoveMenuClick(nodes: List<NodeVersioned>): Boolean
fun onDeleteMenuClick(nodes: List<NodeVersioned>): Boolean
fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List<NodeVersioned>): Boolean
}
enum class PasteMode {
@@ -458,7 +447,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private const val GROUP_KEY = "GROUP_KEY"
private const val IS_SEARCH = "IS_SEARCH"
fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
fun newInstance(group: GroupVersioned?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
val bundle = Bundle()
if (group != null) {
bundle.putParcelable(GROUP_KEY, group)

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities
@@ -23,24 +23,31 @@ import android.app.Activity
import android.app.assist.AssistStructure
import android.app.backup.BackupManager
import android.content.Intent
import android.content.pm.PackageManager
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.preference.PreferenceManager
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.*
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.*
import android.widget.Button
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
import com.kunzisoft.keepass.activities.dialogs.FingerPrintExplanationDialog
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
@@ -49,13 +56,11 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException
import com.kunzisoft.keepass.education.PasswordActivityEducation
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
@@ -67,53 +72,47 @@ import com.kunzisoft.keepass.utils.FileDatabaseInfo
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException
open class PasswordActivity : StylishActivity() {
class PasswordActivity : StylishActivity() {
// Views
private var toolbar: Toolbar? = null
private var containerView: View? = null
private var filenameView: TextView? = null
private var passwordView: EditText? = null
private var keyFileSelectionView: KeyFileSelectionView? = null
private var keyFileView: EditText? = null
private var confirmButtonView: Button? = null
private var checkboxPasswordView: CompoundButton? = null
private var checkboxKeyFileView: CompoundButton? = null
private var checkboxDefaultDatabaseView: CompoundButton? = null
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
private var infoContainerView: ViewGroup? = null
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
private var prefs: SharedPreferences? = null
private var mRememberKeyFile: Boolean = false
private var mOpenFileHelper: OpenFileHelper? = null
private var mPermissionAsked = false
private var readOnly: Boolean = false
private var mForceReadOnly: Boolean = false
set(value) {
infoContainerView?.visibility = if (value) {
readOnly = true
View.VISIBLE
} else {
View.GONE
}
field = value
}
private var mProgressDialogThread: ProgressDialogThread? = null
private var progressDialogThread: ProgressDialogThread? = null
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = PreferenceManager.getDefaultSharedPreferences(this)
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
setContentView(R.layout.activity_password)
toolbar = findViewById(R.id.toolbar)
@@ -123,26 +122,20 @@ open class PasswordActivity : StylishActivity() {
supportActionBar?.setDisplayShowHomeEnabled(true)
containerView = findViewById(R.id.container)
confirmButtonView = findViewById(R.id.activity_password_open_button)
confirmButtonView = findViewById(R.id.pass_ok)
filenameView = findViewById(R.id.filename)
passwordView = findViewById(R.id.password)
keyFileSelectionView = findViewById(R.id.keyfile_selection)
keyFileView = findViewById(R.id.pass_keyfile)
checkboxPasswordView = findViewById(R.id.password_checkbox)
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
advancedUnlockInfoView = findViewById(R.id.biometric_info)
infoContainerView = findViewById(R.id.activity_password_info_container)
advancedUnlockInfoView = findViewById(R.id.fingerprint_info)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
val browseView = findViewById<View>(R.id.open_database_button)
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
keyFileSelectionView?.apply {
mOpenFileHelper?.openFileOnClickViewListener?.let {
setOnClickListener(it)
setOnLongClickListener(it)
}
}
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
passwordView?.setOnEditorActionListener(onEditorActionListener)
passwordView?.addTextChangedListener(object : TextWatcher {
@@ -155,32 +148,52 @@ open class PasswordActivity : StylishActivity() {
checkboxPasswordView?.isChecked = true
}
})
keyFileView?.setOnEditorActionListener(onEditorActionListener)
keyFileView?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
if (editable.toString().isNotEmpty() && checkboxKeyFileView?.isChecked != true)
checkboxKeyFileView?.isChecked = true
}
})
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
enableOrNotTheConfirmationButton()
}
// If is a view intent
getUriFromIntent(intent)
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
mDatabaseKeyFileUri = UriUtil.parse(savedInstanceState.getString(KEY_KEYFILE))
}
mProgressDialogThread = ProgressDialogThread(this).apply {
onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck biometric if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
progressDialogThread = ProgressDialogThread(this) { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_LOAD_TASK -> {
// Recheck fingerprint if error
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
// Stay with the same mode and init it
advancedUnlockedManager?.initBiometricMode()
}
}
var databaseUri: Uri? = null
var masterPassword: String? = null
var keyFileUri: Uri? = null
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
databaseUri?.let { databaseFileUri ->
// Remove the password in view in all cases
removePassword()
if (result.isSuccess) {
mDatabaseKeyFileUri = null
clearCredentialsViews(true)
launchGroupActivity()
} else {
var resultError = ""
@@ -189,43 +202,24 @@ open class PasswordActivity : StylishActivity() {
if (resultException != null) {
resultError = resultException.getLocalizedMessage(resources)
// Relaunch loading if we need to fix UUID
if (resultException is DuplicateUuidDatabaseException) {
if (resultException is LoadDatabaseDuplicateUuidException)
showLoadDatabaseDuplicateUuidMessage {
var databaseUri: Uri? = null
var masterPassword: String? = null
var keyFileUri: Uri? = null
var readOnly = true
var cipherEntity: CipherDatabaseEntity? = null
result.data?.let { resultData ->
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
keyFileUri = resultData.getParcelable(KEY_FILE_KEY)
readOnly = resultData.getBoolean(READ_ONLY_KEY)
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
}
databaseUri?.let { databaseFileUri ->
showProgressDialogAndLoadDatabase(
databaseFileUri,
masterPassword,
keyFileUri,
readOnly,
cipherEntity,
true)
}
showProgressDialogAndLoadDatabase(
databaseFileUri,
masterPassword,
keyFileUri,
readOnly,
cipherEntity,
true)
}
}
}
// Show error message
if (resultMessage != null && resultMessage.isNotEmpty()) {
resultError = "$resultError $resultMessage"
}
Log.e(TAG, resultError, resultException)
Snackbar.make(activity_password_coordinator_layout,
resultError,
Snackbar.LENGTH_LONG).asError().show()
@@ -236,59 +230,19 @@ open class PasswordActivity : StylishActivity() {
}
}
private fun getUriFromIntent(intent: Intent?) {
// If is a view intent
val action = intent?.action
if (action != null
&& action == VIEW_INTENT) {
mDatabaseFileUri = intent.data
mDatabaseKeyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
} else {
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
getUriFromIntent(intent)
}
private fun launchGroupActivity() {
EntrySelectionHelper.doEntrySelectionAction(intent,
{
GroupActivity.launch(this@PasswordActivity,
readOnly)
GroupActivity.launch(this@PasswordActivity, readOnly)
},
{
GroupActivity.launchForKeyboardSelection(this@PasswordActivity,
readOnly)
GroupActivity.launchForKeyboardSelection(this@PasswordActivity, readOnly)
// Do not keep history
finish()
},
{ assistStructure ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
AutofillHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
// Response is build
AutofillHelper.buildResponse(this, items)
finish()
},
{
// Here no search info found
GroupActivity.launchForAutofillResult(this@PasswordActivity,
assistStructure,
null,
readOnly)
},
{
// Simply close if database not opened, normally not happened
finish()
}
)
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly)
}
})
}
@@ -304,61 +258,62 @@ open class PasswordActivity : StylishActivity() {
}
override fun onResume() {
if (Database.getInstance().loaded)
launchGroupActivity()
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
// If the database isn't accessible make sure to clear the password field, if it
// was saved in the instance state
if (Database.getInstance().loaded) {
clearCredentialsViews()
setEmptyViews()
}
// For check shutdown
super.onResume()
mProgressDialogThread?.registerProgressTask()
progressDialogThread?.registerProgressTask()
initUriFromIntent()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
mDatabaseKeyFileUri?.let {
outState.putString(KEY_KEYFILE, it.toString())
}
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState)
}
private fun initUriFromIntent() {
/*
// "canXrite" doesn't work with Google Drive, don't really know why?
mForceReadOnly = mDatabaseFileUri?.let {
!FileDatabaseInfo(this, it).canWrite
} ?: false
*/
mForceReadOnly = mDatabaseFileUri?.let {
!FileDatabaseInfo(this, it).exists
} ?: true
val databaseUri: Uri?
val keyFileUri: Uri?
// If is a view intent
val action = intent.action
if (action != null
&& action == VIEW_INTENT) {
databaseUri = intent.data
keyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
} else {
databaseUri = intent.getParcelableExtra(KEY_FILENAME)
keyFileUri = intent.getParcelableExtra(KEY_KEYFILE)
}
// Post init uri with KeyFile if needed
if (mRememberKeyFile && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
// Retrieve KeyFile in a thread
mDatabaseFileUri?.let { databaseUri ->
databaseUri?.let { databaseUriNotNull ->
FileDatabaseHistoryAction.getInstance(applicationContext)
.getKeyFileUriByDatabaseUri(databaseUri) {
.getKeyFileUriByDatabaseUri(databaseUriNotNull) {
onPostInitUri(databaseUri, it)
}
}
} else {
onPostInitUri(mDatabaseFileUri, mDatabaseKeyFileUri)
onPostInitUri(databaseUri, keyFileUri)
}
}
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
mDatabaseFileUri = databaseFileUri
mDatabaseKeyFileUri = keyFileUri
// Define title
databaseFileUri?.let {
FileDatabaseInfo(this, it).retrieveDatabaseTitle { title ->
@@ -367,18 +322,26 @@ open class PasswordActivity : StylishActivity() {
}
// Define Key File text
if (mRememberKeyFile) {
populateKeyFileTextView(keyFileUri)
val keyUriString = keyFileUri?.toString() ?: ""
if (keyUriString.isNotEmpty() && mRememberKeyFile) { // Bug KeepassDX #18
populateKeyFileTextView(keyUriString)
}
// Define listeners for default database checkbox and validate button
checkboxDefaultDatabaseView?.setOnCheckedChangeListener { _, isChecked ->
var newDefaultFileUri: Uri? = null
var newDefaultFileName: Uri? = null
if (isChecked) {
newDefaultFileUri = databaseFileUri ?: newDefaultFileUri
newDefaultFileName = databaseFileUri ?: newDefaultFileName
}
PreferencesUtil.saveDefaultDatabasePath(this, newDefaultFileUri)
prefs?.edit()?.apply {
newDefaultFileName?.let {
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
} ?: kotlin.run {
remove(KEY_DEFAULT_DATABASE_PATH)
}
apply()
}
val backupManager = BackupManager(this@PasswordActivity)
backupManager.dataChanged()
@@ -386,7 +349,7 @@ open class PasswordActivity : StylishActivity() {
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
// Retrieve settings for default database
val defaultFilename = PreferencesUtil.getDefaultDatabasePath(this)
val defaultFilename = prefs?.getString(KEY_DEFAULT_DATABASE_PATH, "")
if (databaseFileUri != null
&& databaseFileUri.path != null && databaseFileUri.path!!.isNotEmpty()
&& databaseFileUri == UriUtil.parse(defaultFilename)) {
@@ -396,8 +359,6 @@ open class PasswordActivity : StylishActivity() {
// If Activity is launch with a password and want to open directly
val intent = intent
val password = intent.getStringExtra(KEY_PASSWORD)
// Consume the intent extra password
intent.removeExtra(KEY_PASSWORD)
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
if (password != null) {
populatePasswordTextView(password)
@@ -405,11 +366,15 @@ open class PasswordActivity : StylishActivity() {
if (launchImmediately) {
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
} else {
// Init Biometric elements
var biometricInitialize = false
// Init FingerPrint elements
var fingerPrintInit = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(this)) {
advancedUnlockInfoView?.setOnClickListener {
FingerPrintExplanationDialog().show(supportFragmentManager, "fingerPrintExplanationDialog")
}
if (advancedUnlockedManager == null && databaseFileUri != null) {
advancedUnlockedManager = AdvancedUnlockedManager(this,
databaseFileUri,
@@ -431,18 +396,18 @@ open class PasswordActivity : StylishActivity() {
{ passwordDecrypted ->
// Load the database if password is retrieve from biometric
passwordDecrypted?.let {
// Retrieve from biometric
// Retrieve from fingerprint
verifyKeyFileCheckboxAndLoadDatabase(it)
}
})
}
advancedUnlockedManager?.checkBiometricAvailability()
biometricInitialize = true
advancedUnlockedManager?.initBiometric()
fingerPrintInit = true
} else {
advancedUnlockedManager?.destroy()
}
}
if (!biometricInitialize) {
if (!fingerPrintInit) {
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
}
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
@@ -463,9 +428,10 @@ open class PasswordActivity : StylishActivity() {
}
}
private fun clearCredentialsViews(clearKeyFile: Boolean = !mRememberKeyFile) {
private fun setEmptyViews() {
populatePasswordTextView(null)
if (clearKeyFile) {
// Bug KeepassDX #18
if (!mRememberKeyFile) {
populateKeyFileTextView(null)
}
}
@@ -482,20 +448,24 @@ open class PasswordActivity : StylishActivity() {
}
}
private fun populateKeyFileTextView(uri: Uri?) {
if (uri == null || uri.toString().isEmpty()) {
keyFileSelectionView?.uri = null
private fun populateKeyFileTextView(text: String?) {
if (text == null || text.isEmpty()) {
keyFileView?.setText("")
if (checkboxKeyFileView?.isChecked == true)
checkboxKeyFileView?.isChecked = false
} else {
keyFileSelectionView?.uri = uri
keyFileView?.setText(text)
if (checkboxKeyFileView?.isChecked != true)
checkboxKeyFileView?.isChecked = true
}
}
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.pause()
}
progressDialogThread?.unregisterProgressTask()
super.onPause()
}
@@ -509,7 +479,7 @@ open class PasswordActivity : StylishActivity() {
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
val password: String? = passwordView?.text?.toString()
val keyFile: Uri? = keyFileSelectionView?.uri
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
}
@@ -522,7 +492,7 @@ open class PasswordActivity : StylishActivity() {
}
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
val keyFile: Uri? = keyFileSelectionView?.uri
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
verifyKeyFileCheckbox(keyFile)
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
}
@@ -531,13 +501,18 @@ open class PasswordActivity : StylishActivity() {
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
}
private fun removePassword() {
passwordView?.setText("")
checkboxPasswordView?.isChecked = false
}
private fun loadDatabase(databaseFileUri: Uri?,
password: String?,
keyFileUri: Uri?,
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
clearCredentialsViews()
removePassword()
}
databaseFileUri?.let { databaseUri ->
@@ -558,7 +533,7 @@ open class PasswordActivity : StylishActivity() {
readOnly: Boolean,
cipherDatabaseEntity: CipherDatabaseEntity?,
fixDuplicateUUID: Boolean) {
mProgressDialogThread?.startDatabaseLoad(
progressDialogThread?.startDatabaseLoad(
databaseUri,
password,
keyFile,
@@ -574,116 +549,69 @@ open class PasswordActivity : StylishActivity() {
}.show(supportFragmentManager, "duplicateUUIDDialog")
}
// To fix multiple view education
private var performedEductionInProgress = false
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
// Read menu
inflater.inflate(R.menu.open_file, menu)
if (mForceReadOnly) {
menu.removeItem(R.id.menu_open_file_read_mode_key)
} else {
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
}
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
MenuUtil.defaultMenuInflater(inflater, menu)
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// biometric menu
// Fingerprint menu
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
}
super.onCreateOptionsMenu(menu)
launchEducation(menu) {
launchCheckPermission()
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
}
return true
}
// Check permission
private fun launchCheckPermission() {
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissions = arrayOf(writePermission)
if (Build.VERSION.SDK_INT >= 23
&& !readOnly
&& !mPermissionAsked) {
mPermissionAsked = true
// Check self permission to show or not the dialog
if (toolbar != null
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
WRITE_EXTERNAL_STORAGE_REQUEST -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
Toast.makeText(this, R.string.read_only_warning, Toast.LENGTH_LONG).show()
}
}
}
}
// To fix multiple view education
private var performedEductionInProgress = false
private fun launchEducation(menu: Menu, onEducationFinished: (()-> Unit)? = null) {
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
}
}
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu,
onEducationFinished: (()-> Unit)? = null) {
val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
menu: Menu) {
val educationContainerView = containerView
val unlockEducationPerformed = educationContainerView != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
educationToolbar,
educationContainerView,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
})
if (!unlockEducationPerformed) {
val educationToolbar = toolbar
val readOnlyEducationPerformed =
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
{
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
performedNextEducation(passwordActivityEducation, menu)
})
if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
val biometricEducationPerformed =
// fingerprintEducationPerformed
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(advancedUnlockInfoView?.unlockIconImageView!!)
if (!biometricEducationPerformed) {
onEducationFinished?.invoke()
}
}
}
}
@@ -706,7 +634,7 @@ open class PasswordActivity : StylishActivity() {
readOnly = !readOnly
changeOpenFileReadIcon(item)
}
R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
R.id.menu_fingerprint_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
advancedUnlockedManager?.deleteEntryKey()
}
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
@@ -731,8 +659,7 @@ open class PasswordActivity : StylishActivity() {
keyFileResult = it.onActivityResultCallback(requestCode, resultCode, data
) { uri ->
if (uri != null) {
mDatabaseKeyFileUri = uri
populateKeyFileTextView(uri)
populateKeyFileTextView(uri.toString())
}
}
}
@@ -740,7 +667,7 @@ open class PasswordActivity : StylishActivity() {
// this block if not a key file response
when (resultCode) {
LockingActivity.RESULT_EXIT_LOCK, Activity.RESULT_CANCELED -> {
clearCredentialsViews()
setEmptyViews()
Database.getInstance().closeAndClear(applicationContext.filesDir)
}
}
@@ -751,14 +678,14 @@ open class PasswordActivity : StylishActivity() {
private val TAG = PasswordActivity::class.java.name
const val KEY_DEFAULT_DATABASE_PATH = "KEY_DEFAULT_DATABASE_PATH"
private const val KEY_FILENAME = "fileName"
private const val KEY_KEYFILE = "keyFile"
private const val VIEW_INTENT = "android.intent.action.VIEW"
private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) {
@@ -813,15 +740,13 @@ open class PasswordActivity : StylishActivity() {
activity: Activity,
databaseFile: Uri,
keyFile: Uri?,
assistStructure: AssistStructure?,
searchInfo: SearchInfo?) {
assistStructure: AssistStructure?) {
if (assistStructure != null) {
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
AutofillHelper.startActivityForAutofillResult(
activity,
intent,
assistStructure,
searchInfo)
assistStructure)
}
} else {
launch(activity, databaseFile, keyFile)

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
@@ -35,7 +35,7 @@ import android.widget.CompoundButton
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.view.KeyFileSelectionView
import com.kunzisoft.keepass.utils.UriUtil
class AssignMasterKeyDialogFragment : DialogFragment() {
@@ -51,8 +51,9 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
private var passwordRepeatTextInputLayout: TextInputLayout? = null
private var passwordRepeatView: TextView? = null
private var keyFileTextInputLayout: TextInputLayout? = null
private var keyFileCheckBox: CompoundButton? = null
private var keyFileSelectionView: KeyFileSelectionView? = null
private var keyFileView: TextView? = null
private var mListener: AssignPasswordDialogListener? = null
@@ -68,6 +69,16 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
}
}
private val keyFileTextWatcher = object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
keyFileCheckBox?.isChecked = true
}
}
interface AssignPasswordDialogListener {
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
keyFileChecked: Boolean, keyFile: Uri?)
@@ -110,14 +121,13 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
keyFileTextInputLayout = rootView?.findViewById(R.id.keyfile_input_layout)
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
mOpenFileHelper = OpenFileHelper(this)
keyFileSelectionView?.apply {
setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener)
}
rootView?.findViewById<View>(R.id.open_database_button)?.setOnClickListener { view ->
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
val dialog = builder.create()
@@ -166,12 +176,14 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
// To check checkboxes if a text is present
passwordView?.addTextChangedListener(passwordTextWatcher)
keyFileView?.addTextChangedListener(keyFileTextWatcher)
}
override fun onPause() {
super.onPause()
passwordView?.removeTextChangedListener(passwordTextWatcher)
keyFileView?.removeTextChangedListener(keyFileTextWatcher)
}
private fun verifyPassword(): Boolean {
@@ -204,11 +216,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
if (keyFileCheckBox != null
&& keyFileCheckBox!!.isChecked) {
keyFileSelectionView?.uri?.let { uri ->
UriUtil.parse(keyFileView?.text?.toString())?.let { uri ->
mKeyFile = uri
} ?: run {
error = true
keyFileSelectionView?.error = getString(R.string.error_nokeyfile)
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile)
}
}
return error
@@ -253,7 +265,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
) { uri ->
uri?.let { pathUri ->
keyFileCheckBox?.isChecked = true
keyFileSelectionView?.uri = pathUri
keyFileView?.text = pathUri.toString()
}
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs

View File

@@ -1,61 +0,0 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.DatePickerDialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment
class DatePickerFragment : DialogFragment() {
private var mDefaultYear: Int = 2000
private var mDefaultMonth: Int = 1
private var mDefaultDay: Int = 1
private var mListener: DatePickerDialog.OnDateSetListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as DatePickerDialog.OnDateSetListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it
return context?.let {
arguments?.apply {
if (containsKey(DEFAULT_YEAR_BUNDLE_KEY))
mDefaultYear = getInt(DEFAULT_YEAR_BUNDLE_KEY)
if (containsKey(DEFAULT_MONTH_BUNDLE_KEY))
mDefaultMonth = getInt(DEFAULT_MONTH_BUNDLE_KEY)
if (containsKey(DEFAULT_DAY_BUNDLE_KEY))
mDefaultDay = getInt(DEFAULT_DAY_BUNDLE_KEY)
}
DatePickerDialog(it, mListener, mDefaultYear, mDefaultMonth, mDefaultDay)
} ?: super.onCreateDialog(savedInstanceState)
}
companion object {
private const val DEFAULT_YEAR_BUNDLE_KEY = "DEFAULT_YEAR_BUNDLE_KEY"
private const val DEFAULT_MONTH_BUNDLE_KEY = "DEFAULT_MONTH_BUNDLE_KEY"
private const val DEFAULT_DAY_BUNDLE_KEY = "DEFAULT_DAY_BUNDLE_KEY"
fun getInstance(defaultYear: Int,
defaultMonth: Int,
defaultDay: Int): DatePickerFragment {
return DatePickerFragment().apply {
arguments = Bundle().apply {
putInt(DEFAULT_YEAR_BUNDLE_KEY, defaultYear)
putInt(DEFAULT_MONTH_BUNDLE_KEY, defaultMonth)
putInt(DEFAULT_DAY_BUNDLE_KEY, defaultDay)
}
}
}
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
class DeleteNodesDialogFragment : DialogFragment() {
private var mNodesToDelete: List<Node> = ArrayList()
private var mListener: DeleteNodeListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as DeleteNodeListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DeleteNodeListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
arguments?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), this)
}
} ?: savedInstanceState?.apply {
if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY)
&& containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) {
mNodesToDelete = getListNodesFromBundle(Database.getInstance(), savedInstanceState)
}
}
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = AlertDialog.Builder(activity)
builder.setMessage(getString(R.string.warning_permanently_delete_nodes))
builder.setPositiveButton(android.R.string.yes) { _, _ ->
mListener?.permanentlyDeleteNodes(mNodesToDelete)
}
builder.setNegativeButton(android.R.string.no) { _, _ -> dismiss() }
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putAll(getBundleFromListNodes(mNodesToDelete))
}
interface DeleteNodeListener {
fun permanentlyDeleteNodes(nodes: List<Node>)
}
companion object {
fun getInstance(nodesToDelete: List<Node>): DeleteNodesDialogFragment {
return DeleteNodesDialogFragment().apply {
arguments = getBundleFromListNodes(nodesToDelete)
}
}
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.annotation.RequiresApi
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector
import com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintExplanationDialog : DialogFragment() {
private var fingerPrintAnimatedVector: FingerPrintAnimatedVector? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
val builder = AlertDialog.Builder(activity)
val inflater = activity.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_fingerprint_explanation, null)
rootView.findViewById<View>(R.id.fingerprint_setting_link_text).setOnClickListener {
startActivity(Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS))
}
rootView.findViewById<View>(R.id.auto_open_biometric_prompt_button).setOnClickListener {
startActivity(Intent(activity, SettingsAdvancedUnlockActivity::class.java))
}
fingerPrintAnimatedVector = FingerPrintAnimatedVector(activity,
rootView.findViewById(R.id.biometric_image))
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
fingerPrintAnimatedVector?.startScan()
}
override fun onPause() {
super.onPause()
fingerPrintAnimatedVector?.stopScan()
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
@@ -33,8 +33,8 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.GroupVersioned
import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.icons.assignDatabaseIcon
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
@@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
private var editGroupDialogAction: EditGroupDialogAction? = null
private var nameGroup: String? = null
private var iconGroup: IconImage? = null
private var iconGroup: PwIcon? = null
private var nameTextLayoutView: TextInputLayout? = null
private var nameTextView: TextView? = null
@@ -186,8 +186,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
}
interface EditGroupListener {
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
}
companion object {
@@ -206,7 +206,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
return fragment
}
fun build(group: Group): GroupEditDialogFragment {
fun build(group: GroupVersioned): GroupEditDialogFragment {
val bundle = Bundle()
bundle.putString(KEY_NAME, group.title)
bundle.putParcelable(KEY_ICON, group.icon)

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
@@ -35,7 +35,7 @@ import android.widget.GridView
import android.widget.ImageView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.PwIconStandard
import com.kunzisoft.keepass.icons.IconPack
import com.kunzisoft.keepass.icons.IconPackChooser
@@ -72,7 +72,7 @@ class IconPickerDialogFragment : DialogFragment() {
currIconGridView.setOnItemClickListener { _, _, position, _ ->
val bundle = Bundle()
bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position))
iconPickerListener?.iconPicked(bundle)
dismiss()
}
@@ -112,7 +112,7 @@ class IconPickerDialogFragment : DialogFragment() {
// Retrieve the textColor to tint the icon
val ta = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.textColor))
ImageViewCompat.setImageTintList(iconImageView, ColorStateList.valueOf(ta.getColor(0, Color.BLACK)))
ta.recycle()
ta?.recycle()
}
}
@@ -128,7 +128,7 @@ class IconPickerDialogFragment : DialogFragment() {
private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD"
fun getIconStandardFromBundle(bundle: Bundle): IconImageStandard? {
fun getIconStandardFromBundle(bundle: Bundle): PwIconStandard? {
return bundle.getParcelable(KEY_ICON_STANDARD)
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Bundle
import android.provider.Settings
import androidx.fragment.app.DialogFragment
import androidx.appcompat.app.AlertDialog
import android.view.View
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.UriUtil
class KeyboardExplanationDialogFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let {
val builder = AlertDialog.Builder(activity!!)
val inflater = activity!!.layoutInflater
val rootView = inflater.inflate(R.layout.fragment_keyboard_explanation, null)
rootView.findViewById<View>(R.id.keyboards_activate_device_setting_button)
.setOnClickListener { launchActivateKeyboardSetting() }
val containerKeyboardSwitcher = rootView.findViewById<View>(R.id.container_keyboard_switcher)
if (BuildConfig.CLOSED_STORE) {
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_play_store) }
} else {
containerKeyboardSwitcher.setOnClickListener { UriUtil.gotoUrl(context!!, R.string.keyboard_switcher_f_droid) }
}
builder.setView(rootView)
.setPositiveButton(android.R.string.ok) { _, _ -> }
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
private fun launchActivateKeyboardSetting() {
val intent = Intent(Settings.ACTION_INPUT_METHOD_SETTINGS)
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class ReadOnlyDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
var warning = getString(R.string.read_only_warning)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
}
builder.setMessage(warning)
builder.setPositiveButton(getString(android.R.string.ok)) { _, _ -> dismiss() }
builder.setNegativeButton(getString(R.string.beta_dontask)) { _, _ ->
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val edit = prefs.edit()
edit.putBoolean(getString(R.string.show_read_only_warning), false)
edit.apply()
dismiss()
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -1,401 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Spinner
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.google.android.material.textfield.TextInputLayout
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.OtpModel
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_HOTP_COUNTER
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_OTP_DIGITS
import com.kunzisoft.keepass.otp.OtpElement.Companion.MAX_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_HOTP_COUNTER
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_OTP_DIGITS
import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD
import com.kunzisoft.keepass.otp.OtpTokenType
import com.kunzisoft.keepass.otp.OtpType
import com.kunzisoft.keepass.otp.TokenCalculator
import java.util.*
class SetOTPDialogFragment : DialogFragment() {
private var mCreateOTPElementListener: CreateOtpListener? = null
private var mOtpElement: OtpElement = OtpElement()
private var otpTypeSpinner: Spinner? = null
private var otpTokenTypeSpinner: Spinner? = null
private var otpSecretContainer: TextInputLayout? = null
private var otpSecretTextView: EditText? = null
private var otpPeriodContainer: TextInputLayout? = null
private var otpPeriodTextView: EditText? = null
private var otpCounterContainer: TextInputLayout? = null
private var otpCounterTextView: EditText? = null
private var otpDigitsContainer: TextInputLayout? = null
private var otpDigitsTextView: EditText? = null
private var otpAlgorithmSpinner: Spinner? = null
private var otpTypeAdapter: ArrayAdapter<OtpType>? = null
private var otpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var totpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var hotpTokenTypeAdapter: ArrayAdapter<OtpTokenType>? = null
private var otpAlgorithmAdapter: ArrayAdapter<TokenCalculator.HashAlgorithm>? = null
private var mManualEvent = false
private var mOnFocusChangeListener = View.OnFocusChangeListener { _, isFocus ->
if (!isFocus)
mManualEvent = true
}
private var mOnTouchListener = View.OnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mManualEvent = true
}
}
false
}
private var mSecretWellFormed = false
private var mCounterWellFormed = true
private var mPeriodWellFormed = true
private var mDigitsWellFormed = true
override fun onAttach(context: Context) {
super.onAttach(context)
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
mCreateOTPElementListener = context as CreateOtpListener
} catch (e: ClassCastException) {
// The activity doesn't implement the interface, throw exception
throw ClassCastException(context.toString()
+ " must implement " + CreateOtpListener::class.java.name)
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Retrieve OTP model from instance state
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(KEY_OTP)) {
savedInstanceState.getParcelable<OtpModel>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel)
}
}
} else {
arguments?.apply {
if (containsKey(KEY_OTP)) {
getParcelable<OtpModel?>(KEY_OTP)?.let { otpModel ->
mOtpElement = OtpElement(otpModel)
}
}
}
}
activity?.let { activity ->
val root = activity.layoutInflater.inflate(R.layout.fragment_set_otp, null) as ViewGroup?
otpTypeSpinner = root?.findViewById(R.id.setup_otp_type)
otpTokenTypeSpinner = root?.findViewById(R.id.setup_otp_token_type)
otpSecretContainer = root?.findViewById(R.id.setup_otp_secret_label)
otpSecretTextView = root?.findViewById(R.id.setup_otp_secret)
otpAlgorithmSpinner = root?.findViewById(R.id.setup_otp_algorithm)
otpPeriodContainer= root?.findViewById(R.id.setup_otp_period_label)
otpPeriodTextView = root?.findViewById(R.id.setup_otp_period)
otpCounterContainer= root?.findViewById(R.id.setup_otp_counter_label)
otpCounterTextView = root?.findViewById(R.id.setup_otp_counter)
otpDigitsContainer = root?.findViewById(R.id.setup_otp_digits_label)
otpDigitsTextView = root?.findViewById(R.id.setup_otp_digits)
// To fix init element
// With tab keyboard selection
otpSecretTextView?.onFocusChangeListener = mOnFocusChangeListener
// With finger selection
otpTypeSpinner?.setOnTouchListener(mOnTouchListener)
otpTokenTypeSpinner?.setOnTouchListener(mOnTouchListener)
otpSecretTextView?.setOnTouchListener(mOnTouchListener)
otpAlgorithmSpinner?.setOnTouchListener(mOnTouchListener)
otpPeriodTextView?.setOnTouchListener(mOnTouchListener)
otpCounterTextView?.setOnTouchListener(mOnTouchListener)
otpDigitsTextView?.setOnTouchListener(mOnTouchListener)
// HOTP / TOTP Type selection
val otpTypeArray = OtpType.values()
otpTypeAdapter = ArrayAdapter<OtpType>(activity,
android.R.layout.simple_spinner_item, otpTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTypeSpinner?.adapter = otpTypeAdapter
// Otp Token type selection
val hotpTokenTypeArray = OtpTokenType.getHotpTokenTypeValues()
hotpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, hotpTokenTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
// Proprietary only on closed and full version
val totpTokenTypeArray = OtpTokenType.getTotpTokenTypeValues(
BuildConfig.CLOSED_STORE && BuildConfig.FULL_VERSION)
totpTokenTypeAdapter = ArrayAdapter(activity,
android.R.layout.simple_spinner_item, totpTokenTypeArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpTokenTypeAdapter = hotpTokenTypeAdapter
otpTokenTypeSpinner?.adapter = otpTokenTypeAdapter
// OTP Algorithm
val otpAlgorithmArray = TokenCalculator.HashAlgorithm.values()
otpAlgorithmAdapter = ArrayAdapter<TokenCalculator.HashAlgorithm>(activity,
android.R.layout.simple_spinner_item, otpAlgorithmArray).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
otpAlgorithmSpinner?.adapter = otpAlgorithmAdapter
// Set the default value of OTP element
upgradeType()
upgradeTokenType()
upgradeParameters()
attachListeners()
val builder = AlertDialog.Builder(activity)
builder.apply {
setTitle(R.string.entry_setup_otp)
setView(root)
.setPositiveButton(android.R.string.ok) {_, _ -> }
.setNegativeButton(android.R.string.cancel) { _, _ ->
}
}
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
override fun onResume() {
super.onResume()
(dialog as AlertDialog).getButton(Dialog.BUTTON_POSITIVE).setOnClickListener {
if (mSecretWellFormed
&& mCounterWellFormed
&& mPeriodWellFormed
&& mDigitsWellFormed) {
mCreateOTPElementListener?.onOtpCreated(mOtpElement)
dismiss()
}
}
}
private fun attachListeners() {
// Set Type listener
otpTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as OtpType?)?.let {
mOtpElement.type = it
upgradeTokenType()
}
}
}
}
// Set type token listener
otpTokenTypeSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as OtpTokenType?)?.let {
mOtpElement.tokenType = it
upgradeParameters()
}
}
}
}
// Set algorithm spinner
otpAlgorithmSpinner?.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (mManualEvent) {
(parent?.selectedItem as TokenCalculator.HashAlgorithm?)?.let {
mOtpElement.algorithm = it
}
}
}
}
// Set secret in OtpElement
otpSecretTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
s?.toString()?.let { userString ->
try {
mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH))
otpSecretContainer?.error = null
} catch (exception: Exception) {
otpSecretContainer?.error = getString(R.string.error_otp_secret_key)
}
mSecretWellFormed = otpSecretContainer?.error == null
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set counter in OtpElement
otpCounterTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toLongOrNull()?.let {
try {
mOtpElement.counter = it
otpCounterContainer?.error = null
} catch (exception: Exception) {
otpCounterContainer?.error = getString(R.string.error_otp_counter,
MIN_HOTP_COUNTER, MAX_HOTP_COUNTER)
}
mCounterWellFormed = otpCounterContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set period in OtpElement
otpPeriodTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toIntOrNull()?.let {
try {
mOtpElement.period = it
otpPeriodContainer?.error = null
} catch (exception: Exception) {
otpPeriodContainer?.error = getString(R.string.error_otp_period,
MIN_TOTP_PERIOD, MAX_TOTP_PERIOD)
}
mPeriodWellFormed = otpPeriodContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
// Set digits in OtpElement
otpDigitsTextView?.addTextChangedListener(object: TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (mManualEvent) {
s?.toString()?.toIntOrNull()?.let {
try {
mOtpElement.digits = it
otpDigitsContainer?.error = null
} catch (exception: Exception) {
otpDigitsContainer?.error = getString(R.string.error_otp_digits,
MIN_OTP_DIGITS, MAX_OTP_DIGITS)
}
mDigitsWellFormed = otpDigitsContainer?.error == null
}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})
}
private fun upgradeType() {
otpTypeSpinner?.setSelection(OtpType.values().indexOf(mOtpElement.type))
}
private fun upgradeTokenType() {
when (mOtpElement.type) {
OtpType.HOTP -> {
otpPeriodContainer?.visibility = View.GONE
otpCounterContainer?.visibility = View.VISIBLE
otpTokenTypeSpinner?.adapter = hotpTokenTypeAdapter
otpTokenTypeSpinner?.setSelection(OtpTokenType
.getHotpTokenTypeValues().indexOf(mOtpElement.tokenType))
}
OtpType.TOTP -> {
otpPeriodContainer?.visibility = View.VISIBLE
otpCounterContainer?.visibility = View.GONE
otpTokenTypeSpinner?.adapter = totpTokenTypeAdapter
otpTokenTypeSpinner?.setSelection(OtpTokenType
.getTotpTokenTypeValues().indexOf(mOtpElement.tokenType))
}
}
}
private fun upgradeParameters() {
otpAlgorithmSpinner?.setSelection(TokenCalculator.HashAlgorithm.values()
.indexOf(mOtpElement.algorithm))
otpSecretTextView?.apply {
setText(mOtpElement.getBase32Secret())
// Cursor at end
setSelection(this.text.length)
}
otpCounterTextView?.setText(mOtpElement.counter.toString())
otpPeriodTextView?.setText(mOtpElement.period.toString())
otpDigitsTextView?.setText(mOtpElement.digits.toString())
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(KEY_OTP, mOtpElement.otpModel)
}
interface CreateOtpListener {
fun onOtpCreated(otpElement: OtpElement)
}
companion object {
private const val KEY_OTP = "KEY_OTP"
fun build(otpModel: OtpModel? = null): SetOTPDialogFragment {
return SetOTPDialogFragment().apply {
if (otpModel != null) {
arguments = Bundle().apply {
putParcelable(KEY_OTP, otpModel)
}
}
}
}
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
@@ -29,7 +29,7 @@ import android.view.View
import android.widget.CompoundButton
import android.widget.RadioGroup
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.SortNodeEnum
class SortDialogFragment : DialogFragment() {
@@ -82,9 +82,7 @@ class SortDialogFragment : DialogFragment() {
builder.setView(rootView)
// Add action buttons
.setPositiveButton(android.R.string.ok
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum,
SortNodeEnum.SortNodeParameters(mAscending, mGroupsBefore, mRecycleBinBottom))
}
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) }
.setNegativeButton(android.R.string.cancel) { _, _ -> }
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
@@ -152,7 +150,10 @@ class SortDialogFragment : DialogFragment() {
}
interface SortSelectionListener {
fun onSortSelected(sortNodeEnum: SortNodeEnum, sortNodeParameters: SortNodeEnum.SortNodeParameters)
fun onSortSelected(sortNodeEnum: SortNodeEnum,
ascending: Boolean,
groupsBefore: Boolean,
recycleBinBottom: Boolean)
}
companion object {

View File

@@ -1,57 +0,0 @@
package com.kunzisoft.keepass.activities.dialogs
import android.app.DatePickerDialog
import android.app.Dialog
import android.app.TimePickerDialog
import android.content.Context
import android.os.Bundle
import android.text.format.DateFormat
import androidx.fragment.app.DialogFragment
class TimePickerFragment : DialogFragment() {
private var defaultHour: Int = 0
private var defaultMinute: Int = 0
private var mListener: TimePickerDialog.OnTimeSetListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
try {
mListener = context as TimePickerDialog.OnTimeSetListener
} catch (e: ClassCastException) {
throw ClassCastException(context.toString()
+ " must implement " + DatePickerDialog.OnDateSetListener::class.java.name)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Create a new instance of DatePickerDialog and return it
return context?.let {
arguments?.apply {
if (containsKey(DEFAULT_HOUR_BUNDLE_KEY))
defaultHour = getInt(DEFAULT_HOUR_BUNDLE_KEY)
if (containsKey(DEFAULT_MINUTE_BUNDLE_KEY))
defaultMinute = getInt(DEFAULT_MINUTE_BUNDLE_KEY)
}
TimePickerDialog(it, mListener, defaultHour, defaultMinute, DateFormat.is24HourFormat(activity))
} ?: super.onCreateDialog(savedInstanceState)
}
companion object {
private const val DEFAULT_HOUR_BUNDLE_KEY = "DEFAULT_HOUR_BUNDLE_KEY"
private const val DEFAULT_MINUTE_BUNDLE_KEY = "DEFAULT_MINUTE_BUNDLE_KEY"
fun getInstance(defaultHour: Int,
defaultMinute: Int): TimePickerFragment {
return TimePickerFragment().apply {
arguments = Bundle().apply {
putInt(DEFAULT_HOUR_BUNDLE_KEY, defaultHour)
putInt(DEFAULT_MINUTE_BUNDLE_KEY, defaultMinute)
}
}
}
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
import android.app.assist.AssistStructure

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
@@ -52,22 +52,14 @@ class OpenFileHelper {
this.fragment = context
}
inner class OpenFileOnClickViewListener : View.OnClickListener, View.OnLongClickListener {
inner class OpenFileOnClickViewListener : View.OnClickListener {
private fun onAbstractClick(longClick: Boolean = false) {
override fun onClick(v: View) {
try {
if (longClick) {
try {
openActivityWithActionGetContent()
} catch (e: Exception) {
openActivityWithActionOpenDocument()
}
} else {
try {
openActivityWithActionOpenDocument()
} catch (e: Exception) {
openActivityWithActionGetContent()
}
try {
openActivityWithActionOpenDocument()
} catch(e: Exception) {
openActivityWithActionGetContent()
}
} catch (e: Exception) {
Log.e(TAG, "Enable to start the file picker activity", e)
@@ -76,15 +68,6 @@ class OpenFileHelper {
showBrowserDialog()
}
}
override fun onClick(v: View) {
onAbstractClick()
}
override fun onLongClick(v: View?): Boolean {
onAbstractClick(true)
return true
}
}
@SuppressLint("InlinedApi")
@@ -92,10 +75,9 @@ class OpenFileHelper {
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
}
if (fragment != null)
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
@@ -103,15 +85,10 @@ class OpenFileHelper {
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
}
@SuppressLint("InlinedApi")
private fun openActivityWithActionGetContent() {
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
}
if (fragment != null)
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
import android.content.Context

View File

@@ -1,26 +1,30 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.lock
import android.app.Activity
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import android.view.View
@@ -28,11 +32,12 @@ import android.view.ViewGroup
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.LOCK_ACTION
abstract class LockingActivity : StylishActivity() {
@@ -57,10 +62,6 @@ abstract class LockingActivity : StylishActivity() {
return field || mSelectionMode
}
protected var mSelectionMode: Boolean = false
protected var mAutoSaveEnable: Boolean = true
var mProgressDialogThread: ProgressDialogThread? = null
private set
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -74,16 +75,16 @@ abstract class LockingActivity : StylishActivity() {
}
if (mTimeoutEnable) {
mLockReceiver = LockReceiver {
lockAndExit()
mLockReceiver = LockReceiver()
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF)
addAction(LOCK_ACTION)
}
registerLockReceiver(mLockReceiver)
registerReceiver(mLockReceiver, intentFilter)
}
mExitLock = false
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
mProgressDialogThread = ProgressDialogThread(this)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -99,13 +100,8 @@ abstract class LockingActivity : StylishActivity() {
override fun onResume() {
super.onResume()
mProgressDialogThread?.registerProgressTask()
// To refresh when back to normal workflow from selection workflow
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
invalidateOptionsMenu()
if (mTimeoutEnable) {
// End activity if database not loaded
@@ -122,6 +118,8 @@ abstract class LockingActivity : StylishActivity() {
if (!mExitLock)
TimeoutHelper.recordTime(this)
}
invalidateOptionsMenu()
}
override fun onSaveInstanceState(outState: Bundle) {
@@ -131,8 +129,6 @@ abstract class LockingActivity : StylishActivity() {
}
override fun onPause() {
mProgressDialogThread?.unregisterProgressTask()
super.onPause()
if (mTimeoutEnable) {
@@ -142,12 +138,29 @@ abstract class LockingActivity : StylishActivity() {
}
override fun onDestroy() {
unregisterLockReceiver(mLockReceiver)
super.onDestroy()
if (mLockReceiver != null)
unregisterReceiver(mLockReceiver)
}
inner class LockReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// If allowed, lock and exit
if (!TimeoutHelper.temporarilyDisableTimeout) {
intent.action?.let {
when (it) {
Intent.ACTION_SCREEN_OFF -> if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this@LockingActivity)) {
lockAndExit()
}
LOCK_ACTION -> lockAndExit()
}
}
}
}
}
protected fun lockAndExit() {
sendBroadcast(Intent(LOCK_ACTION))
lock()
}
@@ -182,8 +195,17 @@ abstract class LockingActivity : StylishActivity() {
}
fun Activity.lock() {
closeDatabase()
// Stop the Magikeyboard service
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
MagikIME.removeEntry(this)
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
" after inactivity or manual lock")
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
cancelAll()
}
// Clear data
Database.getInstance().closeAndClear(applicationContext.filesDir)
// Add onActivityForResult response
setResult(LockingActivity.RESULT_EXIT_LOCK)
finish()

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.lock
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
/**
* Locking Hide Activity that sets FLAG_SECURE to prevent screenshots, and from
* appearing in the recent app preview
*/
abstract class LockingHideActivity : LockingActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.stylish
@@ -61,7 +61,6 @@ object Stylish {
return when (themeString) {
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red

View File

@@ -1,63 +1,38 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.view.WindowManager
/**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
* appearing in the recent app preview
*/
abstract class StylishActivity : AppCompatActivity() {
@StyleRes
private var themeId: Int = 0
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.themeId = Stylish.getThemeId(this)
setTheme(themeId)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
override fun onResume() {

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.stylish

View File

@@ -1,100 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<EntryAttachmentsAdapter.EntryBinariesViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList()
var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null
private val mDatabase = Database.getInstance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
}
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
val entryAttachment = entryAttachmentsList[position]
holder.binaryFileTitle.text = entryAttachment.name
holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachment.binaryAttachment.length())
holder.binaryFileCompression.apply {
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
|| entryAttachment.binaryAttachment.isCompressed == true) {
text = CompressionAlgorithm.GZip.getName(context.resources)
visibility = View.VISIBLE
} else {
text = ""
visibility = View.GONE
}
}
holder.binaryFileProgress.apply {
visibility = when (entryAttachment.downloadState) {
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
}
progress = entryAttachment.downloadProgression
}
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryAttachment, position)
}
}
override fun getItemCount(): Int {
return entryAttachmentsList.size
}
fun updateProgress(entryAttachment: EntryAttachment) {
val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name }
if (indexEntryAttachment != -1) {
entryAttachmentsList[indexEntryAttachment] = entryAttachment
notifyItemChanged(indexEntryAttachment)
}
}
fun clear() {
entryAttachmentsList.clear()
}
inner class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
}
}

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
@@ -26,13 +7,13 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.EntryVersioned
class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter<EntryHistoryAdapter.EntryHistoryViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryHistoryList: MutableList<Entry> = ArrayList()
var onItemClickListener: ((item: Entry, position: Int)->Unit)? = null
var entryHistoryList: MutableList<EntryVersioned> = ArrayList()
var onItemClickListener: ((item: EntryVersioned, position: Int)->Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false))

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
@@ -34,9 +15,10 @@ import java.util.ArrayList
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var fields: MutableList<Field> = ArrayList()
var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
return FieldViewHolder(view)
@@ -52,11 +34,6 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
return fields.size
}
fun setFields(fieldsToAdd: List<Field>) {
fields.clear()
fields.addAll(fieldsToAdd)
}
fun clear() {
fields.clear()
}

View File

@@ -1,27 +1,25 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import android.util.TypedValue
@@ -84,27 +82,15 @@ class FileDatabaseHistoryAdapter(private val context: Context)
// File path
holder.filePath.text = UriUtil.decode(fileDatabaseInfo.fileUri?.toString())
if (fileDatabaseInfo.exists) {
holder.fileInformation.clearColorFilter()
} else {
holder.fileInformation.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)
}
holder.filePreciseInfoContainer.visibility = if (fileDatabaseInfo.found()) {
// Modification
holder.fileModification.text = fileDatabaseInfo.getModificationString()
// Size
holder.fileSize.text = fileDatabaseInfo.getSizeString()
// Modification
fileDatabaseInfo.getModificationString()?.let {
holder.fileModification.text = it
holder.fileModification.visibility = View.VISIBLE
} ?: run {
holder.fileModification.visibility = View.GONE
}
// Size
fileDatabaseInfo.getSizeString()?.let {
holder.fileSize.text = it
holder.fileSize.visibility = View.VISIBLE
} ?: run {
holder.fileSize.visibility = View.GONE
}
View.VISIBLE
} else
View.GONE
// Click on information
val isExpanded = position == mExpandedPosition
@@ -156,10 +142,6 @@ class FileDatabaseHistoryAdapter(private val context: Context)
return listDatabaseFiles.size
}
fun clearDatabaseFileHistoryList() {
listDatabaseFiles.clear()
}
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
listDatabaseFiles.clear()
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
@@ -196,6 +178,7 @@ class FileDatabaseHistoryAdapter(private val context: Context)
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
var filePath: TextView = itemView.findViewById(R.id.file_path)
var filePreciseInfoContainer: ViewGroup = itemView.findViewById(R.id.file_precise_info_container)
var fileModification: TextView = itemView.findViewById(R.id.file_modification)
var fileSize: TextView = itemView.findViewById(R.id.file_size)
}

View File

@@ -1,74 +1,71 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.SortNodeEnum
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.setTextSize
import com.kunzisoft.keepass.view.strikeOut
import java.util.*
class NodeAdapter
/**
* Create node list adapter with contextMenu or not
* @param context Context to use
*/
class NodeAdapter (private val context: Context)
(private val context: Context)
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
private var nodeComparator: Comparator<NodeVersionedInterface<Group>>? = null
private val nodeSortedListCallback: NodeSortedListCallback
private val nodeSortedList: SortedList<Node>
private val nodeSortedList: SortedList<NodeVersioned>
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
private var textSizeUnit: Int = TypedValue.COMPLEX_UNIT_PX
private var prefSizeMultiplier: Float = 0F
private var subtextDefaultDimension: Float = 0F
private var infoTextDefaultDimension: Float = 0F
private var numberChildrenTextDefaultDimension: Float = 0F
private var iconDefaultDimension: Float = 0F
private var prefTextSize: Float = 0F
private var subtextSize: Float = 0F
private var infoTextSize: Float = 0F
private var numberChildrenTextSize: Float = 0F
private var iconSize: Float = 0F
private var listSort: SortNodeEnum = SortNodeEnum.DB
private var ascendingSort: Boolean = true
private var groupsBeforeSort: Boolean = true
private var recycleBinBottomSort: Boolean = true
private var showUserNames: Boolean = true
private var showNumberEntries: Boolean = true
private var entryFilters = arrayOf<Group.ChildFilter>()
private var actionNodesList = LinkedList<Node>()
private var actionNodesList = LinkedList<NodeVersioned>()
private var nodeClickCallback: NodeClickCallback? = null
private val mDatabase: Database
@@ -84,15 +81,23 @@ class NodeAdapter (private val context: Context)
get() = nodeSortedList.size() <= 0
init {
this.infoTextDefaultDimension = context.resources.getDimension(R.dimen.list_medium_size_default)
this.subtextDefaultDimension = context.resources.getDimension(R.dimen.list_small_size_default)
this.numberChildrenTextDefaultDimension = context.resources.getDimension(R.dimen.list_tiny_size_default)
this.iconDefaultDimension = context.resources.getDimension(R.dimen.list_icon_size_default)
assignPreferences()
this.nodeSortedListCallback = NodeSortedListCallback()
this.nodeSortedList = SortedList(Node::class.java, nodeSortedListCallback)
this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback<NodeVersioned>(this) {
override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int {
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
}
override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean {
return oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
}
override fun areItemsTheSame(item1: NodeVersioned, item2: NodeVersioned): Boolean {
return item1 == item2
}
})
// Database
this.mDatabase = Database.getInstance()
@@ -107,23 +112,20 @@ class NodeAdapter (private val context: Context)
taTextColor.recycle()
}
fun assignPreferences() {
this.prefSizeMultiplier = PreferencesUtil.getListTextSize(context)
notifyChangeSort(
PreferencesUtil.getListSort(context),
SortNodeEnum.SortNodeParameters(
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context)
)
)
private fun assignPreferences() {
this.prefTextSize = PreferencesUtil.getListTextSize(context)
this.infoTextSize = context.resources.getDimension(R.dimen.list_medium_size_default) * prefTextSize
this.subtextSize = context.resources.getDimension(R.dimen.list_small_size_default) * prefTextSize
this.numberChildrenTextSize = context.resources.getDimension(R.dimen.list_tiny_size_default) * prefTextSize
this.iconSize = context.resources.getDimension(R.dimen.list_icon_size_default) * prefTextSize
this.listSort = PreferencesUtil.getListSort(context)
this.ascendingSort = PreferencesUtil.getAscendingSort(context)
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context)
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
this.entryFilters = Group.ChildFilter.getDefaults(context)
// Reinit textSize for all view type
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
}
@@ -131,29 +133,19 @@ class NodeAdapter (private val context: Context)
/**
* Rebuild the list by clear and build children from the group
*/
fun rebuildList(group: Group) {
fun rebuildList(group: GroupVersioned) {
this.nodeSortedList.clear()
assignPreferences()
nodeSortedList.replaceAll(group.getFilteredChildren(*entryFilters)
)
try {
this.nodeSortedList.addAll(group.getChildren())
} catch (e: Exception) {
Log.e(TAG, "Can't add node elements to the list", e)
Toast.makeText(context, "Can't add node elements to the list : " + e.message, Toast.LENGTH_LONG).show()
}
notifyDataSetChanged()
}
private inner class NodeSortedListCallback: SortedListAdapterCallback<Node>(this) {
override fun compare(item1: Node, item2: Node): Int {
return nodeComparator!!.compare(item1, item2)
}
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
return oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
}
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
return item1 == item2
}
}
fun contains(node: Node): Boolean {
fun contains(node: NodeVersioned): Boolean {
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
}
@@ -161,7 +153,7 @@ class NodeAdapter (private val context: Context)
* Add a node to the list
* @param node Node to add
*/
fun addNode(node: Node) {
fun addNode(node: NodeVersioned) {
nodeSortedList.add(node)
}
@@ -169,7 +161,7 @@ class NodeAdapter (private val context: Context)
* Add nodes to the list
* @param nodes Nodes to add
*/
fun addNodes(nodes: List<Node>) {
fun addNodes(nodes: List<NodeVersioned>) {
nodeSortedList.addAll(nodes)
}
@@ -177,7 +169,7 @@ class NodeAdapter (private val context: Context)
* Remove a node in the list
* @param node Node to delete
*/
fun removeNode(node: Node) {
fun removeNode(node: NodeVersioned) {
nodeSortedList.remove(node)
}
@@ -185,7 +177,7 @@ class NodeAdapter (private val context: Context)
* Remove nodes in the list
* @param nodes Nodes to delete
*/
fun removeNodes(nodes: List<Node>) {
fun removeNodes(nodes: List<NodeVersioned>) {
nodes.forEach { node ->
nodeSortedList.remove(node)
}
@@ -217,7 +209,7 @@ class NodeAdapter (private val context: Context)
* @param oldNode Node before the update
* @param newNode Node after the update
*/
fun updateNode(oldNode: Node, newNode: Node) {
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) {
nodeSortedList.beginBatchedUpdates()
nodeSortedList.remove(oldNode)
nodeSortedList.add(newNode)
@@ -229,7 +221,7 @@ class NodeAdapter (private val context: Context)
* @param oldNodes Nodes before the update
* @param newNodes Node after the update
*/
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
fun updateNodes(oldNodes: List<NodeVersioned>, newNodes: List<NodeVersioned>) {
nodeSortedList.beginBatchedUpdates()
oldNodes.forEach { oldNode ->
nodeSortedList.remove(oldNode)
@@ -238,11 +230,11 @@ class NodeAdapter (private val context: Context)
nodeSortedList.endBatchedUpdates()
}
fun notifyNodeChanged(node: Node) {
fun notifyNodeChanged(node: NodeVersioned) {
notifyItemChanged(nodeSortedList.indexOf(node))
}
fun setActionNodes(actionNodes: List<Node>) {
fun setActionNodes(actionNodes: List<NodeVersioned>) {
this.actionNodesList.apply {
clear()
addAll(actionNodes)
@@ -261,9 +253,10 @@ class NodeAdapter (private val context: Context)
/**
* Notify a change sort of the list
*/
fun notifyChangeSort(sortNodeEnum: SortNodeEnum,
sortNodeParameters: SortNodeEnum.SortNodeParameters) {
this.nodeComparator = sortNodeEnum.getNodeComparator(sortNodeParameters)
fun notifyChangeSort(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean) {
this.listSort = sortNodeEnum
this.ascendingSort = ascending
this.groupsBeforeSort = groupsBefore
}
override fun getItemViewType(position: Int): Int {
@@ -290,57 +283,19 @@ class NodeAdapter (private val context: Context)
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
// Relative size of the icon
layoutParams?.apply {
height = (iconDefaultDimension * prefSizeMultiplier).toInt()
width = (iconDefaultDimension * prefSizeMultiplier).toInt()
height = iconSize.toInt()
width = iconSize.toInt()
}
}
// Assign text
holder.text.apply {
text = subNode.title
setTextSize(textSizeUnit, infoTextDefaultDimension, prefSizeMultiplier)
strikeOut(subNode.isCurrentlyExpires)
setTextSize(textSizeUnit, infoTextSize)
paintFlags = if (subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
}
// Add subText with username
holder.subText.apply {
text = ""
strikeOut(subNode.isCurrentlyExpires)
visibility = View.GONE
}
// Specific elements for entry
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
holder.subText.apply {
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextDefaultDimension, prefSizeMultiplier)
}
}
mDatabase.stopManageEntry(entry)
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group)
.getNumberOfChildEntries(*entryFilters)
.toString()
setTextSize(textSizeUnit, numberChildrenTextDefaultDimension, prefSizeMultiplier)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
// Assign click
holder.container.setOnClickListener {
nodeClickCallback?.onNodeClick(subNode)
@@ -350,8 +305,47 @@ class NodeAdapter (private val context: Context)
}
holder.container.isSelected = actionNodesList.contains(subNode)
// Add subText with username
holder.subText.apply {
text = ""
paintFlags = if (subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
visibility = View.GONE
if (subNode.type == Type.ENTRY) {
val entry = subNode as EntryVersioned
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextSize)
}
mDatabase.stopManageEntry(entry)
}
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
setTextSize(textSizeUnit, numberChildrenTextSize)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
}
override fun getItemCount(): Int {
return nodeSortedList.size()
}
@@ -367,8 +361,8 @@ class NodeAdapter (private val context: Context)
* Callback listener to redefine to do an action when a node is click
*/
interface NodeClickCallback {
fun onNodeClick(node: Node)
fun onNodeLongClick(node: Node): Boolean
fun onNodeClick(node: NodeVersioned)
fun onNodeLongClick(node: NodeVersioned): Boolean
}
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
@@ -22,30 +22,26 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.database.Cursor
import android.graphics.Color
import androidx.cursoradapter.widget.CursorAdapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.cursor.EntryCursorKDB
import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX
import com.kunzisoft.keepass.database.cursor.EntryCursor
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwIcon
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.strikeOut
import java.util.*
class SearchEntryCursorAdapter(private val context: Context,
private val database: Database)
class SearchEntryCursorAdapter(context: Context, private val database: Database)
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
private val cursorInflater: LayoutInflater? = context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
private val cursorInflater: LayoutInflater = context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
private var displayUsername: Boolean = false
private val iconColor: Int
@@ -64,7 +60,7 @@ class SearchEntryCursorAdapter(private val context: Context,
override fun newView(context: Context, cursor: Cursor, parent: ViewGroup): View {
val view = cursorInflater!!.inflate(R.layout.item_search_entry, parent, false)
val view = cursorInflater.inflate(R.layout.item_search_entry, parent, false)
val viewHolder = ViewHolder()
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon)
viewHolder.textViewTitle = view.findViewById(R.id.entry_text)
@@ -75,89 +71,56 @@ class SearchEntryCursorAdapter(private val context: Context,
}
override fun bindView(view: View, context: Context, cursor: Cursor) {
getEntryFrom(cursor)?.let { currentEntry ->
val viewHolder = view.tag as ViewHolder
// Assign image
viewHolder.imageViewIcon?.assignDatabaseIcon(
database.drawFactory,
currentEntry.icon,
iconColor)
// Assign title
viewHolder.textViewTitle?.apply {
text = currentEntry.getVisualTitle()
strikeOut(currentEntry.isCurrentlyExpires)
}
// Assign subtitle
viewHolder.textViewSubTitle?.apply {
val entryUsername = currentEntry.username
text = if (displayUsername && entryUsername.isNotEmpty()) {
String.format("(%s)", entryUsername)
} else {
""
}
strikeOut(currentEntry.isCurrentlyExpires)
}
// Retrieve elements from cursor
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))
val iconFactory = database.iconFactory
var icon: PwIcon = iconFactory.getIcon(
UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
if (icon.isUnknown) {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)))
if (icon.isUnknown)
icon = iconFactory.keyIcon
}
}
val title = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE))
val username = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME))
val url = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL))
private fun getEntryFrom(cursor: Cursor): Entry? {
return database.createEntry()?.apply {
database.startManageEntry(this)
entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconFactory)
}
entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconFactory)
}
database.stopManageEntry(this)
val viewHolder = view.tag as ViewHolder
// Assign image
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
// Assign title
val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString())
viewHolder.textViewTitle?.text = showTitle
if (displayUsername && username.isNotEmpty()) {
viewHolder.textViewSubTitle?.text = String.format("(%s)", username)
} else {
viewHolder.textViewSubTitle?.text = ""
}
}
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return searchEntries(context, constraint.toString())
}
private fun searchEntries(context: Context, query: String): Cursor? {
var cursorKDB: EntryCursorKDB? = null
var cursorKDBX: EntryCursorKDBX? = null
if (database.type == DatabaseKDB.TYPE)
cursorKDB = EntryCursorKDB()
if (database.type == DatabaseKDBX.TYPE)
cursorKDBX = EntryCursorKDBX()
val searchGroup = database.createVirtualGroupFromSearch(query, SearchHelper.MAX_SEARCH_ENTRY)
if (searchGroup != null) {
// Search in hide entries but not meta-stream
for (entry in searchGroup.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) {
entry.entryKDB?.let {
cursorKDB?.addEntry(it)
}
entry.entryKDBX?.let {
cursorKDBX?.addEntry(it)
}
}
}
return cursorKDB ?: cursorKDBX
}
fun getEntryFromPosition(position: Int): Entry? {
var pwEntry: Entry? = null
val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) {
pwEntry = getEntryFrom(cursor)
}
return pwEntry
}
private class ViewHolder {
internal var imageViewIcon: ImageView? = null
internal var textViewTitle: TextView? = null
internal var textViewSubTitle: TextView? = null
}
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return database.searchEntries(constraint.toString())
}
fun getEntryFromPosition(position: Int): EntryVersioned? {
var pwEntry: EntryVersioned? = null
val cursor = this.cursor
if (cursor.moveToFirst() && cursor.move(position)) {
pwEntry = database.getEntryFrom(cursor)
}
return pwEntry
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import android.os.AsyncTask

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.Database

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import android.content.Context

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.*

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import android.os.Parcel

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
@@ -112,14 +112,6 @@ class FileDatabaseHistoryAction(applicationContext: Context) {
).execute()
}
fun deleteKeyFileByDatabaseUri(databaseUri: Uri) {
ActionDatabaseAsyncTask(
{
databaseFileHistoryDao.deleteKeyFileByDatabaseUri(databaseUri.toString())
}
).execute()
}
fun deleteAllKeyFiles() {
ActionDatabaseAsyncTask(
{

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.*
@@ -38,9 +19,6 @@ interface FileDatabaseHistoryDao {
@Delete
fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int
@Query("UPDATE file_database_history SET keyfile_uri=null WHERE database_uri = :databaseUriString")
fun deleteKeyFileByDatabaseUri(databaseUriString: String)
@Query("UPDATE file_database_history SET keyfile_uri=null")
fun deleteAllKeyFiles()

View File

@@ -1,22 +1,3 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.ColumnInfo

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.autofill
@@ -26,22 +26,15 @@ import android.content.Intent
import android.os.Build
import android.service.autofill.Dataset
import android.service.autofill.FillResponse
import androidx.annotation.RequiresApi
import android.util.Log
import android.view.autofill.AutofillManager
import android.view.autofill.AutofillValue
import android.widget.RemoteViews
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import java.util.*
@RequiresApi(api = Build.VERSION_CODES.O)
@@ -50,7 +43,6 @@ object AutofillHelper {
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
private const val ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
fun retrieveAssistStructure(intent: Intent?): AssistStructure? {
intent?.let {
@@ -64,44 +56,27 @@ object AutofillHelper {
return String.format("%s (%s)", entryInfo.title, entryInfo.username)
if (entryInfo.title.isNotEmpty())
return entryInfo.title
if (entryInfo.url.isNotEmpty())
return entryInfo.url
if (entryInfo.username.isNotEmpty())
return entryInfo.username
if (entryInfo.url.isNotEmpty())
return entryInfo.url
return ""
}
internal fun addHeader(responseBuilder: FillResponse.Builder,
packageName: String,
webDomain: String?,
applicationId: String?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (webDomain != null) {
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_web_domain).apply {
setTextViewText(R.id.autofill_web_domain_text, webDomain)
})
} else if (applicationId != null) {
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_app_id).apply {
setTextViewText(R.id.autofill_app_id_text, applicationId)
})
}
}
}
internal fun buildDataset(context: Context,
entryInfo: EntryInfo,
struct: StructureParser.Result): Dataset? {
private fun buildDataset(context: Context,
entryInfo: EntryInfo,
struct: StructureParser.Result): Dataset? {
val title = makeEntryTitle(entryInfo)
val views = newRemoteViews(context, title, entryInfo.icon)
val views = newRemoteViews(context.packageName, title)
val builder = Dataset.Builder(views)
builder.setId(entryInfo.id)
struct.usernameId?.let { usernameId ->
builder.setValue(usernameId, AutofillValue.forText(entryInfo.username))
}
struct.passwordId?.let { password ->
builder.setValue(password, AutofillValue.forText(entryInfo.password))
}
struct.password.forEach { id -> builder.setValue(id, AutofillValue.forText(entryInfo.password)) }
val ids = ArrayList(struct.username)
if (entryInfo.username.contains("@") || struct.username.isEmpty())
ids.addAll(struct.email)
ids.forEach { id -> builder.setValue(id, AutofillValue.forText(entryInfo.username)) }
return try {
builder.build()
@@ -112,16 +87,9 @@ object AutofillHelper {
}
/**
* Build the Autofill response for one entry
* Method to hit when right key is selected
*/
fun buildResponse(activity: Activity, entryInfo: EntryInfo) {
buildResponse(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
}
/**
* Build the Autofill response for many entry
*/
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) {
fun buildResponseWhenEntrySelected(activity: Activity, entryInfo: EntryInfo) {
var setResultOk = false
activity.intent?.extras?.let { extras ->
if (extras.containsKey(ASSIST_STRUCTURE)) {
@@ -129,9 +97,8 @@ object AutofillHelper {
StructureParser(structure).parse()?.let { result ->
// New Response
val responseBuilder = FillResponse.Builder()
entriesInfo.forEach {
responseBuilder.addDataset(buildDataset(activity, it, result))
}
val dataset = buildDataset(activity, entryInfo, result)
responseBuilder.addDataset(dataset)
val mReplyIntent = Intent()
Log.d(activity.javaClass.name, "Successed Autofill auth.")
mReplyIntent.putExtra(
@@ -149,48 +116,12 @@ object AutofillHelper {
}
}
/**
* Utility method to perform actions if item is found or not after an auto search in [database]
*/
fun checkAutoSearchInfo(context: Context,
database: Database,
searchInfo: SearchInfo?,
onItemsFound: (items: List<EntryInfo>) -> Unit,
onItemNotFound: () -> Unit,
onDatabaseClosed: () -> Unit) {
if (database.loaded && TimeoutHelper.checkTime(context)) {
var searchWithoutUI = false
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
&& searchInfo != null) {
// If search provide results
database.createVirtualGroupFromSearch(searchInfo, SearchHelper.MAX_SEARCH_ENTRY)?.let { searchGroup ->
if (searchGroup.getNumberOfChildEntries() > 0) {
searchWithoutUI = true
onItemsFound.invoke(
searchGroup.getChildEntriesInfo(database))
}
}
}
if (!searchWithoutUI) {
onItemNotFound.invoke()
}
} else {
onDatabaseClosed.invoke()
}
}
/**
* Utility method to start an activity with an Autofill for result
*/
fun startActivityForAutofillResult(activity: Activity,
intent: Intent,
assistStructure: AssistStructure,
searchInfo: SearchInfo?) {
fun startActivityForAutofillResult(activity: Activity, intent: Intent, assistStructure: AssistStructure) {
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent)
intent.putExtra(ASSIST_STRUCTURE, assistStructure)
searchInfo?.let {
intent.putExtra(KEY_SEARCH_INFO, it)
}
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
}
@@ -209,18 +140,9 @@ object AutofillHelper {
}
}
private fun newRemoteViews(context: Context,
remoteViewsText: String,
remoteViewsIcon: IconImage? = null): RemoteViews {
val presentation = RemoteViews(context.packageName, R.layout.item_autofill_entry)
presentation.setTextViewText(R.id.autofill_entry_text, remoteViewsText)
if (remoteViewsIcon != null) {
presentation.assignDatabaseIcon(context,
R.id.autofill_entry_icon,
Database.getInstance().drawFactory,
remoteViewsIcon,
ContextCompat.getColor(context, R.color.green))
}
private fun newRemoteViews(packageName: String, remoteViewsText: String): RemoteViews {
val presentation = RemoteViews(packageName, R.layout.item_autofill_service)
presentation.setTextViewText(R.id.text, remoteViewsText)
return presentation
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.autofill
@@ -31,7 +31,8 @@ import androidx.appcompat.app.AppCompatActivity
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
import com.kunzisoft.keepass.activities.GroupActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
@RequiresApi(api = Build.VERSION_CODES.O)
class AutofillLauncherActivity : AppCompatActivity() {
@@ -40,31 +41,11 @@ class AutofillLauncherActivity : AppCompatActivity() {
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
val assistStructure = AutofillHelper.retrieveAssistStructure(intent)
if (assistStructure != null) {
// Build search param
val searchInfo = SearchInfo().apply {
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
if (Database.getInstance().loaded && TimeoutHelper.checkTime(this))
GroupActivity.launchForAutofillResult(this, assistStructure, PreferencesUtil.enableReadOnlyDatabase(this))
else {
FileDatabaseSelectActivity.launchForAutofillResult(this, assistStructure)
}
// If database is open
AutofillHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
// Items found
AutofillHelper.buildResponse(this, items)
finish()
},
{
// Show the database UI to select the entry
GroupActivity.launchForAutofillResult(this,
assistStructure)
},
{
// If database not open
FileDatabaseSelectActivity.launchForAutofillResult(this,
assistStructure, searchInfo)
}
)
} else {
setResult(Activity.RESULT_CANCELED)
finish()
@@ -80,20 +61,10 @@ class AutofillLauncherActivity : AppCompatActivity() {
companion object {
private const val KEY_SEARCH_APPLICATION_ID = "KEY_SEARCH_APPLICATION_ID"
private const val KEY_SEARCH_DOMAIN = "KEY_SEARCH_DOMAIN"
fun getAuthIntentSenderForResponse(context: Context,
searchInfo: SearchInfo? = null): IntentSender {
fun getAuthIntentSenderForResponse(context: Context): IntentSender {
val intent = Intent(context, AutofillLauncherActivity::class.java)
return PendingIntent.getActivity(context, 0,
// Doesn't work with Parcelable (don't know why?)
Intent(context, AutofillLauncherActivity::class.java).apply {
searchInfo?.let {
putExtra(KEY_SEARCH_APPLICATION_ID, it.applicationId)
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
}
},
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
intent, PendingIntent.FLAG_CANCEL_CURRENT).intentSender
}
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.autofill
@@ -22,78 +22,31 @@ package com.kunzisoft.keepass.autofill
import android.os.Build
import android.os.CancellationSignal
import android.service.autofill.*
import androidx.annotation.RequiresApi
import android.util.Log
import android.widget.RemoteViews
import androidx.annotation.RequiresApi
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.model.SearchInfo
@RequiresApi(api = Build.VERSION_CODES.O)
class KeeAutofillService : AutofillService() {
override fun onFillRequest(request: FillRequest,
cancellationSignal: CancellationSignal,
override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal,
callback: FillCallback) {
val fillContexts = request.fillContexts
val latestStructure = fillContexts[fillContexts.size - 1].structure
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
cancellationSignal.setOnCancelListener { Log.e(TAG, "Cancel autofill not implemented in this sample.") }
val responseBuilder = FillResponse.Builder()
// Check user's settings for authenticating Responses and Datasets.
StructureParser(latestStructure).parse()?.let { parseResult ->
val searchInfo = SearchInfo().apply {
applicationId = parseResult.applicationId
webDomain = parseResult.domain
}
AutofillHelper.checkAutoSearchInfo(this,
Database.getInstance(),
searchInfo,
{ items ->
val responseBuilder = FillResponse.Builder()
AutofillHelper.addHeader(responseBuilder, packageName,
parseResult.domain, parseResult.applicationId)
items.forEach {
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
}
callback.onSuccess(responseBuilder.build())
},
{
// Show UI if no search result
showUIForEntrySelection(parseResult, searchInfo, callback)
},
{
// Show UI if database not open
showUIForEntrySelection(parseResult, searchInfo, callback)
}
)
}
}
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
searchInfo: SearchInfo,
callback: FillCallback) {
parseResult.allAutofillIds().let { autofillIds ->
if (autofillIds.isNotEmpty()) {
val parseResult = StructureParser(latestStructure).parse()
parseResult?.allAutofillIds()?.let { autofillIds ->
if (listOf(*autofillIds).isNotEmpty()) {
// If the entire Autofill Response is authenticated, AuthActivity is used
// to generate Response.
val sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this,
searchInfo)
val responseBuilder = FillResponse.Builder()
val remoteViewsUnlock: RemoteViews = if (!parseResult.domain.isNullOrEmpty()) {
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
setTextViewText(R.id.autofill_web_domain_text, parseResult.domain)
}
} else if (!parseResult.applicationId.isNullOrEmpty()) {
RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply {
setTextViewText(R.id.autofill_app_id_text, parseResult.applicationId)
}
} else {
RemoteViews(packageName, R.layout.item_autofill_unlock)
}
responseBuilder.setAuthentication(autofillIds, sender, remoteViewsUnlock)
val sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this)
val presentation = RemoteViews(packageName, R.layout.item_autofill_service_unlock)
responseBuilder.setAuthentication(autofillIds, sender, presentation)
callback.onSuccess(responseBuilder.build())
}
}

View File

@@ -1,27 +1,27 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.autofill
import android.app.assist.AssistStructure
import android.os.Build
import android.text.InputType
import androidx.annotation.RequiresApi
import android.text.InputType
import android.util.Log
import android.view.View
import android.view.autofill.AutofillId
@@ -37,196 +37,72 @@ internal class StructureParser(private val structure: AssistStructure) {
private var usernameCandidate: AutofillId? = null
fun parse(): Result? {
try {
result = Result()
result?.apply {
usernameCandidate = null
mainLoop@ for (i in 0 until structure.windowNodeCount) {
val windowNode = structure.getWindowNodeAt(i)
applicationId = windowNode.title.toString().split("/")[0]
Log.d(TAG, "Autofill applicationId: $applicationId")
if (parseViewNode(windowNode.rootViewNode))
break@mainLoop
result = Result()
result?.apply {
usernameCandidate = null
for (i in 0 until structure.windowNodeCount) {
val windowNode = structure.getWindowNodeAt(i)
title.add(windowNode.title)
windowNode.rootViewNode.webDomain?.let {
webDomain.add(it)
}
// If not explicit username field found, add the field just before password field.
if (usernameId == null && passwordId != null && usernameCandidate != null)
usernameId = usernameCandidate
parseViewNode(windowNode.rootViewNode)
}
// Return the result only if password field is retrieved
return if (result?.usernameId != null
&& result?.passwordId != null)
result
else
null
} catch (e: Exception) {
return null
// If not explicit username field found, add the field just before password field.
if (username.isEmpty() && email.isEmpty()
&& password.isNotEmpty() && usernameCandidate != null)
username.add(usernameCandidate!!)
}
return result
}
private fun parseViewNode(node: AssistStructure.ViewNode): Boolean {
// Get the domain of a web app
node.webDomain?.let {
result?.domain = it
Log.d(TAG, "Autofill domain: $it")
}
// Only parse visible nodes
if (node.visibility == View.VISIBLE) {
if (node.autofillId != null
&& node.autofillType == View.AUTOFILL_TYPE_TEXT) {
// Parse methods
val hints = node.autofillHints
if (hints != null && hints.isNotEmpty()) {
if (parseNodeByAutofillHint(node))
return true
} else if (parseNodeByHtmlAttributes(node))
return true
else if (parseNodeByAndroidInput(node))
return true
}
// Recursive method to process each node
for (i in 0 until node.childCount) {
if (parseViewNode(node.getChildAt(i)))
return true
}
}
return false
}
private fun parseNodeByAutofillHint(node: AssistStructure.ViewNode): Boolean {
private fun parseViewNode(node: AssistStructure.ViewNode) {
val hints = node.autofillHints
val autofillId = node.autofillId
node.autofillHints?.forEach {
when {
it.equals(View.AUTOFILL_HINT_USERNAME, true)
|| it.equals(View.AUTOFILL_HINT_EMAIL_ADDRESS, true)
|| it.equals(View.AUTOFILL_HINT_PHONE, true)
|| it.equals("usernameOrEmail", true)-> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username hint")
if (autofillId != null) {
if (hints != null && hints.isNotEmpty()) {
when {
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_USERNAME == it } -> result?.username?.add(autofillId)
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_EMAIL_ADDRESS == it } -> result?.email?.add(autofillId)
Arrays.stream(hints).anyMatch { View.AUTOFILL_HINT_PASSWORD == it } -> result?.password?.add(autofillId)
else -> Log.d(TAG, "unsupported hints")
}
it.equals(View.AUTOFILL_HINT_PASSWORD, true)
|| it.contains("password", true) -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password hint")
return true
} else if (node.autofillType == View.AUTOFILL_TYPE_TEXT) {
val inputType = node.inputType
when {
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS > 0 -> result?.email?.add(autofillId)
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD > 0 -> result?.password?.add(autofillId)
result?.password?.isEmpty() == true -> usernameCandidate = autofillId
}
// Ignore autocomplete="off"
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
it.equals("off", true) ||
it.equals("on", true) -> {
Log.d(TAG, "Autofill web hint")
return parseNodeByHtmlAttributes(node)
}
else -> Log.d(TAG, "Autofill unsupported hint $it")
}
}
return false
}
private fun parseNodeByHtmlAttributes(node: AssistStructure.ViewNode): Boolean {
val autofillId = node.autofillId
val nodHtml = node.htmlInfo
when (nodHtml?.tag?.toLowerCase(Locale.ENGLISH)) {
"input" -> {
nodHtml.attributes?.forEach { pairAttribute ->
when (pairAttribute.first.toLowerCase(Locale.ENGLISH)) {
"type" -> {
when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) {
"tel", "email" -> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
}
"text" -> {
usernameCandidate = autofillId
Log.d(TAG, "Autofill username candidate web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
}
"password" -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
return true
}
}
}
}
}
}
}
return false
}
private fun parseNodeByAndroidInput(node: AssistStructure.ViewNode): Boolean {
val autofillId = node.autofillId
val inputType = node.inputType
if (inputType and InputType.TYPE_CLASS_TEXT != 0) {
when {
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != 0 -> {
result?.usernameId = autofillId
Log.d(TAG, "Autofill username android type: $inputType")
}
inputType and InputType.TYPE_TEXT_VARIATION_NORMAL != 0 ||
inputType and InputType.TYPE_NUMBER_VARIATION_NORMAL != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_PERSON_NAME != 0 -> {
usernameCandidate = autofillId
Log.d(TAG, "Autofill username candidate android type: $inputType")
}
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != 0 ||
inputType and InputType.TYPE_NUMBER_VARIATION_PASSWORD != 0 -> {
result?.passwordId = autofillId
Log.d(TAG, "Autofill password android type: $inputType")
return true
}
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_FILTER != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_PHONETIC != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_URI != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != 0 ||
inputType and InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != 0 -> {
// Type not used
}
else -> {
Log.d(TAG, "Autofill unknown android type: $inputType")
usernameCandidate = autofillId
}
}
}
return false
for (i in 0 until node.childCount)
parseViewNode(node.getChildAt(i))
}
@RequiresApi(api = Build.VERSION_CODES.O)
internal class Result {
var applicationId: String? = null
var domain: String? = null
set(value) {
if (field == null)
field = value
}
val title: MutableList<CharSequence>
val webDomain: MutableList<String>
val username: MutableList<AutofillId>
val email: MutableList<AutofillId>
val password: MutableList<AutofillId>
var usernameId: AutofillId? = null
set(value) {
if (field == null)
field = value
}
var passwordId: AutofillId? = null
set(value) {
if (field == null)
field = value
}
init {
title = ArrayList()
webDomain = ArrayList()
username = ArrayList()
email = ArrayList()
password = ArrayList()
}
fun allAutofillIds(): Array<AutofillId> {
val all = ArrayList<AutofillId>()
usernameId?.let {
all.add(it)
}
passwordId?.let {
all.add(it)
}
all.addAll(username)
all.addAll(email)
all.addAll(password)
return all.toTypedArray()
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.backup

View File

@@ -1,35 +1,13 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.Menu
import android.view.MenuInflater
import android.widget.CompoundButton
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricConstants
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
@@ -41,12 +19,12 @@ import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
@RequiresApi(api = Build.VERSION_CODES.M)
class AdvancedUnlockedManager(var context: FragmentActivity,
var databaseFileUri: Uri,
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
private var checkboxPasswordView: CompoundButton?,
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
var advancedUnlockInfoView: AdvancedUnlockInfoView?,
var checkboxPasswordView: CompoundButton?,
var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
var passwordView: TextView?,
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
@@ -56,59 +34,65 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
init {
// fingerprint related code here
fun initBiometric() {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper == null || !biometricUnlockDatabaseHelper!!.isBiometricInitialized) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context, this)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.setAuthenticationCallback(biometricCallback)
}
// Add a check listener to change fingerprint mode
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
checkBiometricAvailability()
// Add old listener to enable the button, only be call here because of onCheckedChange bug
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
}
checkBiometricAvailability()
}
/**
* Check biometric availability and change the current mode depending of device's state
*/
@Synchronized
fun checkBiometricAvailability() {
// biometric not supported (by API level or hardware) so keep option hidden
// fingerprint not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate()
if (!PreferencesUtil.isBiometricUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.UNAVAILABLE)
} else {
// biometric is available but not configured, show icon but in disabled state with some information
// fingerprint is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
toggleMode(Mode.NOT_CONFIGURED)
} else {
// Check if fingerprint well init (be called the first time the fingerprint is configured
// and the activity still active)
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
// callback for fingerprint findings
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
}
// Recheck to change the mode
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
toggleMode(Mode.STORE)
} else {
if (checkboxPasswordView?.isChecked == true) {
// listen for encryption
toggleMode(Mode.STORE_CREDENTIAL)
} else {
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
// biometric available but no stored password found yet for this DB so show info don't listen
toggleMode( if (containsCipher) {
// listen for decryption
Mode.EXTRACT_CREDENTIAL
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
// fingerprint available but no stored password found yet for this DB so show info don't listen
toggleMode( if (it) {
// listen for decryption
Mode.OPEN
} else {
// wait for typing
Mode.WAIT_CREDENTIAL
})
}
}
}
@@ -122,7 +106,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
}
private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
private val biometricCallback = object : BiometricPrompt.AuthenticationCallback () {
override fun onAuthenticationError(
errorCode: Int,
@@ -143,15 +127,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
context.runOnUiThread {
when (biometricMode) {
Mode.UNAVAILABLE -> {}
Mode.BIOMETRIC_NOT_CONFIGURED -> {}
Mode.KEY_MANAGER_UNAVAILABLE -> {}
Mode.WAIT_CREDENTIAL -> {}
Mode.STORE_CREDENTIAL -> {
Mode.UNAVAILABLE -> {
}
Mode.PAUSE -> {
}
Mode.NOT_CONFIGURED -> {
}
Mode.WAIT_CREDENTIAL -> {
}
Mode.STORE -> {
// newly store the entered password in encrypted way
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
}
Mode.EXTRACT_CREDENTIAL -> {
Mode.OPEN -> {
// retrieve the encrypted value from preferences
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
it?.encryptedValue?.let { value ->
@@ -167,7 +155,11 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
private fun initNotAvailable() {
showFingerPrintViews(false)
advancedUnlockInfoView?.setIconViewClickListener(false, null)
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initPause() {
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initNotConfigured() {
@@ -175,19 +167,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.configure_biometric)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
}
}
private fun initKeyManagerNotAvailable() {
showFingerPrintViews(true)
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
}
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initWaitData() {
@@ -195,19 +175,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("")
advancedUnlockInfoView?.setIconViewClickListener(false) {
biometricAuthenticationCallback.onAuthenticationError(
BiometricConstants.ERROR_UNABLE_TO_PROCESS
, context.getString(R.string.credential_before_click_biometric_button))
}
}
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject,
promptInfo: BiometricPrompt.PromptInfo) {
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, cryptoObject)
}
advancedUnlockInfoView?.setIconViewClickListener(null)
}
private fun initEncryptData() {
@@ -220,7 +188,9 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
cryptoObject?.let { crypto ->
// Set listener to open the biometric dialog and save credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, crypto)
}
}
}
@@ -241,13 +211,17 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
cryptoObject?.let { crypto ->
// Set listener to open the biometric dialog and check credential
advancedUnlockInfoView?.setIconViewClickListener { _ ->
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, crypto)
}
}
// Auto open the biometric prompt
if (isBiometricPromptAutoOpenEnable) {
isBiometricPromptAutoOpenEnable = false
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
context.runOnUiThread {
biometricPrompt?.authenticate(promptInfo, crypto)
}
}
}
@@ -261,19 +235,28 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun initBiometricMode() {
when (biometricMode) {
Mode.UNAVAILABLE -> initNotAvailable()
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
Mode.PAUSE -> initPause()
Mode.NOT_CONFIGURED -> initNotConfigured()
Mode.WAIT_CREDENTIAL -> initWaitData()
Mode.STORE_CREDENTIAL -> initEncryptData()
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
Mode.STORE -> initEncryptData()
Mode.OPEN -> initDecryptData()
}
// Show fingerprint key deletion
context.invalidateOptionsMenu()
}
fun pause() {
biometricMode = Mode.PAUSE
initBiometricMode()
}
fun destroy() {
// Restore the checked listener
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
biometricMode = Mode.UNAVAILABLE
initBiometricMode()
biometricUnlockDatabaseHelper = null
}
// Only to fix multiple fingerprint menu #332
@@ -282,7 +265,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
if (!addBiometricMenuInProgress) {
addBiometricMenuInProgress = true
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.BIOMETRIC_NOT_CONFIGURED)
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.NOT_CONFIGURED)
&& it) {
menuInflater.inflate(R.menu.advanced_unlock, menu)
addBiometricMenuInProgress = false
@@ -294,7 +277,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
fun deleteEntryKey() {
biometricUnlockDatabaseHelper?.deleteEntryKey()
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri)
biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED
biometricMode = Mode.NOT_CONFIGURED
checkBiometricAvailability()
}
@@ -313,9 +296,8 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
override fun onBiometricException(e: Exception) {
e.localizedMessage?.let {
setAdvancedUnlockedMessageView(it)
}
if (e.localizedMessage != null)
setAdvancedUnlockedMessageView(e.localizedMessage)
}
private fun showFingerPrintViews(show: Boolean) {
@@ -341,7 +323,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
}
enum class Mode {
UNAVAILABLE, BIOMETRIC_NOT_CONFIGURED, KEY_MANAGER_UNAVAILABLE, WAIT_CREDENTIAL, STORE_CREDENTIAL, EXTRACT_CREDENTIAL
UNAVAILABLE, PAUSE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN
}
companion object {

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
@@ -42,7 +42,8 @@ import javax.crypto.SecretKey
import javax.crypto.spec.IvParameterSpec
@RequiresApi(api = Build.VERSION_CODES.M)
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
private val biometricUnlockCallback: BiometricUnlockCallback) {
private var biometricPrompt: BiometricPrompt? = null
@@ -52,14 +53,12 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private var keyguardManager: KeyguardManager? = null
private var cryptoObject: BiometricPrompt.CryptoObject? = null
private var isKeyManagerInit = false
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
var biometricUnlockCallback: BiometricUnlockCallback? = null
private var isBiometricInit = false
private var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
setConfirmationRequired(true)
// TODO device credential
/*
if (keyguardManager?.isDeviceSecure == true)
@@ -71,8 +70,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
setConfirmationRequired(false)
setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
// TODO device credential
/*
if (keyguardManager?.isDeviceSecure == true)
@@ -82,20 +80,20 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
setNegativeButtonText(context.getString(android.R.string.cancel))
}.build()
val isKeyManagerInitialized: Boolean
val isBiometricInitialized: Boolean
get() {
if (!isKeyManagerInit) {
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
if (!isBiometricInit) {
biometricUnlockCallback.onBiometricException(Exception("Biometric not initialized"))
}
return isKeyManagerInit
return isBiometricInit
}
init {
if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) {
// really not much to do when no fingerprint support found
isKeyManagerInit = false
isBiometricInit = false
} else {
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
try {
this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE)
@@ -105,19 +103,17 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
+ BIOMETRIC_BLOCKS_MODES + "/"
+ BIOMETRIC_ENCRYPTION_PADDING)
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
isKeyManagerInit = (keyStore != null
&& keyGenerator != null
&& cipher != null)
isBiometricInit = true
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize the keystore", e)
isKeyManagerInit = false
biometricUnlockCallback?.onBiometricException(e)
isBiometricInit = false
biometricUnlockCallback.onBiometricException(e)
}
}
}
private fun getSecretKey(): SecretKey? {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return null
}
try {
@@ -143,14 +139,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
}
} catch (e: Exception) {
Log.e(TAG, "Unable to create a key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey?
}
} catch (e: Exception) {
Log.e(TAG, "Unable to retrieve the key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
return null
}
@@ -159,7 +155,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
@@ -172,18 +168,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
biometricUnlockCallback.onInvalidKeyException(unrecoverableKeyException)
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
biometricUnlockCallback.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize encrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun encryptData(value: String) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
@@ -193,20 +190,21 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
// passes updated iv spec on to callback so this can be stored for decryption
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
biometricUnlockCallback.handleEncryptedResult(encryptedBase64, ivSpecValue)
}
} catch (e: Exception) {
Log.e(TAG, "Unable to encrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
: (biometricPrompt: BiometricPrompt?,
cryptoObject: BiometricPrompt.CryptoObject?,
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
@@ -226,30 +224,32 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
deleteEntryKey()
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
biometricUnlockCallback.onInvalidKeyException(invalidKeyException)
} catch (e: Exception) {
Log.e(TAG, "Unable to initialize decrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun decryptData(encryptedValue: String) {
if (!isKeyManagerInitialized) {
if (!isBiometricInitialized) {
return
}
try {
// actual decryption here
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
cipher?.doFinal(encrypted)?.let { decrypted ->
biometricUnlockCallback?.handleDecryptedResult(String(decrypted))
biometricUnlockCallback.handleDecryptedResult(String(decrypted))
}
} catch (badPaddingException: BadPaddingException) {
Log.e(TAG, "Unable to decrypt data", badPaddingException)
biometricUnlockCallback?.onInvalidKeyException(badPaddingException)
biometricUnlockCallback.onInvalidKeyException(badPaddingException)
} catch (e: Exception) {
Log.e(TAG, "Unable to decrypt data", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun deleteEntryKey() {
@@ -258,10 +258,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY)
} catch (e: Exception) {
Log.e(TAG, "Unable to delete entry key in keystore", e)
biometricUnlockCallback?.onBiometricException(e)
biometricUnlockCallback.onBiometricException(e)
}
}
fun setAuthenticationCallback(authenticationCallback: BiometricPrompt.AuthenticationCallback) {
this.authenticationCallback = authenticationCallback
}
@Synchronized
fun initBiometricPrompt() {
if (biometricPrompt == null) {
@@ -295,24 +299,22 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
* Remove entry key in keystore
*/
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
biometricCallback: BiometricUnlockErrorCallback) {
BiometricUnlockDatabaseHelper(context).apply {
biometricUnlockCallback = object : BiometricUnlockCallback {
biometricUnlockCallback: BiometricUnlockErrorCallback) {
val fingerPrintHelper = BiometricUnlockDatabaseHelper(context, object : BiometricUnlockCallback {
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
override fun handleDecryptedResult(decryptedValue: String) {}
override fun handleDecryptedResult(decryptedValue: String) {}
override fun onInvalidKeyException(e: Exception) {
biometricCallback.onInvalidKeyException(e)
}
override fun onBiometricException(e: Exception) {
biometricCallback.onBiometricException(e)
}
override fun onInvalidKeyException(e: Exception) {
biometricUnlockCallback.onInvalidKeyException(e)
}
deleteEntryKey()
}
override fun onBiometricException(e: Exception) {
biometricUnlockCallback.onBiometricException(e)
}
})
fingerPrintHelper.deleteEntryKey()
}
}

View File

@@ -1,63 +1,62 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
import android.content.Context
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import androidx.annotation.RequiresApi
import android.widget.ImageView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.kunzisoft.keepass.R
@RequiresApi(api = Build.VERSION_CODES.M)
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
private val scanFingerprint: AnimatedVectorDrawableCompat? =
AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint)
private val scanFingerprint: AnimatedVectorDrawable =
context.getDrawable(R.drawable.scan_fingerprint) as AnimatedVectorDrawable
init {
imageView.setImageDrawable(scanFingerprint)
}
private var animationCallback = object : Animatable2Compat.AnimationCallback() {
private var animationCallback = object : Animatable2.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable) {
imageView.post {
scanFingerprint?.start()
}
if (!scanFingerprint.isRunning)
scanFingerprint.start()
}
}
fun startScan() {
scanFingerprint?.registerAnimationCallback(animationCallback)
scanFingerprint.registerAnimationCallback(animationCallback)
if (scanFingerprint?.isRunning != true)
scanFingerprint?.start()
if (!scanFingerprint.isRunning)
scanFingerprint.start()
}
fun stopScan() {
scanFingerprint?.unregisterAnimationCallback(animationCallback)
scanFingerprint.unregisterAnimationCallback(animationCallback)
if (scanFingerprint?.isRunning == true)
if (scanFingerprint.isRunning)
scanFingerprint.stop()
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto

View File

@@ -1,31 +1,33 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.longTo8Bytes
import java.io.IOException
import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import java.util.Arrays
import javax.crypto.Mac
import kotlin.math.min
@@ -58,7 +60,7 @@ object CryptoUtil {
throw RuntimeException(e)
}
val pbR = longTo8Bytes(r)
val pbR = LEDataOutputStream.writeLongBuf(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto;

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto

View File

@@ -1,28 +1,28 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
@@ -41,28 +41,13 @@ class AesEngine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.AESRijndael
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0x31.toByte(), 0xC1.toByte(), 0xF2.toByte(), 0xE6.toByte(), 0xBF.toByte(), 0x71.toByte(), 0x43.toByte(), 0x50.toByte(), 0xBE.toByte(), 0x58.toByte(), 0x05.toByte(), 0x21.toByte(), 0x6A.toByte(), 0xFC.toByte(), 0x5A.toByte(), 0xFF.toByte()))
}
}

View File

@@ -1,26 +1,26 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import org.spongycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -44,28 +44,13 @@ class ChaCha20Engine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.ChaCha20
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.ChaCha20
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xD6.toByte(), 0x03.toByte(), 0x8A.toByte(), 0x2B.toByte(), 0x8B.toByte(), 0x6F.toByte(), 0x4C.toByte(), 0xB5.toByte(), 0xA5.toByte(), 0x24.toByte(), 0x33.toByte(), 0x9A.toByte(), 0x31.toByte(), 0xDB.toByte(), 0xB5.toByte(), 0x9A.toByte()))
}
}

View File

@@ -1,25 +1,25 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -46,6 +46,6 @@ abstract class CipherEngine {
return getCipher(opmode, key, IV, false)
}
abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
abstract fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm
}

View File

@@ -1,31 +1,33 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
import com.kunzisoft.keepass.utils.Types
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.*
import java.util.UUID
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
@@ -45,28 +47,13 @@ class TwofishEngine : CipherEngine() {
return cipher
}
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
return PwEncryptionAlgorithm.Twofish
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xAD.toByte(), 0x68.toByte(), 0xF2.toByte(), 0x9F.toByte(), 0x57.toByte(), 0x6F.toByte(), 0x4B.toByte(), 0xB9.toByte(), 0xA3.toByte(), 0x6A.toByte(), 0xD4.toByte(), 0x7A.toByte(), 0xF9.toByte(), 0x65.toByte(), 0x34.toByte(), 0x6C.toByte()))
}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.finalkey;

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
@@ -23,7 +23,7 @@ import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.Types
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -32,7 +32,7 @@ class AesKdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters
get() {
return KdfParameters(uuid!!).apply {
return KdfParameters(uuid).apply {
setParamUUID()
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
}
@@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() {
private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = bytes16ToUuid(
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xC9.toByte(),
0xD9.toByte(),
0xF3.toByte(),

View File

@@ -1,27 +1,27 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.utils.Types
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -30,7 +30,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
override val defaultParameters: KdfParameters
get() {
val p = KdfParameters(uuid!!)
val p = KdfParameters(uuid)
p.setParamUUID()
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
@@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
val CIPHER_UUID: UUID = Types.bytestoUUID(
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
0x6D.toByte(),

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation;

View File

@@ -1,25 +1,25 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.database.ObjectNameResource
import java.io.IOException
import java.io.Serializable

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation;
import com.kunzisoft.keepass.utils.VariantDictionary;
import com.kunzisoft.keepass.stream.LEDataInputStream;
import com.kunzisoft.keepass.stream.LEDataOutputStream;
import com.kunzisoft.keepass.utils.Types;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.UUID;
public class KdfParameters extends VariantDictionary {
private UUID kdfUUID;
private static final String ParamUUID = "$UUID";
KdfParameters(UUID uuid) {
kdfUUID = uuid;
}
public UUID getUUID() {
return kdfUUID;
}
protected void setParamUUID() {
setByteArray(ParamUUID, Types.UUIDtoBytes(kdfUUID));
}
public static KdfParameters deserialize(byte[] data) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
LEDataInputStream lis = new LEDataInputStream(bis);
VariantDictionary d = VariantDictionary.deserialize(lis);
if (d == null) {
return null;
}
UUID uuid = Types.bytestoUUID(d.getByteArray(ParamUUID));
KdfParameters kdfP = new KdfParameters(uuid);
kdfP.copyTo(d);
return kdfP;
}
public static byte[] serialize(KdfParameters kdf) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
LEDataOutputStream los = new LEDataOutputStream(bos);
KdfParameters.serialize(kdf, los);
return bos.toByteArray();
}
}

View File

@@ -1,67 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.stream.uuidTo16Bytes
import com.kunzisoft.keepass.utils.VariantDictionary
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.*
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() {
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
}
companion object {
private const val PARAM_UUID = "\$UUID"
@Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data)
val lis = LittleEndianDataInputStream(bis)
val d = deserialize(lis) ?: return null
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
val kdfP = KdfParameters(uuid)
kdfP.copyTo(d)
return kdfP
}
@Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream()
val los = LittleEndianDataOutputStream(bos)
serialize(kdf, los)
return bos.toByteArray()
}
}
}

View File

@@ -1,23 +1,23 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
package com.kunzisoft.keepass.database
/** "Delegate" class for operating on each group when traversing all of

View File

@@ -1,23 +1,23 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.utils
package com.kunzisoft.keepass.database
import android.content.res.Resources

View File

@@ -0,0 +1,170 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.NodeVersioned
import com.kunzisoft.keepass.database.element.Type
import java.util.*
enum class SortNodeEnum {
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator<NodeVersioned> {
return when (this) {
DB -> NodeNaturalComparator(ascending, groupsBefore, false) // Force false because natural order contains recycle bin
TITLE -> NodeTitleComparator(ascending, groupsBefore, recycleBinBottom)
USERNAME -> NodeUsernameComparator(ascending, groupsBefore, recycleBinBottom)
CREATION_TIME -> NodeCreationComparator(ascending, groupsBefore, recycleBinBottom)
LAST_MODIFY_TIME -> NodeLastModificationComparator(ascending, groupsBefore, recycleBinBottom)
LAST_ACCESS_TIME -> NodeLastAccessComparator(ascending, groupsBefore, recycleBinBottom)
}
}
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<NodeVersioned> {
abstract fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int
private fun specificOrderOrHashIfEquals(object1: NodeVersioned, object2: NodeVersioned): Int {
val specificOrderComp = compareBySpecificOrder(object1, object2)
return if (specificOrderComp == 0) {
object1.hashCode() - object2.hashCode()
} else if (!ascending) -specificOrderComp else specificOrderComp // If descending, revert
}
override fun compare(object1: NodeVersioned,object2: NodeVersioned): Int {
if (object1 == object2)
return 0
if (object1.type == Type.GROUP) {
return if (object2.type == Type.GROUP) {
// RecycleBin at end of groups
if (recycleBinBottom) {
if (Database.getInstance().recycleBin == object1)
return 1
if (Database.getInstance().recycleBin == object2)
return -1
}
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.ENTRY) {
if (groupsBefore)
-1
else
1
} else {
-1
}
} else if (object1.type == Type.ENTRY) {
return if (object2.type == Type.ENTRY) {
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.GROUP) {
if (groupsBefore)
1
else
-1
} else {
-1
}
}
// Type not known
return -1
}
}
/**
* Comparator of node by natural database placement
*/
class NodeNaturalComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.nodePositionInParent.compareTo(object2.nodePositionInParent)
}
}
/**
* Comparator of Node by Title
*/
class NodeTitleComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.title.compareTo(object2.title, ignoreCase = true)
}
}
/**
* Comparator of Node by Username, Groups by title
*/
class NodeUsernameComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) {
// To get username if it's a ref
return (object1 as EntryVersioned).getEntryInfo(Database.getInstance()).username
.compareTo((object2 as EntryVersioned).getEntryInfo(Database.getInstance()).username,
ignoreCase = true)
}
return NodeTitleComparator(ascending, groupsBefore, recycleBinBottom).compare(object1, object2)
}
}
/**
* Comparator of node by creation
*/
class NodeCreationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.creationTime.date
.compareTo(object2.creationTime.date)
}
}
/**
* Comparator of node by last modification
*/
class NodeLastModificationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastModificationTime.date
.compareTo(object2.lastModificationTime.date)
}
}
/**
* Comparator of node by last access
*/
class NodeLastAccessComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int {
return object1.lastAccessTime.date
.compareTo(object2.lastAccessTime.date)
}
}
}

View File

@@ -1,43 +1,43 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.utils.UriUtil
open class AssignPasswordInDatabaseRunnable (
open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
context: Context,
database: Database,
protected val mDatabaseUri: Uri,
withMasterPassword: Boolean,
masterPassword: String?,
withKeyFile: Boolean,
keyFile: Uri?)
: SaveDatabaseRunnable(context, database, true) {
keyFile: Uri?,
save: Boolean,
actionRunnable: ActionRunnable? = null)
: SaveDatabaseRunnable(context, database, save, actionRunnable) {
private var mMasterPassword: String? = null
protected var mKeyFile: Uri? = null
private var mKeyFile: Uri? = null
private var mBackupKey: ByteArray? = null
@@ -48,7 +48,7 @@ open class AssignPasswordInDatabaseRunnable (
this.mKeyFile = keyFile
}
override fun onStartRun() {
override fun run() {
// Set key
try {
// TODO move master key methods
@@ -57,24 +57,17 @@ open class AssignPasswordInDatabaseRunnable (
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFile)
database.retrieveMasterKey(mMasterPassword, uriInputStream)
// To save the database
super.run()
finishRun(true)
} catch (e: Exception) {
erase(mBackupKey)
setError(e)
finishRun(false, e.message)
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
// Erase the biometric
CipherDatabaseAction.getInstance(context)
.deleteByDatabaseUri(mDatabaseUri)
// Erase the register keyfile
FileDatabaseHistoryAction.getInstance(context)
.deleteKeyFileByDatabaseUri(mDatabaseUri)
override fun onFinishRun(result: Result) {
if (!result.isSuccess) {
// Erase the current master key
erase(database.masterKey)
@@ -82,6 +75,8 @@ open class AssignPasswordInDatabaseRunnable (
database.masterKey = it
}
}
super.onFinishRun(result)
}
/**

View File

@@ -1,70 +1,58 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
class CreateDatabaseRunnable(context: Context,
private val mDatabaseUri: Uri,
private val mDatabase: Database,
databaseUri: Uri,
private val databaseName: String,
private val rootName: String,
withMasterPassword: Boolean,
masterPassword: String?,
withKeyFile: Boolean,
keyFile: Uri?)
: AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) {
keyFile: Uri?,
save: Boolean,
actionRunnable: ActionRunnable? = null)
: AssignPasswordInDatabaseRunnable(context, mDatabase, withMasterPassword, masterPassword, withKeyFile, keyFile, save, actionRunnable) {
override fun onStartRun() {
override fun run() {
try {
// Create new database record
mDatabase.apply {
createData(mDatabaseUri, databaseName, rootName)
createData(mDatabaseUri)
// Set Database state
loaded = true
// Commit changes
super.run()
}
finishRun(true)
} catch (e: Exception) {
mDatabase.closeAndClear()
setError(e)
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
if (result.isSuccess) {
// Add database to recent files
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context.applicationContext)
.addOrUpdateDatabaseUri(mDatabaseUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mKeyFile else null)
}
} else {
Log.e("CreateDatabaseRunnable", "Unable to create the database")
finishRun(false, e.message)
}
}
override fun onFinishRun(result: Result) {}
}

View File

@@ -1,20 +1,20 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
* This file is part of KeePass DX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX is distributed in the hope that it will be useful,
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
@@ -25,9 +25,7 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
@@ -42,18 +40,16 @@ class LoadDatabaseRunnable(private val context: Context,
private val mOmitBackup: Boolean,
private val mFixDuplicateUUID: Boolean,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mDuplicateUuidAction: ((Result) -> Unit)?)
: ActionRunnable() {
actionFinishRunnable: ActionRunnable?)
: ActionRunnable(actionFinishRunnable, executeNestedActionIfResultFalse = true) {
private val cacheDirectory = context.applicationContext.filesDir
override fun onStartRun() {
// Clear before we load
mDatabase.closeAndClear(cacheDirectory)
}
override fun onActionRun() {
override fun run() {
try {
// Clear before we load
mDatabase.closeAndClear(cacheDirectory)
mDatabase.loadData(mUri, mPass, mKey,
mReadonly,
context.contentResolver,
@@ -61,37 +57,35 @@ class LoadDatabaseRunnable(private val context: Context,
mOmitBackup,
mFixDuplicateUUID,
progressTaskUpdater)
}
catch (e: DuplicateUuidDatabaseException) {
mDuplicateUuidAction?.invoke(result)
setError(e)
}
catch (e: LoadDatabaseException) {
setError(e)
}
}
override fun onFinishRun() {
if (result.isSuccess) {
// Save keyFile in app database
if (PreferencesUtil.rememberDatabaseLocations(context)) {
val rememberKeyFile = PreferencesUtil.rememberKeyFiles(context)
if (rememberKeyFile) {
var keyUri = mKey
if (!rememberKeyFile) {
keyUri = null
}
FileDatabaseHistoryAction.getInstance(context)
.addOrUpdateDatabaseUri(mUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mKey else null)
.addOrUpdateDatabaseUri(mUri, keyUri)
}
// Register the biometric
mCipherEntity?.let { cipherDatabaseEntity ->
CipherDatabaseAction.getInstance(context)
.addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
.addOrUpdateCipherDatabase(cipherDatabaseEntity) {
finishRun(true)
}
} ?: run {
finishRun(true)
}
}
catch (e: LoadDatabaseException) {
finishRun(false, e)
}
}
// Register the current time to init the lock timer
PreferencesUtil.saveCurrentTime(context)
// Start the opening notification
DatabaseOpenNotificationService.start(context)
} else {
override fun onFinishRun(result: Result) {
if (!result.isSuccess) {
mDatabase.closeAndClear(cacheDirectory)
}
}

Some files were not shown because too many files have changed in this diff Show More