mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
421 Commits
2.5.0.0bet
...
2.5.0.0bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
462c29b769 | ||
|
|
3939276d58 | ||
|
|
ff85f18c4c | ||
|
|
dd14fe4123 | ||
|
|
d804659ee2 | ||
|
|
e3152cf901 | ||
|
|
de236f321f | ||
|
|
66f4611866 | ||
|
|
6a6ef052af | ||
|
|
4e4606dabd | ||
|
|
0ce11103ab | ||
|
|
899d0e0557 | ||
|
|
4aefeff41f | ||
|
|
08d59e50e8 | ||
|
|
e6c06aba8c | ||
|
|
e5c2a04922 | ||
|
|
c134ccf8d9 | ||
|
|
4f959d1ff6 | ||
|
|
1c06d93951 | ||
|
|
b2dff29baa | ||
|
|
72633e9472 | ||
|
|
898a88f7d8 | ||
|
|
c2f09f6666 | ||
|
|
d45bcbc347 | ||
|
|
1f834567f8 | ||
|
|
0b62c04867 | ||
|
|
c71bb2a570 | ||
|
|
0a575b5bf8 | ||
|
|
6f2bf903c5 | ||
|
|
210ad4b8db | ||
|
|
b42cf0e204 | ||
|
|
2045d3aab8 | ||
|
|
e20cb9431d | ||
|
|
7161eaea8b | ||
|
|
b786da52f5 | ||
|
|
ccbfec838d | ||
|
|
f5c1872225 | ||
|
|
3001013bad | ||
|
|
1280803e5e | ||
|
|
f84fc07fe0 | ||
|
|
d83e53f589 | ||
|
|
52ba2c53f1 | ||
|
|
f266e1de4c | ||
|
|
5a0aafd3ce | ||
|
|
c7ce07a43c | ||
|
|
a73644c285 | ||
|
|
e28f1ffc99 | ||
|
|
afc691b2f4 | ||
|
|
00f6eb83c3 | ||
|
|
bbf51ebbec | ||
|
|
7816c8f16e | ||
|
|
fdbcba2412 | ||
|
|
50dc6cb0aa | ||
|
|
e413198d12 | ||
|
|
72592772f9 | ||
|
|
f918206bcd | ||
|
|
6b34d67da8 | ||
|
|
04fb093d4d | ||
|
|
7de0e0cc4a | ||
|
|
5cf7688e38 | ||
|
|
97cd06b52b | ||
|
|
6d522ead1d | ||
|
|
8e0fae62f3 | ||
|
|
af72098d60 | ||
|
|
62da582f4e | ||
|
|
f3efa6eddc | ||
|
|
c85fb7bd0a | ||
|
|
fc7b183461 | ||
|
|
a378810f88 | ||
|
|
2f5b322fad | ||
|
|
efb9b50f85 | ||
|
|
84fdef8eb6 | ||
|
|
76050a261b | ||
|
|
2667361450 | ||
|
|
a2b3313cc0 | ||
|
|
d343446235 | ||
|
|
cbce70f8a4 | ||
|
|
4573d6b6fb | ||
|
|
973305d13c | ||
|
|
4637016372 | ||
|
|
698ba4f7f1 | ||
|
|
502ebabf1f | ||
|
|
13f88626ca | ||
|
|
a39f58f7b5 | ||
|
|
aeb36468ce | ||
|
|
fdd196526d | ||
|
|
ecca892b16 | ||
|
|
03343d0301 | ||
|
|
a61c8b0cb6 | ||
|
|
a92129da00 | ||
|
|
0b783d6390 | ||
|
|
9aa1a2e30e | ||
|
|
15b3c69514 | ||
|
|
f2722e09ec | ||
|
|
b442859be0 | ||
|
|
1496a2efb1 | ||
|
|
a0edb111f0 | ||
|
|
6bcf54fe29 | ||
|
|
3d7e24816a | ||
|
|
f5e02ec63f | ||
|
|
ed1f4ebace | ||
|
|
eb0e496cfd | ||
|
|
8b9fc30a6d | ||
|
|
12c2a6e99c | ||
|
|
714433b62d | ||
|
|
e42933d786 | ||
|
|
e9531e4edd | ||
|
|
0cd9cd294d | ||
|
|
b03fb12fca | ||
|
|
b7b2e8dc4e | ||
|
|
96568abc51 | ||
|
|
a180688858 | ||
|
|
2590067558 | ||
|
|
b5499238f7 | ||
|
|
cc5b96f539 | ||
|
|
32343dc937 | ||
|
|
1e71dd3031 | ||
|
|
ebf6f6a52a | ||
|
|
6bf6d661af | ||
|
|
fe16879f35 | ||
|
|
ead384d1bb | ||
|
|
1ebdc0bacd | ||
|
|
8eca8cdd53 | ||
|
|
24c61b1b37 | ||
|
|
ea18cc7166 | ||
|
|
387c499829 | ||
|
|
339470dd6e | ||
|
|
02d1089dbc | ||
|
|
1bc0932cec | ||
|
|
76cc2739c8 | ||
|
|
b23908aec2 | ||
|
|
6b1b8c0f7b | ||
|
|
06320a7eba | ||
|
|
fc02145d0c | ||
|
|
5360738775 | ||
|
|
0957df752a | ||
|
|
fe56d06b5d | ||
|
|
f4955b16cd | ||
|
|
07692ba73d | ||
|
|
64ac3e8f32 | ||
|
|
8013d3109a | ||
|
|
be6f01dc99 | ||
|
|
30f7779ec6 | ||
|
|
6b5823ca70 | ||
|
|
d4a09ed569 | ||
|
|
84fe409c4b | ||
|
|
51d90891ad | ||
|
|
b1fa06099c | ||
|
|
fa9d8ad6ad | ||
|
|
6703d7b451 | ||
|
|
73afd44d9c | ||
|
|
93cb93bb9b | ||
|
|
82902cff71 | ||
|
|
3657f7e54c | ||
|
|
a57a2f738d | ||
|
|
b93592d703 | ||
|
|
fd288e624b | ||
|
|
095fa3f6ef | ||
|
|
b1f6c578ad | ||
|
|
1bc991bfcb | ||
|
|
02feb478e8 | ||
|
|
c2df79f0c9 | ||
|
|
ef13965747 | ||
|
|
13421601de | ||
|
|
5a9b46c4b5 | ||
|
|
6268642097 | ||
|
|
12eadbc092 | ||
|
|
c9475d1dc2 | ||
|
|
56805defb6 | ||
|
|
477a784201 | ||
|
|
f54bac15c9 | ||
|
|
ae28ebe701 | ||
|
|
f16adf00da | ||
|
|
d17699f6f7 | ||
|
|
8afcf043ee | ||
|
|
dda9d034aa | ||
|
|
652bad51b4 | ||
|
|
4d2ccc0789 | ||
|
|
0e1c21e0f4 | ||
|
|
8d3f58b2cc | ||
|
|
be78905d85 | ||
|
|
b3f232c840 | ||
|
|
075a16c1c3 | ||
|
|
18a13e6266 | ||
|
|
7149bdbc3a | ||
|
|
9a4c4aa9bf | ||
|
|
2b32cab9d1 | ||
|
|
66611db261 | ||
|
|
fdc2095b42 | ||
|
|
2f9cab0da2 | ||
|
|
bd0d474751 | ||
|
|
cca0ab2669 | ||
|
|
cb5ca575d5 | ||
|
|
f4caaad9ee | ||
|
|
b9cfb32a20 | ||
|
|
095e5e5dd6 | ||
|
|
ffc58688d8 | ||
|
|
6a69a7f416 | ||
|
|
b4188b4712 | ||
|
|
4a22c28df4 | ||
|
|
76e9a25b1a | ||
|
|
1928d0823e | ||
|
|
c183d22412 | ||
|
|
b684353721 | ||
|
|
72f0e871c7 | ||
|
|
9a63962903 | ||
|
|
938de28b49 | ||
|
|
20fc094d71 | ||
|
|
40180d5883 | ||
|
|
59e5865318 | ||
|
|
f63d6bdc1d | ||
|
|
fe33c0ae7d | ||
|
|
ca4ad1c1fd | ||
|
|
adf5382804 | ||
|
|
7f5406ac98 | ||
|
|
23b21ea154 | ||
|
|
49d4d0421a | ||
|
|
23859a61bb | ||
|
|
221f81f51e | ||
|
|
6e7c0d5073 | ||
|
|
e8e3d53685 | ||
|
|
e6d9df2b98 | ||
|
|
477a8f2e38 | ||
|
|
5e66697b8b | ||
|
|
16320abb7d | ||
|
|
f122c2832c | ||
|
|
d84c561f44 | ||
|
|
da49c9c045 | ||
|
|
553920e37c | ||
|
|
450d2d113b | ||
|
|
744c80e34d | ||
|
|
c0f8cca7c6 | ||
|
|
b129f220f7 | ||
|
|
7a3df02e38 | ||
|
|
befd29c396 | ||
|
|
b8245621ea | ||
|
|
ecda25a743 | ||
|
|
d97a85b997 | ||
|
|
8c0d7ab9ed | ||
|
|
f3fa73ea34 | ||
|
|
788734ccad | ||
|
|
e088f4a4ad | ||
|
|
86bd018e4e | ||
|
|
283145034d | ||
|
|
163162497e | ||
|
|
56911fb58f | ||
|
|
dae6481aff | ||
|
|
6b2eb5e4f6 | ||
|
|
c563787f73 | ||
|
|
2737755b85 | ||
|
|
fd9486ca77 | ||
|
|
14020ec0b5 | ||
|
|
5a6c466ebd | ||
|
|
76fcd5fe19 | ||
|
|
3732ff1ebc | ||
|
|
22dd09954b | ||
|
|
ef7387f2f3 | ||
|
|
f774298587 | ||
|
|
f8134307f6 | ||
|
|
fe461f2e7c | ||
|
|
023c841747 | ||
|
|
af95c0903a | ||
|
|
0d756db8aa | ||
|
|
2c5dcc9b11 | ||
|
|
21c6ea73b2 | ||
|
|
51dc302bb0 | ||
|
|
87760ab4f6 | ||
|
|
88ebe58a88 | ||
|
|
fb023b81b5 | ||
|
|
2de6bbc6c0 | ||
|
|
4ef436629d | ||
|
|
9fd342f1e7 | ||
|
|
8988f17765 | ||
|
|
9d160db281 | ||
|
|
2e58c2f1b3 | ||
|
|
d1d2b99e09 | ||
|
|
def9744f75 | ||
|
|
214e2cf109 | ||
|
|
b25180c617 | ||
|
|
6a5263df77 | ||
|
|
2982f67717 | ||
|
|
b559670dff | ||
|
|
891d3142d2 | ||
|
|
2637788429 | ||
|
|
a21de3b892 | ||
|
|
e087e19120 | ||
|
|
721d61dda7 | ||
|
|
e0e7e431cf | ||
|
|
93948e7c61 | ||
|
|
b150c718a0 | ||
|
|
a71e4c3902 | ||
|
|
b7dc13d863 | ||
|
|
1e3c58e359 | ||
|
|
5f75599e9f | ||
|
|
b602f9b77d | ||
|
|
aa948c1ece | ||
|
|
e599a51152 | ||
|
|
ee6052f4d1 | ||
|
|
eba527f477 | ||
|
|
09e0d6d3cc | ||
|
|
9aefc984be | ||
|
|
2ce3b21f1b | ||
|
|
4d2f3cb4b1 | ||
|
|
e62b46c4c0 | ||
|
|
6472601170 | ||
|
|
89dd7bfefb | ||
|
|
fb2ea4c0ed | ||
|
|
8d84358d48 | ||
|
|
1d8661c633 | ||
|
|
48130eee45 | ||
|
|
2cf83962fe | ||
|
|
aecf7c0c39 | ||
|
|
39606e2676 | ||
|
|
6e00fa2d01 | ||
|
|
f79aa339e9 | ||
|
|
f412fce912 | ||
|
|
cc20b7503c | ||
|
|
2573434763 | ||
|
|
f153c26fef | ||
|
|
125f461cbe | ||
|
|
b705b4b712 | ||
|
|
c67b0bb858 | ||
|
|
ab1fc8c5d5 | ||
|
|
8477f4ba08 | ||
|
|
e6518ffdc8 | ||
|
|
99917c7f28 | ||
|
|
fcc29f67a3 | ||
|
|
7dd49f050c | ||
|
|
5f96de84b0 | ||
|
|
54c2f5a61f | ||
|
|
921c6f88aa | ||
|
|
a0cb579df4 | ||
|
|
d6a7c34ff3 | ||
|
|
bf2e61f149 | ||
|
|
eaf5dc5988 | ||
|
|
879ee013db | ||
|
|
e13d53eae4 | ||
|
|
d72c8184c9 | ||
|
|
c4d3c8cbfb | ||
|
|
02a3d85f80 | ||
|
|
19b0722f1f | ||
|
|
f14222b192 | ||
|
|
4f4f6d30d9 | ||
|
|
fdd329e982 | ||
|
|
55a4d388b3 | ||
|
|
5c6be448ec | ||
|
|
3e79ddcc21 | ||
|
|
5362758424 | ||
|
|
c10e3df2a7 | ||
|
|
166784021a | ||
|
|
5615c31e08 | ||
|
|
fb60dd5921 | ||
|
|
ff4c1b779b | ||
|
|
53a7b99567 | ||
|
|
a57103bafb | ||
|
|
2540f32dbf | ||
|
|
499ccd6b7c | ||
|
|
a4359560b9 | ||
|
|
149483cc2d | ||
|
|
a1d2022492 | ||
|
|
891036c35c | ||
|
|
4e16ba5f56 | ||
|
|
7137a2fadb | ||
|
|
9d90d0eaba | ||
|
|
94a9942db5 | ||
|
|
5f347fe106 | ||
|
|
a34a84ae16 | ||
|
|
40b0982298 | ||
|
|
4100258476 | ||
|
|
5f3f6661b7 | ||
|
|
75af97e0ae | ||
|
|
58f158c457 | ||
|
|
ce27eae1f0 | ||
|
|
0aa0b3e993 | ||
|
|
1cc5a08236 | ||
|
|
4c587eeb03 | ||
|
|
ab70c2d014 | ||
|
|
8413160ac5 | ||
|
|
5abc403171 | ||
|
|
9b891013b8 | ||
|
|
9413987355 | ||
|
|
f95b514b41 | ||
|
|
f6985c8944 | ||
|
|
4388d56c52 | ||
|
|
a70fe24c97 | ||
|
|
8e0392753c | ||
|
|
9c9980bba6 | ||
|
|
2226c15d29 | ||
|
|
82a859bd9c | ||
|
|
83873fab81 | ||
|
|
31f2be7b91 | ||
|
|
16458e6646 | ||
|
|
2b9678707d | ||
|
|
cdbb23d7f1 | ||
|
|
23fd1b83f4 | ||
|
|
40b0ebe49b | ||
|
|
7cd8682544 | ||
|
|
d0dd478ac8 | ||
|
|
ffb547c452 | ||
|
|
bd829f129f | ||
|
|
5ad3f62de5 | ||
|
|
b0ec4942bc | ||
|
|
2cbc9675f6 | ||
|
|
116643a45a | ||
|
|
2f0eb283ed | ||
|
|
6d46fccdcd | ||
|
|
f5dc94bfec | ||
|
|
94bdb0e3da | ||
|
|
65360c2a1e | ||
|
|
70d30bdbe6 | ||
|
|
66f7e6d1b1 | ||
|
|
a8ccb67a87 | ||
|
|
66051382f1 | ||
|
|
0fb3028c91 | ||
|
|
5e5baa4892 | ||
|
|
9d1257ed9d | ||
|
|
9d7546053d | ||
|
|
a1b692abe5 | ||
|
|
4e06842d0f | ||
|
|
f04c2ee1da | ||
|
|
9558fcaf21 |
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
44
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**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]
|
||||||
|
- Location: [e.g. Remote file retrieved with GDrive app]
|
||||||
|
- Size: [e.g. 150Mo]
|
||||||
|
- Contains attachment: [e.g. Yes]
|
||||||
|
|
||||||
|
**KeePassDX (please complete the following information):**
|
||||||
|
- Version: [e.g. 2.5.0.0beta23]
|
||||||
|
- Build: [e.g. Free]
|
||||||
|
- Language: [e.g. French]
|
||||||
|
|
||||||
|
**Android (please complete the following information):**
|
||||||
|
- Device: [e.g. GalaxyS8]
|
||||||
|
- Version: [e.g. 8.1]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
|
- Browser for Autofill: [e.g. Chrome version X]
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -38,6 +38,13 @@ proguard/
|
|||||||
# Android Studio captures folder
|
# Android Studio captures folder
|
||||||
captures/
|
captures/
|
||||||
|
|
||||||
|
# Eclipse/VS Code
|
||||||
|
.project
|
||||||
|
.settings/*
|
||||||
|
*/.project
|
||||||
|
*/.classpath
|
||||||
|
*/.settings/*
|
||||||
|
|
||||||
# Intellij
|
# Intellij
|
||||||
*.iml
|
*.iml
|
||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
|
|||||||
31
CHANGELOG
31
CHANGELOG
@@ -1,3 +1,34 @@
|
|||||||
|
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)
|
||||||
|
* 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
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta23)
|
KeepassDX (2.5.0.0beta23)
|
||||||
* New, more secure database creation workflow
|
* New, more secure database creation workflow
|
||||||
* Recognize more database files
|
* Recognize more database files
|
||||||
|
|||||||
45
CONTRIBUTORS
45
CONTRIBUTORS
@@ -1,45 +0,0 @@
|
|||||||
Original author:
|
|
||||||
Brian Pellin
|
|
||||||
|
|
||||||
Achim Weimert
|
|
||||||
Johan Berts - search patches
|
|
||||||
Mike Mohr - Better native code for aes and sha
|
|
||||||
Tobias Selig - icon support
|
|
||||||
Tolga Onbay, Dirk Bergstrom - password generator
|
|
||||||
Space Cowboy - holo theme
|
|
||||||
josefwells
|
|
||||||
Nicholas FitzRoy-Dale - auto launch intents
|
|
||||||
yulin2 - responsiveness improvements
|
|
||||||
Tadashi Saito
|
|
||||||
vhschlenker
|
|
||||||
bumper314 - Samsung multiwindow support
|
|
||||||
Hans Cappelle - fingerprint sensor integration
|
|
||||||
Jeremy Jamet - Keepass DX Material Design - Patches
|
|
||||||
|
|
||||||
Translations:
|
|
||||||
Diego Pierotto - Italian
|
|
||||||
Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French
|
|
||||||
Maciej Bieniek, cod3r - Polish
|
|
||||||
Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian
|
|
||||||
MaWi, rvs2008, meviox, MaDill, EdlerProgrammierer, Jan Thomas - German
|
|
||||||
yslandro - Norwegian Nynorsk
|
|
||||||
王科峰 - Chinese
|
|
||||||
Typhoon - Slovak
|
|
||||||
Masahiro Inamura - Japanese
|
|
||||||
Matsuu Takuto - Japanese
|
|
||||||
Carlos Schlyter - Portugese (Brazil)
|
|
||||||
YSmhXQDd6Z - Portugese (Portugal)
|
|
||||||
andriykopanytsia - Ukranian
|
|
||||||
intel, Zoltán Antal - Hungarian
|
|
||||||
H Vanek - Czech
|
|
||||||
jipanos - Spanish
|
|
||||||
Erik Fdevriendt, Erik Jan Meijer - Dutch
|
|
||||||
Frederik Svarre - Danish
|
|
||||||
Oriol Garrote - Catalan
|
|
||||||
Mika Takala - Finnish
|
|
||||||
Niclas Burgren - Swedish
|
|
||||||
Raimonds - Latvian
|
|
||||||
dgarciabad - Basque
|
|
||||||
Arthur Zamarin - Hebrew
|
|
||||||
RaptorTFX - Greek
|
|
||||||
zygimantus - Lithuanian
|
|
||||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||
KeePass DX is free software: you can redistribute it and/or modify
|
KeePassDX is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
@@ -13,7 +13,7 @@ KeePass DX is free software: you can redistribute it and/or modify
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The KeePass DX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3.
|
The KeePassDX icon was created by Jeremy JAMET and is licensed under the terms of GPLv3.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
31
ReadMe.md
31
ReadMe.md
@@ -1,6 +1,6 @@
|
|||||||
# Android Keepass DX
|
# Android KeepassDX
|
||||||
|
|
||||||
<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/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/screen.jpg" width="220">
|
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
|
||||||
|
|
||||||
@@ -8,26 +8,27 @@
|
|||||||
|
|
||||||
* Create database files / entries and groups
|
* Create database files / entries and groups
|
||||||
* Support for **.kdb** and **.kdbx** files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm
|
* 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, KeePass XC...)
|
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
|
||||||
* Allows **fast copy** of fields and opening of URI / URL
|
* Allows **fast copy** of fields and opening of URI / URL
|
||||||
* **Fingerprint** for fast unlocking
|
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
|
||||||
|
* **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
|
||||||
* Material design with **themes**
|
* Material design with **themes**
|
||||||
* **AutoFill** and Integration
|
* **AutoFill** and Integration
|
||||||
* Field filling **keyboard**
|
* Field filling **keyboard**
|
||||||
* Precise management of **settings**
|
* Precise management of **settings**
|
||||||
* Code written in **native language** *(Kotlin / Java / JNI / C)*
|
* Code written in **native language** *(Kotlin / Java / JNI / C)*
|
||||||
|
|
||||||
Keepass DX is **open source** and **ad-free**.
|
KeepassDX is **open source** and **ad-free**.
|
||||||
|
|
||||||
## What is KeePass DX?
|
## What is KeePassDX?
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Is it really free?
|
## Is it really free?
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
*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.*
|
*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.*
|
||||||
|
|
||||||
@@ -38,7 +39,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/)**.
|
* 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/))
|
* 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.
|
* **[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 KeePass DX
|
* Buy the **[Pro version](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro)** of KeePassDX
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
@@ -54,7 +55,7 @@ You can contribute in different ways to help us on our work.
|
|||||||
|
|
||||||
## F.A.Q.
|
## F.A.Q.
|
||||||
|
|
||||||
Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
|
Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassDX/wiki/F.A.Q.)
|
||||||
|
|
||||||
## Other devices
|
## Other devices
|
||||||
|
|
||||||
@@ -64,21 +65,21 @@ Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
|
Copyright (c) 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
|
||||||
|
|
||||||
This file is part of KeePass DX.
|
This file is part of KeePassDX.
|
||||||
|
|
||||||
[KeePass DX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
|
[KeePassDX](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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
KeePass DX is distributed in the hope that it will be useful,
|
KeePassDX is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
|
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
|
||||||
|
|||||||
1
_config.yml
Normal file
1
_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
||||||
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "com.kunzisoft.keepass"
|
applicationId "com.kunzisoft.keepass"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode = 23
|
versionCode = 26
|
||||||
versionName = "2.5.0.0beta23"
|
versionName = "2.5.0.0beta26"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||||
@@ -27,7 +27,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled = false
|
minifyEnabled = false
|
||||||
@@ -80,7 +79,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def spongycastleVersion = "1.58.0.0"
|
def spongycastleVersion = "1.58.0.0"
|
||||||
def room_version = "2.1.0"
|
def room_version = "2.2.1"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
@@ -89,7 +88,7 @@ dependencies {
|
|||||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.biometric:biometric:1.0.0-beta01'
|
implementation 'androidx.biometric:biometric:1.0.0'
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
|
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
@@ -97,17 +96,17 @@ dependencies {
|
|||||||
|
|
||||||
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
||||||
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
||||||
// Expandable view
|
|
||||||
implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2'
|
|
||||||
// Time
|
// Time
|
||||||
implementation 'joda-time:joda-time:2.9.9'
|
implementation 'joda-time:joda-time:2.9.9'
|
||||||
|
// Color
|
||||||
|
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
|
||||||
// Education
|
// Education
|
||||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
|
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
|
||||||
// Apache Commons Collections
|
// Apache Commons Collections
|
||||||
implementation 'commons-collections:commons-collections:3.2.1'
|
implementation 'commons-collections:commons-collections:3.2.1'
|
||||||
implementation 'org.apache.commons:commons-io:1.3.2'
|
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||||
// Base64
|
// Apache Commons Codec
|
||||||
implementation 'biz.source_code:base64coder:2010-12-19'
|
implementation 'commons-codec:commons-codec:1.11'
|
||||||
// Icon pack
|
// Icon pack
|
||||||
implementation project(path: ':icon-pack-classic')
|
implementation project(path: ':icon-pack-classic')
|
||||||
implementation project(path: ':icon-pack-material')
|
implementation project(path: ':icon-pack-material')
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +1,33 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests
|
package com.kunzisoft.keepass.tests
|
||||||
|
|
||||||
import org.junit.Assert.assertArrayEquals
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
|
||||||
import java.io.ByteArrayOutputStream
|
import com.kunzisoft.keepass.stream.*
|
||||||
import java.util.Calendar
|
|
||||||
import java.util.Random
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwDate
|
class StringDatabaseKDBUtilsTest : TestCase() {
|
||||||
import com.kunzisoft.keepass.stream.LEDataInputStream
|
|
||||||
import com.kunzisoft.keepass.stream.LEDataOutputStream
|
|
||||||
import com.kunzisoft.keepass.utils.Types
|
|
||||||
|
|
||||||
class TypesTest : TestCase() {
|
|
||||||
|
|
||||||
fun testReadWriteLongZero() {
|
fun testReadWriteLongZero() {
|
||||||
testReadWriteLong(0.toByte())
|
testReadWriteLong(0.toByte())
|
||||||
@@ -56,15 +51,9 @@ class TypesTest : TestCase() {
|
|||||||
|
|
||||||
private fun testReadWriteLong(value: Byte) {
|
private fun testReadWriteLong(value: Byte) {
|
||||||
val orig = ByteArray(8)
|
val orig = ByteArray(8)
|
||||||
val dest = ByteArray(8)
|
setArray(orig, value, 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() {
|
fun testReadWriteIntZero() {
|
||||||
@@ -81,24 +70,22 @@ class TypesTest : TestCase() {
|
|||||||
|
|
||||||
private fun testReadWriteInt(value: Byte) {
|
private fun testReadWriteInt(value: Byte) {
|
||||||
val orig = ByteArray(4)
|
val orig = ByteArray(4)
|
||||||
val dest = ByteArray(4)
|
|
||||||
|
|
||||||
for (i in 0..3) {
|
for (i in 0..3) {
|
||||||
orig[i] = 0
|
orig[i] = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
setArray(orig, value, 0, 4)
|
setArray(orig, value, 4)
|
||||||
|
|
||||||
val one = LEDataInputStream.readInt(orig, 0)
|
val one = bytes4ToInt(orig)
|
||||||
|
val dest = intTo4Bytes(one)
|
||||||
LEDataOutputStream.writeInt(one, dest, 0)
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest)
|
assertArrayEquals(orig, dest)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setArray(buf: ByteArray, value: Byte, offset: Int, size: Int) {
|
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
||||||
for (i in offset until offset + size) {
|
for (i in 0 until size) {
|
||||||
buf[i] = value
|
buf[i] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,11 +96,10 @@ class TypesTest : TestCase() {
|
|||||||
orig[0] = 0
|
orig[0] = 0
|
||||||
orig[1] = 1
|
orig[1] = 1
|
||||||
|
|
||||||
val one = LEDataInputStream.readUShort(orig, 0)
|
val one = bytes2ToUShort(orig)
|
||||||
val dest = LEDataOutputStream.writeUShortBuf(one)
|
val dest = uShortTo2Bytes(one)
|
||||||
|
|
||||||
assertArrayEquals(orig, dest)
|
assertArrayEquals(orig, dest)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testReadWriteShortMin() {
|
fun testReadWriteShortMin() {
|
||||||
@@ -126,15 +112,12 @@ class TypesTest : TestCase() {
|
|||||||
|
|
||||||
private fun testReadWriteShort(value: Byte) {
|
private fun testReadWriteShort(value: Byte) {
|
||||||
val orig = ByteArray(2)
|
val orig = ByteArray(2)
|
||||||
val dest = ByteArray(2)
|
setArray(orig, value, 2)
|
||||||
|
|
||||||
setArray(orig, value, 0, 2)
|
val one = bytes2ToUShort(orig)
|
||||||
|
val dest = uShortTo2Bytes(one)
|
||||||
val one = LEDataInputStream.readUShort(orig, 0)
|
|
||||||
LEDataOutputStream.writeUShort(one, dest, 0)
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest)
|
assertArrayEquals(orig, dest)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testReadWriteByteZero() {
|
fun testReadWriteByteZero() {
|
||||||
@@ -150,16 +133,8 @@ class TypesTest : TestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun testReadWriteByte(value: Byte) {
|
private fun testReadWriteByte(value: Byte) {
|
||||||
val orig = ByteArray(1)
|
val dest: Byte = uIntToByte(byteToUInt(value))
|
||||||
val dest = ByteArray(1)
|
assert(value == dest)
|
||||||
|
|
||||||
setArray(orig, value, 0, 1)
|
|
||||||
|
|
||||||
val one = Types.readUByte(orig, 0)
|
|
||||||
Types.writeUByte(one, dest, 0)
|
|
||||||
|
|
||||||
assertArrayEquals(orig, dest)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testDate() {
|
fun testDate() {
|
||||||
@@ -168,27 +143,37 @@ class TypesTest : TestCase() {
|
|||||||
val expected = Calendar.getInstance()
|
val expected = Calendar.getInstance()
|
||||||
expected.set(2008, 1, 2, 3, 4, 5)
|
expected.set(2008, 1, 2, 3, 4, 5)
|
||||||
|
|
||||||
val buf = PwDate.writeTime(expected.time, cal)
|
|
||||||
val actual = Calendar.getInstance()
|
val actual = Calendar.getInstance()
|
||||||
actual.time = PwDate.readTime(buf, 0, cal)
|
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)!!)
|
||||||
|
|
||||||
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
|
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
|
||||||
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
|
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
|
||||||
assertEquals("Day mismatch: ", 1, actual.get(Calendar.DAY_OF_MONTH))
|
assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
|
||||||
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
|
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
|
||||||
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
|
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
|
||||||
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
|
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() {
|
fun testUUID() {
|
||||||
val rnd = Random()
|
|
||||||
val bUUID = ByteArray(16)
|
val bUUID = ByteArray(16)
|
||||||
rnd.nextBytes(bUUID)
|
Random().nextBytes(bUUID)
|
||||||
|
|
||||||
val uuid = Types.bytestoUUID(bUUID)
|
val uuid = bytes16ToUuid(bUUID)
|
||||||
val eUUID = Types.UUIDtoBytes(uuid)
|
val eUUID = uuidTo16Bytes(uuid)
|
||||||
|
|
||||||
|
val lUUID = bytes16ToUuid(bUUID)
|
||||||
|
val leUUID = uuidTo16Bytes(lUUID)
|
||||||
|
|
||||||
assertArrayEquals("UUID match failed", bUUID, eUUID)
|
assertArrayEquals("UUID match failed", bUUID, eUUID)
|
||||||
|
assertArrayEquals("UUID match failed", bUUID, leUUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@@ -199,8 +184,8 @@ class TypesTest : TestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val bos = ByteArrayOutputStream()
|
val bos = ByteArrayOutputStream()
|
||||||
val leos = LEDataOutputStream(bos)
|
val leos = LittleEndianDataOutputStream(bos)
|
||||||
leos.writeLong(Types.ULONG_MAX_VALUE)
|
leos.writeLong(ULONG_MAX_VALUE)
|
||||||
leos.close()
|
leos.close()
|
||||||
|
|
||||||
val uLongMax = bos.toByteArray()
|
val uLongMax = bos.toByteArray()
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.crypto
|
package com.kunzisoft.keepass.tests.crypto
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.crypto
|
package com.kunzisoft.keepass.tests.crypto
|
||||||
@@ -39,9 +39,8 @@ import junit.framework.TestCase
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
import com.kunzisoft.keepass.crypto.engine.AesEngine
|
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.BetterCipherInputStream
|
||||||
import com.kunzisoft.keepass.stream.LEDataInputStream
|
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
|
||||||
|
|
||||||
class CipherTest : TestCase() {
|
class CipherTest : TestCase() {
|
||||||
private val rand = Random()
|
private val rand = Random()
|
||||||
@@ -93,7 +92,7 @@ class CipherTest : TestCase() {
|
|||||||
|
|
||||||
val bis = ByteArrayInputStream(secrettext)
|
val bis = ByteArrayInputStream(secrettext)
|
||||||
val cis = BetterCipherInputStream(bis, decrypt)
|
val cis = BetterCipherInputStream(bis, decrypt)
|
||||||
val lis = LEDataInputStream(cis)
|
val lis = LittleEndianDataInputStream(cis)
|
||||||
|
|
||||||
val decrypttext = lis.readBytes(MESSAGE_LENGTH)
|
val decrypttext = lis.readBytes(MESSAGE_LENGTH)
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.crypto
|
package com.kunzisoft.keepass.tests.crypto
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.stream
|
package com.kunzisoft.keepass.tests.stream
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.utils
|
package com.kunzisoft.keepass.tests.utils
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.kunzisoft.keepass"
|
package="com.kunzisoft.keepass"
|
||||||
android:installLocation="auto">
|
android:installLocation="auto">
|
||||||
<supports-screens
|
<supports-screens
|
||||||
@@ -9,8 +10,8 @@
|
|||||||
android:anyDensity="true" />
|
android:anyDensity="true" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -20,7 +21,10 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup"
|
android:fullBackupContent="@xml/backup"
|
||||||
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
||||||
android:theme="@style/KeepassDXStyle.Night">
|
android:largeHeap="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:theme="@style/KeepassDXStyle.Night"
|
||||||
|
tools:targetApi="n">
|
||||||
<!-- TODO backup API Key -->
|
<!-- TODO backup API Key -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
@@ -131,7 +135,8 @@
|
|||||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
|
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
|
||||||
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
||||||
android:label="@string/keyboard_name">
|
android:label="@string/keyboard_name"
|
||||||
|
android:exported="true">
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
|
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
|
||||||
android:label="@string/keyboard_setting_label">
|
android:label="@string/keyboard_setting_label">
|
||||||
@@ -140,11 +145,18 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
|
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
<service
|
||||||
|
android:name=".notifications.AttachmentFileNotificationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
|
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
|
|||||||
@@ -1,65 +1,90 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
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.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.PwNodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.education.EntryActivityEducation
|
import com.kunzisoft.keepass.education.EntryActivityEducation
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
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.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.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
|
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
|
||||||
|
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
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.EntryContentsView
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
class EntryActivity : LockingHideActivity() {
|
class EntryActivity : LockingActivity() {
|
||||||
|
|
||||||
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
|
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
|
||||||
private var titleIconView: ImageView? = null
|
private var titleIconView: ImageView? = null
|
||||||
|
private var historyView: View? = null
|
||||||
private var entryContentsView: EntryContentsView? = null
|
private var entryContentsView: EntryContentsView? = null
|
||||||
|
private var entryProgress: ProgressBar? = null
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
|
|
||||||
private var mEntry: EntryVersioned? = null
|
private var mDatabase: Database? = null
|
||||||
|
|
||||||
|
private var mEntry: Entry? = null
|
||||||
|
|
||||||
|
private var mIsHistory: Boolean = false
|
||||||
|
private var mEntryLastVersion: Entry? = null
|
||||||
|
private var mEntryHistoryPosition: Int = -1
|
||||||
|
|
||||||
private var mShowPassword: Boolean = false
|
private var mShowPassword: Boolean = false
|
||||||
|
|
||||||
|
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
|
||||||
|
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
|
||||||
|
|
||||||
private var clipboardHelper: ClipboardHelper? = null
|
private var clipboardHelper: ClipboardHelper? = null
|
||||||
private var firstLaunchOfActivity: Boolean = false
|
private var firstLaunchOfActivity: Boolean = false
|
||||||
|
|
||||||
@@ -75,28 +100,11 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
val currentDatabase = Database.getInstance()
|
mDatabase = Database.getInstance()
|
||||||
mReadOnly = currentDatabase.isReadOnly || mReadOnly
|
mReadOnly = mDatabase!!.isReadOnly || mReadOnly
|
||||||
|
|
||||||
mShowPassword = !PreferencesUtil.isPasswordMask(this)
|
mShowPassword = !PreferencesUtil.isPasswordMask(this)
|
||||||
|
|
||||||
// Get Entry from UUID
|
|
||||||
try {
|
|
||||||
val keyEntry: PwNodeId<*> = intent.getParcelableExtra(KEY_ENTRY)
|
|
||||||
mEntry = currentDatabase.getEntryById(keyEntry)
|
|
||||||
} catch (e: ClassCastException) {
|
|
||||||
Log.e(TAG, "Unable to retrieve the entry key")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mEntry == null) {
|
|
||||||
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
|
|
||||||
finish()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update last access time.
|
|
||||||
mEntry?.touch(modified = false, touchParents = false)
|
|
||||||
|
|
||||||
// Retrieve the textColor to tint the icon
|
// Retrieve the textColor to tint the icon
|
||||||
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
val taIconColor = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||||
iconColor = taIconColor.getColor(0, Color.BLACK)
|
iconColor = taIconColor.getColor(0, Color.BLACK)
|
||||||
@@ -108,17 +116,59 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
// Get views
|
// Get views
|
||||||
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
|
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
|
||||||
titleIconView = findViewById(R.id.entry_icon)
|
titleIconView = findViewById(R.id.entry_icon)
|
||||||
|
historyView = findViewById(R.id.history_container)
|
||||||
entryContentsView = findViewById(R.id.entry_contents)
|
entryContentsView = findViewById(R.id.entry_contents)
|
||||||
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
||||||
|
entryProgress = findViewById(R.id.entry_progress)
|
||||||
|
|
||||||
// Init the clipboard helper
|
// Init the clipboard helper
|
||||||
clipboardHelper = ClipboardHelper(this)
|
clipboardHelper = ClipboardHelper(this)
|
||||||
firstLaunchOfActivity = true
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO Visual error for entry history
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
// Get Entry from UUID
|
||||||
|
try {
|
||||||
|
val keyEntry: NodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
|
||||||
|
mEntry = mDatabase?.getEntryById(keyEntry)
|
||||||
|
mEntryLastVersion = mEntry
|
||||||
|
} catch (e: ClassCastException) {
|
||||||
|
Log.e(TAG, "Unable to retrieve the entry key")
|
||||||
|
}
|
||||||
|
|
||||||
|
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
|
||||||
|
mEntryHistoryPosition = historyPosition
|
||||||
|
if (historyPosition >= 0) {
|
||||||
|
mIsHistory = true
|
||||||
|
mEntry = mEntry?.getHistory()?.get(historyPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mEntry == null) {
|
||||||
|
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update last access time.
|
||||||
|
mEntry?.touch(modified = false, touchParents = false)
|
||||||
|
|
||||||
mEntry?.let { entry ->
|
mEntry?.let { entry ->
|
||||||
// Fill data in resume to update from EntryEditActivity
|
// Fill data in resume to update from EntryEditActivity
|
||||||
fillEntryDataInContentsView(entry)
|
fillEntryDataInContentsView(entry)
|
||||||
@@ -138,10 +188,25 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mAttachmentFileBinderManager?.apply {
|
||||||
|
registerProgressTask()
|
||||||
|
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
|
||||||
|
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
|
||||||
|
entryContentsView?.updateAttachmentDownloadProgress(attachment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
firstLaunchOfActivity = false
|
firstLaunchOfActivity = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillEntryDataInContentsView(entry: EntryVersioned) {
|
override fun onPause() {
|
||||||
|
mAttachmentFileBinderManager?.unregisterProgressTask()
|
||||||
|
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillEntryDataInContentsView(entry: Entry) {
|
||||||
|
|
||||||
val database = Database.getInstance()
|
val database = Database.getInstance()
|
||||||
database.startManageEntry(entry)
|
database.startManageEntry(entry)
|
||||||
@@ -174,7 +239,7 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
"\n\n" +
|
"\n\n" +
|
||||||
getString(R.string.clipboard_warning))
|
getString(R.string.clipboard_warning))
|
||||||
.create().apply {
|
.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)
|
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
fillEntryDataInContentsView(entry)
|
fillEntryDataInContentsView(entry)
|
||||||
@@ -206,6 +271,17 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//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?.assignURL(entry.url)
|
||||||
entryContentsView?.assignComment(entry.notes)
|
entryContentsView?.assignComment(entry.notes)
|
||||||
|
|
||||||
@@ -237,23 +313,57 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
}
|
}
|
||||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
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
|
// Assign dates
|
||||||
entry.creationTime.date?.let {
|
entryContentsView?.assignCreationDate(entry.creationTime)
|
||||||
entryContentsView?.assignCreationDate(it)
|
entryContentsView?.assignModificationDate(entry.lastModificationTime)
|
||||||
}
|
entryContentsView?.assignLastAccessDate(entry.lastAccessTime)
|
||||||
entry.lastModificationTime.date?.let {
|
entryContentsView?.setExpires(entry.isCurrentlyExpires)
|
||||||
entryContentsView?.assignModificationDate(it)
|
if (entry.expires) {
|
||||||
}
|
entryContentsView?.assignExpiresDate(entry.expiryTime)
|
||||||
entry.lastAccessTime.date?.let {
|
|
||||||
entryContentsView?.assignLastAccessDate(it)
|
|
||||||
}
|
|
||||||
val expires = entry.expiryTime.date
|
|
||||||
if (entry.isExpires && expires != null) {
|
|
||||||
entryContentsView?.assignExpiresDate(expires)
|
|
||||||
} else {
|
} else {
|
||||||
entryContentsView?.assignExpiresDate(getString(R.string.never))
|
entryContentsView?.assignExpiresDate(getString(R.string.never))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manage history
|
||||||
|
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
|
||||||
|
if (mIsHistory) {
|
||||||
|
val taColorAccent = theme.obtainStyledAttributes(intArrayOf(R.attr.colorAccent))
|
||||||
|
collapsingToolbarLayout?.contentScrim = ColorDrawable(taColorAccent.getColor(0, Color.BLACK))
|
||||||
|
taColorAccent.recycle()
|
||||||
|
}
|
||||||
|
val entryHistory = entry.getHistory()
|
||||||
|
// TODO 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entryContentsView?.refreshHistory()
|
||||||
|
|
||||||
// Assign special data
|
// Assign special data
|
||||||
entryContentsView?.assignUUID(entry.nodeId.id)
|
entryContentsView?.assignUUID(entry.nodeId.id)
|
||||||
|
|
||||||
@@ -262,6 +372,7 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
|
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
|
||||||
// Not directly get the entry from intent data but from database
|
// Not directly get the entry from intent data but from database
|
||||||
@@ -269,6 +380,15 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
fillEntryDataInContentsView(it)
|
fillEntryDataInContentsView(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
|
||||||
|
if (createdFileUri != null) {
|
||||||
|
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
|
||||||
|
mAttachmentFileBinderManager
|
||||||
|
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
|
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
|
||||||
@@ -287,9 +407,12 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
val inflater = menuInflater
|
val inflater = menuInflater
|
||||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||||
inflater.inflate(R.menu.entry, menu)
|
inflater.inflate(R.menu.entry, menu)
|
||||||
inflater.inflate(R.menu.database_lock, menu)
|
inflater.inflate(R.menu.database, menu)
|
||||||
|
if (mIsHistory && !mReadOnly) {
|
||||||
if (mReadOnly) {
|
inflater.inflate(R.menu.entry_history, menu)
|
||||||
|
}
|
||||||
|
if (mIsHistory || mReadOnly) {
|
||||||
|
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||||
menu.findItem(R.id.menu_edit)?.isVisible = false
|
menu.findItem(R.id.menu_edit)?.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,21 +481,18 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
MenuUtil.onContributionItemSelected(this)
|
MenuUtil.onContributionItemSelected(this)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_toggle_pass -> {
|
R.id.menu_toggle_pass -> {
|
||||||
mShowPassword = !mShowPassword
|
mShowPassword = !mShowPassword
|
||||||
changeShowPasswordIcon(item)
|
changeShowPasswordIcon(item)
|
||||||
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_edit -> {
|
R.id.menu_edit -> {
|
||||||
mEntry?.let {
|
mEntry?.let {
|
||||||
EntryEditActivity.launch(this@EntryActivity, it)
|
EntryEditActivity.launch(this@EntryActivity, it)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.menu_goto_url -> {
|
R.id.menu_goto_url -> {
|
||||||
var url: String = mEntry?.url ?: ""
|
var url: String = mEntry?.url ?: ""
|
||||||
|
|
||||||
@@ -382,18 +502,33 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UriUtil.gotoUrl(this, url)
|
UriUtil.gotoUrl(this, url)
|
||||||
|
|
||||||
return true
|
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_lock -> {
|
R.id.menu_lock -> {
|
||||||
lockAndExit()
|
lockAndExit()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.menu_save_database -> {
|
||||||
|
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||||
|
}
|
||||||
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
|
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item)
|
return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +539,7 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
TODO Slowdown when add entry as result
|
TODO Slowdown when add entry as result
|
||||||
Intent intent = new Intent();
|
Intent intent = new Intent();
|
||||||
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
|
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
|
||||||
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
|
onFinish(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
|
||||||
*/
|
*/
|
||||||
super.finish()
|
super.finish()
|
||||||
}
|
}
|
||||||
@@ -412,13 +547,16 @@ class EntryActivity : LockingHideActivity() {
|
|||||||
companion object {
|
companion object {
|
||||||
private val TAG = EntryActivity::class.java.name
|
private val TAG = EntryActivity::class.java.name
|
||||||
|
|
||||||
const val KEY_ENTRY = "entry"
|
const val KEY_ENTRY = "KEY_ENTRY"
|
||||||
|
const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION"
|
||||||
|
|
||||||
fun launch(activity: Activity, pw: EntryVersioned, readOnly: Boolean) {
|
fun launch(activity: Activity, entry: Entry, readOnly: Boolean, historyPosition: Int? = null) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
val intent = Intent(activity, EntryActivity::class.java)
|
val intent = Intent(activity, EntryActivity::class.java)
|
||||||
intent.putExtra(KEY_ENTRY, pw.nodeId)
|
intent.putExtra(KEY_ENTRY, entry.nodeId)
|
||||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||||
|
if (historyPosition != null)
|
||||||
|
intent.putExtra(KEY_ENTRY_HISTORY_POSITION, historyPosition)
|
||||||
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
@@ -22,47 +22,54 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
|
|
||||||
import com.kunzisoft.keepass.database.action.node.ActionNodeValues
|
|
||||||
import com.kunzisoft.keepass.database.action.node.AddEntryRunnable
|
|
||||||
import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable
|
|
||||||
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
|
|
||||||
import com.kunzisoft.keepass.database.element.*
|
import com.kunzisoft.keepass.database.element.*
|
||||||
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
import com.kunzisoft.keepass.education.EntryEditActivityEducation
|
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.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.view.EntryEditContentsView
|
import com.kunzisoft.keepass.view.EntryEditContentsView
|
||||||
|
import com.kunzisoft.keepass.view.asError
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class EntryEditActivity : LockingHideActivity(),
|
class EntryEditActivity : LockingActivity(),
|
||||||
IconPickerDialogFragment.IconPickerListener,
|
IconPickerDialogFragment.IconPickerListener,
|
||||||
GeneratePasswordDialogFragment.GeneratePasswordListener {
|
GeneratePasswordDialogFragment.GeneratePasswordListener,
|
||||||
|
SetOTPDialogFragment.CreateOtpListener {
|
||||||
|
|
||||||
private var mDatabase: Database? = null
|
private var mDatabase: Database? = null
|
||||||
|
|
||||||
// Refs of an entry and group in database, are not modifiable
|
// Refs of an entry and group in database, are not modifiable
|
||||||
private var mEntry: EntryVersioned? = null
|
private var mEntry: Entry? = null
|
||||||
private var mParent: GroupVersioned? = null
|
private var mParent: Group? = null
|
||||||
// New or copy of mEntry in the database to be modifiable
|
// New or copy of mEntry in the database to be modifiable
|
||||||
private var mNewEntry: EntryVersioned? = null
|
private var mNewEntry: Entry? = null
|
||||||
private var mIsNew: Boolean = false
|
private var mIsNew: Boolean = false
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
|
private var coordinatorLayout: CoordinatorLayout? = null
|
||||||
private var scrollView: ScrollView? = null
|
private var scrollView: ScrollView? = null
|
||||||
|
|
||||||
private var entryEditContentsView: EntryEditContentsView? = null
|
private var entryEditContentsView: EntryEditContentsView? = null
|
||||||
|
|
||||||
private var saveView: View? = null
|
private var saveView: View? = null
|
||||||
|
|
||||||
// Education
|
// Education
|
||||||
@@ -78,6 +85,8 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
|
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
|
||||||
|
|
||||||
scrollView = findViewById(R.id.entry_edit_scroll)
|
scrollView = findViewById(R.id.entry_edit_scroll)
|
||||||
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
|
||||||
|
|
||||||
@@ -86,11 +95,14 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
// Focus view to reinitialize timeout
|
// Focus view to reinitialize timeout
|
||||||
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
|
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
|
||||||
|
|
||||||
|
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||||
|
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||||
|
|
||||||
// Likely the app has been killed exit the activity
|
// Likely the app has been killed exit the activity
|
||||||
mDatabase = Database.getInstance()
|
mDatabase = Database.getInstance()
|
||||||
|
|
||||||
// Entry is retrieve, it's an entry to update
|
// Entry is retrieve, it's an entry to update
|
||||||
intent.getParcelableExtra<PwNodeId<*>>(KEY_ENTRY)?.let {
|
intent.getParcelableExtra<NodeId<UUID>>(KEY_ENTRY)?.let {
|
||||||
mIsNew = false
|
mIsNew = false
|
||||||
// Create an Entry copy to modify from the database entry
|
// Create an Entry copy to modify from the database entry
|
||||||
mEntry = mDatabase?.getEntryById(it)
|
mEntry = mDatabase?.getEntryById(it)
|
||||||
@@ -105,25 +117,27 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the icon after an orientation change
|
// Create the new entry from the current one
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
if (savedInstanceState == null
|
||||||
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY) as EntryVersioned
|
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||||
} else {
|
|
||||||
mEntry?.let { entry ->
|
mEntry?.let { entry ->
|
||||||
// Create a copy to modify
|
// Create a copy to modify
|
||||||
mNewEntry = EntryVersioned(entry).also { newEntry ->
|
mNewEntry = Entry(entry).also { newEntry ->
|
||||||
|
|
||||||
// WARNING Remove the parent to keep memory with parcelable
|
// WARNING Remove the parent to keep memory with parcelable
|
||||||
newEntry.parent = null
|
newEntry.removeParent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent is retrieve, it's a new entry to create
|
// Parent is retrieve, it's a new entry to create
|
||||||
intent.getParcelableExtra<PwNodeId<*>>(KEY_PARENT)?.let {
|
intent.getParcelableExtra<NodeId<*>>(KEY_PARENT)?.let {
|
||||||
mIsNew = true
|
mIsNew = true
|
||||||
mNewEntry = mDatabase?.createEntry()
|
// Create an empty new entry
|
||||||
|
if (savedInstanceState == null
|
||||||
|
|| !savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||||
|
mNewEntry = mDatabase?.createEntry()
|
||||||
|
}
|
||||||
mParent = mDatabase?.getGroupById(it)
|
mParent = mDatabase?.getGroupById(it)
|
||||||
// Add the default icon
|
// Add the default icon
|
||||||
mDatabase?.drawFactory?.let { iconFactory ->
|
mDatabase?.drawFactory?.let { iconFactory ->
|
||||||
@@ -131,6 +145,12 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the new entry after an orientation change
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(KEY_NEW_ENTRY)) {
|
||||||
|
mNewEntry = savedInstanceState.getParcelable(KEY_NEW_ENTRY)
|
||||||
|
}
|
||||||
|
|
||||||
// Close the activity if entry or parent can't be retrieve
|
// Close the activity if entry or parent can't be retrieve
|
||||||
if (mNewEntry == null || mParent == null) {
|
if (mNewEntry == null || mParent == null) {
|
||||||
finish()
|
finish()
|
||||||
@@ -152,13 +172,35 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
saveView = findViewById(R.id.entry_edit_save)
|
saveView = findViewById(R.id.entry_edit_save)
|
||||||
saveView?.setOnClickListener { saveEntry() }
|
saveView?.setOnClickListener { saveEntry() }
|
||||||
|
|
||||||
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) { addNewCustomField() }
|
entryEditContentsView?.allowCustomField(mNewEntry?.allowCustomFields() == true) {
|
||||||
|
addNewCustomField()
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the education views
|
// Verify the education views
|
||||||
entryEditActivityEducation = EntryEditActivityEducation(this)
|
entryEditActivityEducation = EntryEditActivityEducation(this)
|
||||||
|
|
||||||
|
// Create progress dialog
|
||||||
|
mProgressDialogThread?.onActionFinish = { actionTask, result ->
|
||||||
|
when (actionTask) {
|
||||||
|
ACTION_DATABASE_CREATE_ENTRY_TASK,
|
||||||
|
ACTION_DATABASE_UPDATE_ENTRY_TASK -> {
|
||||||
|
if (result.isSuccess)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show error
|
||||||
|
if (!result.isSuccess) {
|
||||||
|
result.message?.let { resultMessage ->
|
||||||
|
Snackbar.make(coordinatorLayout!!,
|
||||||
|
resultMessage,
|
||||||
|
Snackbar.LENGTH_LONG).asError().show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateViewsWithEntry(newEntry: EntryVersioned) {
|
private fun populateViewsWithEntry(newEntry: Entry) {
|
||||||
// Don't start the field reference manager, we want to see the raw ref
|
// Don't start the field reference manager, we want to see the raw ref
|
||||||
mDatabase?.stopManageEntry(newEntry)
|
mDatabase?.stopManageEntry(newEntry)
|
||||||
|
|
||||||
@@ -168,30 +210,33 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
// Set info in view
|
// Set info in view
|
||||||
entryEditContentsView?.apply {
|
entryEditContentsView?.apply {
|
||||||
title = newEntry.title
|
title = newEntry.title
|
||||||
username = newEntry.username
|
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
|
||||||
url = newEntry.url
|
url = newEntry.url
|
||||||
password = newEntry.password
|
password = newEntry.password
|
||||||
notes = newEntry.notes
|
notes = newEntry.notes
|
||||||
for (entry in newEntry.customFields.entries) {
|
for (entry in newEntry.customFields.entries) {
|
||||||
addNewCustomField(entry.key, entry.value)
|
post {
|
||||||
|
putCustomField(entry.key, entry.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateEntryWithViews(newEntry: EntryVersioned) {
|
private fun populateEntryWithViews(newEntry: Entry) {
|
||||||
|
|
||||||
mDatabase?.startManageEntry(newEntry)
|
mDatabase?.startManageEntry(newEntry)
|
||||||
|
|
||||||
newEntry.apply {
|
newEntry.apply {
|
||||||
// Build info from view
|
// Build info from view
|
||||||
entryEditContentsView?.let { entryView ->
|
entryEditContentsView?.let { entryView ->
|
||||||
|
removeAllFields()
|
||||||
title = entryView.title
|
title = entryView.title
|
||||||
username = entryView.username
|
username = entryView.username
|
||||||
url = entryView.url
|
url = entryView.url
|
||||||
password = entryView.password
|
password = entryView.password
|
||||||
notes = entryView.notes
|
notes = entryView.notes
|
||||||
entryView.customFields.forEach { customField ->
|
entryView.customFields.forEach { customField ->
|
||||||
addExtraField(customField.name, customField.protectedValue)
|
putExtraField(customField.name, customField.protectedValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +244,7 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
mDatabase?.stopManageEntry(newEntry)
|
mDatabase?.stopManageEntry(newEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) {
|
private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) {
|
||||||
mNewEntry?.icon = icon
|
mNewEntry?.icon = icon
|
||||||
mDatabase?.drawFactory?.let { iconDrawFactory ->
|
mDatabase?.drawFactory?.let { iconDrawFactory ->
|
||||||
entryEditContentsView?.setIcon(iconDrawFactory, icon)
|
entryEditContentsView?.setIcon(iconDrawFactory, icon)
|
||||||
@@ -217,9 +262,7 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
* Add a new customized field view and scroll to bottom
|
* Add a new customized field view and scroll to bottom
|
||||||
*/
|
*/
|
||||||
private fun addNewCustomField() {
|
private fun addNewCustomField() {
|
||||||
entryEditContentsView?.addNewCustomField()
|
entryEditContentsView?.addEmptyCustomField()
|
||||||
// Scroll bottom
|
|
||||||
scrollView?.post { scrollView?.fullScroll(ScrollView.FOCUS_DOWN) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -230,47 +273,32 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
// Launch a validation and show the error if present
|
// Launch a validation and show the error if present
|
||||||
if (entryEditContentsView?.isValid() == true) {
|
if (entryEditContentsView?.isValid() == true) {
|
||||||
// Clone the entry
|
// Clone the entry
|
||||||
mDatabase?.let { database ->
|
mNewEntry?.let { newEntry ->
|
||||||
mNewEntry?.let { newEntry ->
|
|
||||||
|
|
||||||
// WARNING Add the parent previously deleted
|
// WARNING Add the parent previously deleted
|
||||||
newEntry.parent = mEntry?.parent
|
newEntry.parent = mEntry?.parent
|
||||||
// Build info
|
// Build info
|
||||||
newEntry.lastAccessTime = PwDate()
|
newEntry.lastAccessTime = DateInstant()
|
||||||
newEntry.lastModificationTime = PwDate()
|
newEntry.lastModificationTime = DateInstant()
|
||||||
|
|
||||||
populateEntryWithViews(newEntry)
|
populateEntryWithViews(newEntry)
|
||||||
|
|
||||||
// Open a progress dialog and save entry
|
// Open a progress dialog and save entry
|
||||||
var actionRunnable: ActionRunnable? = null
|
if (mIsNew) {
|
||||||
val afterActionNodeFinishRunnable = object : AfterActionNodeFinishRunnable() {
|
mParent?.let { parent ->
|
||||||
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
|
mProgressDialogThread?.startDatabaseCreateEntry(
|
||||||
if (actionNodeValues.result.isSuccess)
|
newEntry,
|
||||||
finish()
|
parent,
|
||||||
}
|
!mReadOnly && mAutoSaveEnable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (mIsNew) {
|
} else {
|
||||||
mParent?.let { parent ->
|
mEntry?.let { oldEntry ->
|
||||||
actionRunnable = AddEntryRunnable(this@EntryEditActivity,
|
mProgressDialogThread?.startDatabaseUpdateEntry(
|
||||||
database,
|
oldEntry,
|
||||||
newEntry,
|
newEntry,
|
||||||
parent,
|
!mReadOnly && mAutoSaveEnable
|
||||||
afterActionNodeFinishRunnable,
|
)
|
||||||
!mReadOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
mEntry?.let { oldEntry ->
|
|
||||||
actionRunnable = UpdateEntryRunnable(this@EntryEditActivity,
|
|
||||||
database,
|
|
||||||
oldEntry,
|
|
||||||
newEntry,
|
|
||||||
afterActionNodeFinishRunnable,
|
|
||||||
!mReadOnly)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
actionRunnable?.let { runnable ->
|
|
||||||
ProgressDialogSaveDatabaseThread(this@EntryEditActivity) { runnable }.start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,8 +309,12 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
|
|
||||||
val inflater = menuInflater
|
val inflater = menuInflater
|
||||||
inflater.inflate(R.menu.database_lock, menu)
|
inflater.inflate(R.menu.database, menu)
|
||||||
|
// Save database not needed here
|
||||||
|
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||||
MenuUtil.contributionMenuInflater(inflater, menu)
|
MenuUtil.contributionMenuInflater(inflater, menu)
|
||||||
|
if (mDatabase?.allowOTP == true)
|
||||||
|
inflater.inflate(R.menu.entry_otp, menu)
|
||||||
|
|
||||||
entryEditActivityEducation?.let {
|
entryEditActivityEducation?.let {
|
||||||
Handler().post { performedNextEducation(it) }
|
Handler().post { performedNextEducation(it) }
|
||||||
@@ -293,7 +325,7 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
|
|
||||||
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) {
|
||||||
val passwordView = entryEditContentsView?.generatePasswordView
|
val passwordView = entryEditContentsView?.generatePasswordView
|
||||||
val addNewFieldView = entryEditContentsView?.addNewFieldView
|
val addNewFieldView = entryEditContentsView?.addNewFieldButton
|
||||||
|
|
||||||
val generatePasswordEducationPerformed = passwordView != null
|
val generatePasswordEducationPerformed = passwordView != null
|
||||||
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
&& entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation(
|
||||||
@@ -323,18 +355,34 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
lockAndExit()
|
lockAndExit()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.menu_save_database -> {
|
||||||
|
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||||
|
}
|
||||||
R.id.menu_contribute -> {
|
R.id.menu_contribute -> {
|
||||||
MenuUtil.onContributionItemSelected(this)
|
MenuUtil.onContributionItemSelected(this)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
R.id.menu_add_otp -> {
|
||||||
|
// Retrieve the current otpElement if exists
|
||||||
|
// and open the dialog to set up the OTP
|
||||||
|
SetOTPDialogFragment.build(mEntry?.getOtpElement()?.otpModel)
|
||||||
|
.show(supportFragmentManager, "addOTPDialog")
|
||||||
|
return true
|
||||||
|
}
|
||||||
android.R.id.home -> finish()
|
android.R.id.home -> finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item)
|
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) {
|
override fun iconPicked(bundle: Bundle) {
|
||||||
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
IconPickerDialogFragment.getIconStandardFromBundle(bundle)?.let { icon ->
|
||||||
temporarilySaveAndShowSelectedIcon(icon)
|
temporarilySaveAndShowSelectedIcon(icon)
|
||||||
@@ -342,7 +390,10 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
outState.putParcelable(KEY_NEW_ENTRY, mNewEntry)
|
mNewEntry?.let {
|
||||||
|
populateEntryWithViews(it)
|
||||||
|
outState.putParcelable(KEY_NEW_ENTRY, it)
|
||||||
|
}
|
||||||
|
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
@@ -405,7 +456,7 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
* @param activity from activity
|
* @param activity from activity
|
||||||
* @param pwEntry Entry to update
|
* @param pwEntry Entry to update
|
||||||
*/
|
*/
|
||||||
fun launch(activity: Activity, pwEntry: EntryVersioned) {
|
fun launch(activity: Activity, pwEntry: Entry) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||||
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
|
intent.putExtra(KEY_ENTRY, pwEntry.nodeId)
|
||||||
@@ -419,7 +470,7 @@ class EntryEditActivity : LockingHideActivity(),
|
|||||||
* @param activity from activity
|
* @param activity from activity
|
||||||
* @param pwGroup Group who will contains new entry
|
* @param pwGroup Group who will contains new entry
|
||||||
*/
|
*/
|
||||||
fun launch(activity: Activity, pwGroup: GroupVersioned) {
|
fun launch(activity: Activity, pwGroup: Group) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
val intent = Intent(activity, EntryEditActivity::class.java)
|
val intent = Intent(activity, EntryEditActivity::class.java)
|
||||||
intent.putExtra(KEY_PARENT, pwGroup.nodeId)
|
intent.putExtra(KEY_PARENT, pwGroup.nodeId)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
@@ -29,38 +29,32 @@ import android.os.Bundle
|
|||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
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.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
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.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable
|
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.utils.*
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
|
||||||
import com.kunzisoft.keepass.view.asError
|
import com.kunzisoft.keepass.view.asError
|
||||||
import kotlinx.android.synthetic.main.activity_file_selection.*
|
import kotlinx.android.synthetic.main.activity_file_selection.*
|
||||||
import net.cachapa.expandablelayout.ExpandableLayout
|
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
|
||||||
class FileDatabaseSelectActivity : StylishActivity(),
|
class FileDatabaseSelectActivity : StylishActivity(),
|
||||||
@@ -69,11 +63,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
// Views
|
// Views
|
||||||
private var fileListContainer: View? = null
|
private var fileListContainer: View? = null
|
||||||
private var createButtonView: View? = null
|
private var createButtonView: View? = null
|
||||||
private var browseButtonView: View? = null
|
private var openDatabaseButtonView: View? = null
|
||||||
private var openButtonView: View? = null
|
|
||||||
private var fileSelectExpandableButtonView: View? = null
|
|
||||||
private var fileSelectExpandableLayout: ExpandableLayout? = null
|
|
||||||
private var openFileNameView: EditText? = null
|
|
||||||
|
|
||||||
// Adapter to manage database history list
|
// Adapter to manage database history list
|
||||||
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
|
private var mAdapterDatabaseHistory: FileDatabaseHistoryAdapter? = null
|
||||||
@@ -84,7 +74,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
|
|
||||||
private var mOpenFileHelper: OpenFileHelper? = null
|
private var mOpenFileHelper: OpenFileHelper? = null
|
||||||
|
|
||||||
private var mDefaultPath: String? = null
|
private var mProgressDialogThread: ProgressDialogThread? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -98,63 +88,22 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
toolbar.title = ""
|
toolbar.title = ""
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
openFileNameView = findViewById(R.id.file_filename)
|
|
||||||
|
|
||||||
// Set the initial value of the filename
|
|
||||||
mDefaultPath = (Environment.getExternalStorageDirectory().absolutePath
|
|
||||||
+ getString(R.string.database_file_path_default)
|
|
||||||
+ getString(R.string.database_file_name_default)
|
|
||||||
+ getString(R.string.database_file_extension_default))
|
|
||||||
openFileNameView?.setHint(R.string.open_link_database)
|
|
||||||
|
|
||||||
// Button to expand file selection
|
|
||||||
fileSelectExpandableButtonView = findViewById(R.id.file_select_expandable_button)
|
|
||||||
fileSelectExpandableLayout = findViewById(R.id.file_select_expandable)
|
|
||||||
fileSelectExpandableButtonView?.setOnClickListener { _ ->
|
|
||||||
if (fileSelectExpandableLayout?.isExpanded == true)
|
|
||||||
fileSelectExpandableLayout?.collapse()
|
|
||||||
else
|
|
||||||
fileSelectExpandableLayout?.expand()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open button
|
|
||||||
openButtonView = findViewById(R.id.open_database)
|
|
||||||
openButtonView?.setOnClickListener { _ ->
|
|
||||||
var fileName = openFileNameView?.text?.toString() ?: ""
|
|
||||||
mDefaultPath?.let {
|
|
||||||
if (fileName.isEmpty())
|
|
||||||
fileName = it
|
|
||||||
}
|
|
||||||
UriUtil.parse(fileName)?.let { fileNameUri ->
|
|
||||||
launchPasswordActivityWithPath(fileNameUri)
|
|
||||||
} ?: run {
|
|
||||||
Log.e(TAG, "Unable to open the database link")
|
|
||||||
Snackbar.make(activity_file_selection_coordinator_layout, getString(R.string.error_can_not_handle_uri), Snackbar.LENGTH_LONG).asError().show()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create button
|
// Create button
|
||||||
createButtonView = findViewById(R.id.create_database)
|
createButtonView = findViewById(R.id.create_database_button)
|
||||||
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
|
||||||
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.
|
// There is an activity which can handle this intent.
|
||||||
createButtonView?.visibility = View.VISIBLE
|
createButtonView?.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
// No Activity found that can handle this intent.
|
||||||
|
createButtonView?.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
createButtonView?.setOnClickListener { createNewFile() }
|
createButtonView?.setOnClickListener { createNewFile() }
|
||||||
|
|
||||||
mOpenFileHelper = OpenFileHelper(this)
|
mOpenFileHelper = OpenFileHelper(this)
|
||||||
browseButtonView = findViewById(R.id.browse_button)
|
openDatabaseButtonView = findViewById(R.id.open_database_button)
|
||||||
browseButtonView?.setOnClickListener(mOpenFileHelper!!.getOpenFileOnClickViewListener {
|
openDatabaseButtonView?.setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||||
UriUtil.parse(openFileNameView?.text?.toString())
|
|
||||||
})
|
|
||||||
|
|
||||||
// History list
|
// History list
|
||||||
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
|
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
|
||||||
@@ -207,6 +156,20 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
&& savedInstanceState.containsKey(EXTRA_DATABASE_URI)) {
|
&& savedInstanceState.containsKey(EXTRA_DATABASE_URI)) {
|
||||||
mDatabaseFileUri = savedInstanceState.getParcelable(EXTRA_DATABASE_URI)
|
mDatabaseFileUri = savedInstanceState.getParcelable(EXTRA_DATABASE_URI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attach the dialog thread to this activity
|
||||||
|
mProgressDialogThread = ProgressDialogThread(this).apply {
|
||||||
|
onActionFinish = { actionTask, _ ->
|
||||||
|
when (actionTask) {
|
||||||
|
ACTION_DATABASE_CREATE_TASK -> {
|
||||||
|
// TODO Check
|
||||||
|
// mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
|
// updateFileListVisibility()
|
||||||
|
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,18 +177,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
*/
|
*/
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private fun createNewFile() {
|
private fun createNewFile() {
|
||||||
try {
|
createDocument(this, getString(R.string.database_file_name_default) +
|
||||||
startActivityForResult(Intent(
|
getString(R.string.database_file_extension_default), "application/x-keepass")
|
||||||
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) {
|
private fun fileNoFoundAction(e: FileNotFoundException) {
|
||||||
@@ -267,6 +220,23 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun launchGroupActivity(readOnly: Boolean) {
|
||||||
|
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||||
|
{
|
||||||
|
GroupActivity.launch(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, readOnly)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private fun launchPasswordActivityWithPath(databaseUri: Uri) {
|
private fun launchPasswordActivityWithPath(databaseUri: Uri) {
|
||||||
launchPasswordActivity(databaseUri, null)
|
launchPasswordActivity(databaseUri, null)
|
||||||
// Delete flickering for kitkat <=
|
// Delete flickering for kitkat <=
|
||||||
@@ -294,6 +264,11 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
val database = Database.getInstance()
|
||||||
|
if (database.loaded) {
|
||||||
|
launchGroupActivity(database.isReadOnly)
|
||||||
|
}
|
||||||
|
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
updateExternalStorageWarning()
|
updateExternalStorageWarning()
|
||||||
@@ -306,6 +281,16 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register progress task
|
||||||
|
mProgressDialogThread?.registerProgressTask()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
// Unregister progress task
|
||||||
|
mProgressDialogThread?.unregisterProgressTask()
|
||||||
|
|
||||||
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@@ -331,21 +316,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
mDatabaseFileUri?.let { databaseUri ->
|
mDatabaseFileUri?.let { databaseUri ->
|
||||||
|
|
||||||
// Create the new database
|
// Create the new database
|
||||||
ProgressDialogThread(this@FileDatabaseSelectActivity,
|
mProgressDialogThread?.startDatabaseCreate(
|
||||||
{
|
databaseUri,
|
||||||
CreateDatabaseRunnable(this@FileDatabaseSelectActivity,
|
masterPasswordChecked,
|
||||||
databaseUri,
|
masterPassword,
|
||||||
Database.getInstance(),
|
keyFileChecked,
|
||||||
masterPasswordChecked,
|
keyFile
|
||||||
masterPassword,
|
)
|
||||||
keyFileChecked,
|
|
||||||
keyFile,
|
|
||||||
true, // TODO get readonly
|
|
||||||
LaunchGroupActivityFinish(databaseUri, keyFile)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
R.string.progress_create)
|
|
||||||
.start()
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val error = getString(R.string.error_create_database_file)
|
val error = getString(R.string.error_create_database_file)
|
||||||
@@ -354,28 +331,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class LaunchGroupActivityFinish(private val databaseFileUri: Uri,
|
|
||||||
private val keyFileUri: Uri?) : ActionRunnable() {
|
|
||||||
|
|
||||||
override fun run() {
|
|
||||||
finishRun(true, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinishRun(result: Result) {
|
|
||||||
runOnUiThread {
|
|
||||||
if (result.isSuccess) {
|
|
||||||
// Add database to recent files
|
|
||||||
mFileDatabaseHistoryAction?.addOrUpdateDatabaseUri(databaseFileUri, keyFileUri)
|
|
||||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
|
||||||
updateFileListVisibility()
|
|
||||||
GroupActivity.launch(this@FileDatabaseSelectActivity)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "Unable to open the database")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAssignKeyDialogNegativeClick(
|
override fun onAssignKeyDialogNegativeClick(
|
||||||
masterPasswordChecked: Boolean, masterPassword: String?,
|
masterPasswordChecked: Boolean, masterPassword: String?,
|
||||||
keyFileChecked: Boolean, keyFile: Uri?) {
|
keyFileChecked: Boolean, keyFile: Uri?) {
|
||||||
@@ -392,23 +347,19 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
mOpenFileHelper?.onActivityResultCallback(requestCode, resultCode, data
|
||||||
) { uri ->
|
) { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
if (PreferencesUtil.autoOpenSelectedFile(this@FileDatabaseSelectActivity)) {
|
launchPasswordActivityWithPath(uri)
|
||||||
launchPasswordActivityWithPath(uri)
|
|
||||||
} else {
|
|
||||||
fileSelectExpandableLayout?.expand(false)
|
|
||||||
openFileNameView?.setText(uri.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the created URI from the file manager
|
// Retrieve the created URI from the file manager
|
||||||
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
|
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
|
||||||
mDatabaseFileUri = data?.data
|
mDatabaseFileUri = databaseFileCreatedUri
|
||||||
if (mDatabaseFileUri != null) {
|
if (mDatabaseFileUri != null) {
|
||||||
AssignMasterKeyDialogFragment().show(supportFragmentManager, "passwordDialog")
|
AssignMasterKeyDialogFragment.getInstance(true)
|
||||||
|
.show(supportFragmentManager, "passwordDialog")
|
||||||
}
|
}
|
||||||
// else {
|
// else {
|
||||||
// TODO Show error
|
// TODO Show error
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,20 +389,15 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
})
|
})
|
||||||
if (!createDatabaseEducationPerformed) {
|
if (!createDatabaseEducationPerformed) {
|
||||||
// selectDatabaseEducationPerformed
|
// selectDatabaseEducationPerformed
|
||||||
browseButtonView != null
|
openDatabaseButtonView != null
|
||||||
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
&& fileDatabaseSelectActivityEducation.checkAndPerformedSelectDatabaseEducation(
|
||||||
browseButtonView!!,
|
openDatabaseButtonView!!,
|
||||||
{tapTargetView ->
|
{tapTargetView ->
|
||||||
tapTargetView?.let {
|
tapTargetView?.let {
|
||||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
|
mOpenFileHelper?.openFileOnClickViewListener?.onClick(it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{}
|
||||||
fileSelectExpandableButtonView?.let {
|
|
||||||
fileDatabaseSelectActivityEducation
|
|
||||||
.checkAndPerformedOpenLinkDatabaseEducation(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,8 +412,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
private const val EXTRA_STAY = "EXTRA_STAY"
|
private const val EXTRA_STAY = "EXTRA_STAY"
|
||||||
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
|
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
|
||||||
|
|
||||||
private const val CREATE_FILE_REQUEST_CODE = 3853
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------
|
* -------------------------
|
||||||
* No Standard Launch, pass by PasswordActivity
|
* No Standard Launch, pass by PasswordActivity
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
@@ -29,55 +28,71 @@ import android.graphics.Color
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.fragment.app.FragmentManager
|
|
||||||
import androidx.appcompat.widget.SearchView
|
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
|
import androidx.appcompat.widget.SearchView
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
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.R
|
||||||
|
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
|
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.dialogs.SortDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.adapters.NodeAdapter
|
|
||||||
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
|
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogSaveDatabaseThread
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.action.node.*
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.action.node.ActionNodeDatabaseRunnable.Companion.NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY
|
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||||
import com.kunzisoft.keepass.database.element.*
|
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.education.GroupActivityEducation
|
import com.kunzisoft.keepass.education.GroupActivityEducation
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||||
|
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
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_NODES_KEY
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.OLD_NODES_KEY
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.view.AddNodeButtonView
|
import com.kunzisoft.keepass.view.AddNodeButtonView
|
||||||
import net.cachapa.expandablelayout.ExpandableLayout
|
import com.kunzisoft.keepass.view.ToolbarAction
|
||||||
|
import com.kunzisoft.keepass.view.asError
|
||||||
|
|
||||||
class GroupActivity : LockingActivity(),
|
class GroupActivity : LockingActivity(),
|
||||||
GroupEditDialogFragment.EditGroupListener,
|
GroupEditDialogFragment.EditGroupListener,
|
||||||
IconPickerDialogFragment.IconPickerListener,
|
IconPickerDialogFragment.IconPickerListener,
|
||||||
NodeAdapter.NodeMenuListener,
|
ListNodesFragment.NodeClickListener,
|
||||||
|
ListNodesFragment.NodesActionMenuListener,
|
||||||
|
DeleteNodesDialogFragment.DeleteNodeListener,
|
||||||
ListNodesFragment.OnScrollListener,
|
ListNodesFragment.OnScrollListener,
|
||||||
NodeAdapter.NodeClickCallback,
|
|
||||||
SortDialogFragment.SortSelectionListener {
|
SortDialogFragment.SortSelectionListener {
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
|
private var coordinatorLayout: CoordinatorLayout? = null
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
private var searchTitleView: View? = null
|
private var searchTitleView: View? = null
|
||||||
private var toolbarPasteExpandableLayout: ExpandableLayout? = null
|
private var toolbarAction: ToolbarAction? = null
|
||||||
private var toolbarPaste: Toolbar? = null
|
|
||||||
private var iconView: ImageView? = null
|
private var iconView: ImageView? = null
|
||||||
|
private var numberChildrenView: TextView? = null
|
||||||
private var modeTitleView: TextView? = null
|
private var modeTitleView: TextView? = null
|
||||||
private var addNodeButtonView: AddNodeButtonView? = null
|
private var addNodeButtonView: AddNodeButtonView? = null
|
||||||
private var groupNameView: TextView? = null
|
private var groupNameView: TextView? = null
|
||||||
@@ -88,11 +103,9 @@ class GroupActivity : LockingActivity(),
|
|||||||
private var mCurrentGroupIsASearch: Boolean = false
|
private var mCurrentGroupIsASearch: Boolean = false
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
private var mRootGroup: GroupVersioned? = null
|
private var mRootGroup: Group? = null
|
||||||
private var mCurrentGroup: GroupVersioned? = null
|
private var mCurrentGroup: Group? = null
|
||||||
private var mOldGroupToUpdate: GroupVersioned? = null
|
private var mOldGroupToUpdate: Group? = null
|
||||||
private var mNodeToCopy: NodeVersioned? = null
|
|
||||||
private var mNodeToMove: NodeVersioned? = null
|
|
||||||
|
|
||||||
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
|
private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null
|
||||||
|
|
||||||
@@ -110,15 +123,19 @@ class GroupActivity : LockingActivity(),
|
|||||||
setContentView(layoutInflater.inflate(R.layout.activity_group, null))
|
setContentView(layoutInflater.inflate(R.layout.activity_group, null))
|
||||||
|
|
||||||
// Initialize views
|
// Initialize views
|
||||||
iconView = findViewById(R.id.icon)
|
coordinatorLayout = findViewById(R.id.group_coordinator)
|
||||||
|
iconView = findViewById(R.id.group_icon)
|
||||||
|
numberChildrenView = findViewById(R.id.group_numbers)
|
||||||
addNodeButtonView = findViewById(R.id.add_node_button)
|
addNodeButtonView = findViewById(R.id.add_node_button)
|
||||||
toolbar = findViewById(R.id.toolbar)
|
toolbar = findViewById(R.id.toolbar)
|
||||||
searchTitleView = findViewById(R.id.search_title)
|
searchTitleView = findViewById(R.id.search_title)
|
||||||
groupNameView = findViewById(R.id.group_name)
|
groupNameView = findViewById(R.id.group_name)
|
||||||
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout)
|
toolbarAction = findViewById(R.id.toolbar_action)
|
||||||
toolbarPaste = findViewById(R.id.toolbar_paste)
|
|
||||||
modeTitleView = findViewById(R.id.mode_title_view)
|
modeTitleView = findViewById(R.id.mode_title_view)
|
||||||
|
|
||||||
|
toolbar?.title = ""
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
// Focus view to reinitialize timeout
|
// Focus view to reinitialize timeout
|
||||||
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
|
resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView)
|
||||||
|
|
||||||
@@ -126,13 +143,6 @@ class GroupActivity : LockingActivity(),
|
|||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
|
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
|
||||||
mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY)
|
mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY)
|
||||||
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) {
|
|
||||||
mNodeToCopy = savedInstanceState.getParcelable(NODE_TO_COPY_KEY)
|
|
||||||
toolbarPaste?.setOnMenuItemClickListener(OnCopyMenuItemClickListener())
|
|
||||||
} else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
|
|
||||||
mNodeToMove = savedInstanceState.getParcelable(NODE_TO_MOVE_KEY)
|
|
||||||
toolbarPaste?.setOnMenuItemClickListener(OnMoveMenuItemClickListener())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -153,17 +163,6 @@ class GroupActivity : LockingActivity(),
|
|||||||
// Update last access time.
|
// Update last access time.
|
||||||
mCurrentGroup?.touch(modified = false, touchParents = false)
|
mCurrentGroup?.touch(modified = false, touchParents = false)
|
||||||
|
|
||||||
toolbar?.title = ""
|
|
||||||
setSupportActionBar(toolbar)
|
|
||||||
|
|
||||||
toolbarPaste?.inflateMenu(R.menu.node_paste_menu)
|
|
||||||
toolbarPaste?.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp)
|
|
||||||
toolbarPaste?.setNavigationOnClickListener {
|
|
||||||
toolbarPasteExpandableLayout?.collapse()
|
|
||||||
mNodeToCopy = null
|
|
||||||
mNodeToMove = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the textColor to tint the icon
|
// Retrieve the textColor to tint the icon
|
||||||
val taTextColor = theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
|
val taTextColor = theme.obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
|
||||||
mIconColor = taTextColor.getColor(0, Color.WHITE)
|
mIconColor = taTextColor.getColor(0, Color.WHITE)
|
||||||
@@ -197,9 +196,75 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Search suggestion
|
|
||||||
mDatabase?.let { database ->
|
mDatabase?.let { database ->
|
||||||
|
// Search suggestion
|
||||||
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database)
|
mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database)
|
||||||
|
|
||||||
|
// Init dialog thread
|
||||||
|
mProgressDialogThread?.onActionFinish = { actionTask, result ->
|
||||||
|
|
||||||
|
var oldNodes: List<Node> = ArrayList()
|
||||||
|
result.data?.getBundle(OLD_NODES_KEY)?.let { oldNodesBundle ->
|
||||||
|
oldNodes = getListNodesFromBundle(database, oldNodesBundle)
|
||||||
|
}
|
||||||
|
var newNodes: List<Node> = ArrayList()
|
||||||
|
result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle ->
|
||||||
|
newNodes = getListNodesFromBundle(database, newNodesBundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
when (actionTask) {
|
||||||
|
ACTION_DATABASE_UPDATE_GROUP_TASK -> {
|
||||||
|
if (result.isSuccess) {
|
||||||
|
mListNodesFragment?.updateNodes(oldNodes, newNodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ACTION_DATABASE_CREATE_GROUP_TASK,
|
||||||
|
ACTION_DATABASE_COPY_NODES_TASK,
|
||||||
|
ACTION_DATABASE_MOVE_NODES_TASK -> {
|
||||||
|
if (result.isSuccess) {
|
||||||
|
mListNodesFragment?.addNodes(newNodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ACTION_DATABASE_DELETE_NODES_TASK -> {
|
||||||
|
if (result.isSuccess) {
|
||||||
|
|
||||||
|
// Rebuild all the list to avoid bug when delete node from sort
|
||||||
|
mListNodesFragment?.rebuildList()
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
mListNodesFragment?.updateNode(recycleBin)
|
||||||
|
}
|
||||||
|
// Recycle bin not here, verify if parents are similar to add it
|
||||||
|
else if (currentGroup == recycleBin.parent) {
|
||||||
|
mListNodesFragment?.addNode(recycleBin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.isSuccess) {
|
||||||
|
coordinatorLayout?.let { coordinatorLayout ->
|
||||||
|
result.exception?.errorId?.let { errorId ->
|
||||||
|
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
|
||||||
|
} ?: result.message?.let { message ->
|
||||||
|
Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).asError().show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finishNodeAction()
|
||||||
|
|
||||||
|
refreshNumberOfChildren()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(TAG, "Finished creating tree")
|
Log.i(TAG, "Finished creating tree")
|
||||||
@@ -221,7 +286,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openSearchGroup(group: GroupVersioned?) {
|
private fun openSearchGroup(group: Group?) {
|
||||||
// Delete the previous search fragment
|
// Delete the previous search fragment
|
||||||
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
|
val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG)
|
||||||
if (searchFragment != null) {
|
if (searchFragment != null) {
|
||||||
@@ -233,11 +298,11 @@ class GroupActivity : LockingActivity(),
|
|||||||
openGroup(group, true)
|
openGroup(group, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openChildGroup(group: GroupVersioned) {
|
private fun openChildGroup(group: Group) {
|
||||||
openGroup(group, false)
|
openGroup(group, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun openGroup(group: GroupVersioned?, isASearch: Boolean) {
|
private fun openGroup(group: Group?, isASearch: Boolean) {
|
||||||
// Check TimeoutHelper
|
// Check TimeoutHelper
|
||||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
||||||
// Open a group in a new fragment
|
// Open a group in a new fragment
|
||||||
@@ -274,16 +339,10 @@ class GroupActivity : LockingActivity(),
|
|||||||
mOldGroupToUpdate?.let {
|
mOldGroupToUpdate?.let {
|
||||||
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, it)
|
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, it)
|
||||||
}
|
}
|
||||||
mNodeToCopy?.let {
|
|
||||||
outState.putParcelable(NODE_TO_COPY_KEY, it)
|
|
||||||
}
|
|
||||||
mNodeToMove?.let {
|
|
||||||
outState.putParcelable(NODE_TO_MOVE_KEY, it)
|
|
||||||
}
|
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): GroupVersioned? {
|
private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): Group? {
|
||||||
|
|
||||||
// Force read only if the database is like that
|
// Force read only if the database is like that
|
||||||
mReadOnly = mDatabase?.isReadOnly == true || mReadOnly
|
mReadOnly = mDatabase?.isReadOnly == true || mReadOnly
|
||||||
@@ -294,7 +353,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
// else a real group
|
// else a real group
|
||||||
else {
|
else {
|
||||||
var pwGroupId: PwNodeId<*>? = null
|
var pwGroupId: NodeId<*>? = null
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) {
|
if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) {
|
||||||
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY)
|
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY)
|
||||||
} else {
|
} else {
|
||||||
@@ -303,7 +362,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "Creating tree view")
|
Log.w(TAG, "Creating tree view")
|
||||||
val currentGroup: GroupVersioned?
|
val currentGroup: Group?
|
||||||
currentGroup = if (pwGroupId == null) {
|
currentGroup = if (pwGroupId == null) {
|
||||||
mRootGroup
|
mRootGroup
|
||||||
} else {
|
} else {
|
||||||
@@ -359,6 +418,9 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign number of children
|
||||||
|
refreshNumberOfChildren()
|
||||||
|
|
||||||
// Show selection mode message if needed
|
// Show selection mode message if needed
|
||||||
if (mSelectionMode) {
|
if (mSelectionMode) {
|
||||||
modeTitleView?.visibility = View.VISIBLE
|
modeTitleView?.visibility = View.VISIBLE
|
||||||
@@ -373,12 +435,8 @@ class GroupActivity : LockingActivity(),
|
|||||||
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
|
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
|
||||||
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
|
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
|
||||||
mCurrentGroup?.let {
|
mCurrentGroup?.let {
|
||||||
val isRoot = it == mRootGroup
|
|
||||||
if (!it.allowAddEntryIfIsRoot())
|
if (!it.allowAddEntryIfIsRoot())
|
||||||
addEntryEnabled = !isRoot && addEntryEnabled
|
addEntryEnabled = it != mRootGroup && addEntryEnabled
|
||||||
if (isRoot) {
|
|
||||||
showWarnings()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
enableAddGroup(addGroupEnabled)
|
enableAddGroup(addGroupEnabled)
|
||||||
enableAddEntry(addEntryEnabled)
|
enableAddEntry(addEntryEnabled)
|
||||||
@@ -388,20 +446,31 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun refreshNumberOfChildren() {
|
||||||
|
numberChildrenView?.apply {
|
||||||
|
if (PreferencesUtil.showNumberEntries(context)) {
|
||||||
|
text = mCurrentGroup?.getChildEntries(*Group.ChildFilter.getDefaults(context))?.size?.toString() ?: ""
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onScrolled(dy: Int) {
|
override fun onScrolled(dy: Int) {
|
||||||
addNodeButtonView?.hideButtonOnScrollListener(dy)
|
addNodeButtonView?.hideButtonOnScrollListener(dy)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNodeClick(node: NodeVersioned) {
|
override fun onNodeClick(node: Node) {
|
||||||
when (node.type) {
|
when (node.type) {
|
||||||
Type.GROUP -> try {
|
Type.GROUP -> try {
|
||||||
openChildGroup(node as GroupVersioned)
|
openChildGroup(node as Group)
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
Log.e(TAG, "Node can't be cast in Group")
|
Log.e(TAG, "Node can't be cast in Group")
|
||||||
}
|
}
|
||||||
|
|
||||||
Type.ENTRY -> try {
|
Type.ENTRY -> try {
|
||||||
val entryVersioned = node as EntryVersioned
|
val entryVersioned = node as Entry
|
||||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||||
{
|
{
|
||||||
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
|
EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly)
|
||||||
@@ -419,8 +488,10 @@ class GroupActivity : LockingActivity(),
|
|||||||
{
|
{
|
||||||
// Build response with the entry selected
|
// Build response with the entry selected
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
|
||||||
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity,
|
mDatabase?.let { database ->
|
||||||
entryVersioned.getEntryInfo(mDatabase!!))
|
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity,
|
||||||
|
entryVersioned.getEntryInfo(database))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finish()
|
finish()
|
||||||
})
|
})
|
||||||
@@ -430,150 +501,134 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOpenMenuClick(node: NodeVersioned): Boolean {
|
private var actionNodeMode: ActionMode? = null
|
||||||
|
|
||||||
|
private fun finishNodeAction() {
|
||||||
|
actionNodeMode?.finish()
|
||||||
|
actionNodeMode = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNodeSelected(nodes: List<Node>): Boolean {
|
||||||
|
if (nodes.isNotEmpty()) {
|
||||||
|
if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) {
|
||||||
|
mListNodesFragment?.actionNodesCallback(nodes, this)?.let {
|
||||||
|
actionNodeMode = toolbarAction?.startSupportActionMode(it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actionNodeMode?.invalidate()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finishNodeAction()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOpenMenuClick(node: Node): Boolean {
|
||||||
|
finishNodeAction()
|
||||||
onNodeClick(node)
|
onNodeClick(node)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEditMenuClick(node: NodeVersioned): Boolean {
|
override fun onEditMenuClick(node: Node): Boolean {
|
||||||
|
finishNodeAction()
|
||||||
when (node.type) {
|
when (node.type) {
|
||||||
Type.GROUP -> {
|
Type.GROUP -> {
|
||||||
mOldGroupToUpdate = node as GroupVersioned
|
mOldGroupToUpdate = node as Group
|
||||||
GroupEditDialogFragment.build(mOldGroupToUpdate!!)
|
GroupEditDialogFragment.build(mOldGroupToUpdate!!)
|
||||||
.show(supportFragmentManager,
|
.show(supportFragmentManager,
|
||||||
GroupEditDialogFragment.TAG_CREATE_GROUP)
|
GroupEditDialogFragment.TAG_CREATE_GROUP)
|
||||||
}
|
}
|
||||||
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as EntryVersioned)
|
Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as Entry)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCopyMenuClick(node: NodeVersioned): Boolean {
|
override fun onCopyMenuClick(nodes: List<Node>): Boolean {
|
||||||
toolbarPasteExpandableLayout?.expand()
|
actionNodeMode?.invalidate()
|
||||||
mNodeToCopy = node
|
|
||||||
toolbarPaste?.setOnMenuItemClickListener(OnCopyMenuItemClickListener())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class OnCopyMenuItemClickListener : Toolbar.OnMenuItemClickListener {
|
// Nothing here fragment calls onPasteMenuClick internally
|
||||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
|
||||||
toolbarPasteExpandableLayout?.collapse()
|
|
||||||
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.menu_paste -> {
|
|
||||||
when (mNodeToCopy?.type) {
|
|
||||||
Type.GROUP -> Log.e(TAG, "Copy not allowed for group")
|
|
||||||
Type.ENTRY -> {
|
|
||||||
mCurrentGroup?.let { currentGroup ->
|
|
||||||
copyEntry(mNodeToCopy as EntryVersioned, currentGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mNodeToCopy = null
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun copyEntry(entryToCopy: EntryVersioned, newParent: GroupVersioned) {
|
|
||||||
ProgressDialogSaveDatabaseThread(this) {
|
|
||||||
CopyEntryRunnable(this,
|
|
||||||
Database.getInstance(),
|
|
||||||
entryToCopy,
|
|
||||||
newParent,
|
|
||||||
AfterAddNodeRunnable(),
|
|
||||||
!mReadOnly)
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMoveMenuClick(node: NodeVersioned): Boolean {
|
|
||||||
toolbarPasteExpandableLayout?.expand()
|
|
||||||
mNodeToMove = node
|
|
||||||
toolbarPaste?.setOnMenuItemClickListener(OnMoveMenuItemClickListener())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class OnMoveMenuItemClickListener : Toolbar.OnMenuItemClickListener {
|
|
||||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
|
||||||
toolbarPasteExpandableLayout?.collapse()
|
|
||||||
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.menu_paste -> {
|
|
||||||
when (mNodeToMove?.type) {
|
|
||||||
Type.GROUP -> {
|
|
||||||
mCurrentGroup?.let { currentGroup ->
|
|
||||||
moveGroup(mNodeToMove as GroupVersioned, currentGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type.ENTRY -> {
|
|
||||||
mCurrentGroup?.let { currentGroup ->
|
|
||||||
moveEntry(mNodeToMove as EntryVersioned, currentGroup)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mNodeToMove = null
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun moveGroup(groupToMove: GroupVersioned, newParent: GroupVersioned) {
|
|
||||||
ProgressDialogSaveDatabaseThread(this) {
|
|
||||||
MoveGroupRunnable(
|
|
||||||
this,
|
|
||||||
Database.getInstance(),
|
|
||||||
groupToMove,
|
|
||||||
newParent,
|
|
||||||
AfterAddNodeRunnable(),
|
|
||||||
!mReadOnly)
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun moveEntry(entryToMove: EntryVersioned, newParent: GroupVersioned) {
|
|
||||||
ProgressDialogSaveDatabaseThread(this) {
|
|
||||||
MoveEntryRunnable(
|
|
||||||
this,
|
|
||||||
Database.getInstance(),
|
|
||||||
entryToMove,
|
|
||||||
newParent,
|
|
||||||
AfterAddNodeRunnable(),
|
|
||||||
!mReadOnly)
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDeleteMenuClick(node: NodeVersioned): Boolean {
|
|
||||||
when (node.type) {
|
|
||||||
Type.GROUP -> deleteGroup(node as GroupVersioned)
|
|
||||||
Type.ENTRY -> deleteEntry(node as EntryVersioned)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteGroup(group: GroupVersioned) {
|
override fun onMoveMenuClick(nodes: List<Node>): Boolean {
|
||||||
//TODO Verify trash recycle bin
|
actionNodeMode?.invalidate()
|
||||||
ProgressDialogSaveDatabaseThread(this) {
|
|
||||||
DeleteGroupRunnable(
|
// Nothing here fragment calls onPasteMenuClick internally
|
||||||
this,
|
return true
|
||||||
Database.getInstance(),
|
|
||||||
group,
|
|
||||||
AfterDeleteNodeRunnable(),
|
|
||||||
!mReadOnly)
|
|
||||||
}.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteEntry(entry: EntryVersioned) {
|
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
|
||||||
ProgressDialogSaveDatabaseThread(this) {
|
nodes: List<Node>): Boolean {
|
||||||
DeleteEntryRunnable(
|
// Move or copy only if allowed (in root if allowed)
|
||||||
this,
|
if (mCurrentGroup != mDatabase?.rootGroup
|
||||||
Database.getInstance(),
|
|| mDatabase?.rootCanContainsEntry() == true) {
|
||||||
entry,
|
|
||||||
AfterDeleteNodeRunnable(),
|
when (pasteMode) {
|
||||||
!mReadOnly)
|
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
|
||||||
}.start()
|
// 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 -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
coordinatorLayout?.let { coordinatorLayout ->
|
||||||
|
Snackbar.make(coordinatorLayout,
|
||||||
|
R.string.error_copy_entry_here,
|
||||||
|
Snackbar.LENGTH_LONG).asError().show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(
|
||||||
|
nodes,
|
||||||
|
!mReadOnly && mAutoSaveEnable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
@@ -584,16 +639,32 @@ class GroupActivity : LockingActivity(),
|
|||||||
mSearchSuggestionAdapter?.reInit(this)
|
mSearchSuggestionAdapter?.reInit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
|
||||||
|
finishNodeAction()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
|
||||||
val inflater = menuInflater
|
val inflater = menuInflater
|
||||||
inflater.inflate(R.menu.search, menu)
|
inflater.inflate(R.menu.search, menu)
|
||||||
inflater.inflate(R.menu.database_lock, menu)
|
inflater.inflate(R.menu.database, menu)
|
||||||
|
if (mReadOnly) {
|
||||||
|
menu.findItem(R.id.menu_save_database)?.isVisible = false
|
||||||
|
}
|
||||||
if (!mSelectionMode) {
|
if (!mSelectionMode) {
|
||||||
inflater.inflate(R.menu.default_menu, menu)
|
inflater.inflate(R.menu.default_menu, menu)
|
||||||
MenuUtil.contributionMenuInflater(inflater, 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
|
// Get the SearchView and set the searchable configuration
|
||||||
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||||
|
|
||||||
@@ -702,6 +773,17 @@ class GroupActivity : LockingActivity(),
|
|||||||
lockAndExit()
|
lockAndExit()
|
||||||
return true
|
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)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// Check the time lock before launching settings
|
// Check the time lock before launching settings
|
||||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, mReadOnly, true)
|
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, mReadOnly, true)
|
||||||
@@ -712,8 +794,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
|
|
||||||
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
|
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
|
||||||
name: String?,
|
name: String?,
|
||||||
icon: PwIcon?) {
|
icon: IconImage?) {
|
||||||
val database = Database.getInstance()
|
|
||||||
|
|
||||||
if (name != null && name.isNotEmpty() && icon != null) {
|
if (name != null && name.isNotEmpty() && icon != null) {
|
||||||
when (action) {
|
when (action) {
|
||||||
@@ -721,111 +802,49 @@ class GroupActivity : LockingActivity(),
|
|||||||
// If group creation
|
// If group creation
|
||||||
mCurrentGroup?.let { currentGroup ->
|
mCurrentGroup?.let { currentGroup ->
|
||||||
// Build the group
|
// Build the group
|
||||||
database.createGroup()?.let { newGroup ->
|
mDatabase?.createGroup()?.let { newGroup ->
|
||||||
newGroup.title = name
|
newGroup.title = name
|
||||||
newGroup.icon = icon
|
newGroup.icon = icon
|
||||||
// Not really needed here because added in runnable but safe
|
// Not really needed here because added in runnable but safe
|
||||||
newGroup.parent = currentGroup
|
newGroup.parent = currentGroup
|
||||||
|
|
||||||
// If group created save it in the database
|
mProgressDialogThread?.startDatabaseCreateGroup(
|
||||||
ProgressDialogSaveDatabaseThread(this) {
|
newGroup,
|
||||||
AddGroupRunnable(this,
|
currentGroup,
|
||||||
Database.getInstance(),
|
!mReadOnly && mAutoSaveEnable
|
||||||
newGroup,
|
)
|
||||||
currentGroup,
|
|
||||||
AfterAddNodeRunnable(),
|
|
||||||
!mReadOnly)
|
|
||||||
}.start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GroupEditDialogFragment.EditGroupDialogAction.UPDATE ->
|
GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> {
|
||||||
// If update add new elements
|
// If update add new elements
|
||||||
mOldGroupToUpdate?.let { oldGroupToUpdate ->
|
mOldGroupToUpdate?.let { oldGroupToUpdate ->
|
||||||
GroupVersioned(oldGroupToUpdate).let { updateGroup ->
|
val updateGroup = Group(oldGroupToUpdate).let { updateGroup ->
|
||||||
updateGroup.title = name
|
updateGroup.apply {
|
||||||
// TODO custom icon
|
// WARNING remove parent and children to keep memory
|
||||||
updateGroup.icon = icon
|
removeParent()
|
||||||
|
removeChildren()
|
||||||
|
|
||||||
mListNodesFragment?.removeNode(oldGroupToUpdate)
|
title = name
|
||||||
|
this.icon = icon // TODO custom icon
|
||||||
// If group updated save it in the database
|
}
|
||||||
ProgressDialogSaveDatabaseThread(this) {
|
|
||||||
UpdateGroupRunnable(this,
|
|
||||||
Database.getInstance(),
|
|
||||||
oldGroupToUpdate,
|
|
||||||
updateGroup,
|
|
||||||
AfterUpdateNodeRunnable(),
|
|
||||||
!mReadOnly)
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inner class AfterAddNodeRunnable : AfterActionNodeFinishRunnable() {
|
|
||||||
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
|
|
||||||
runOnUiThread {
|
|
||||||
if (actionNodeValues.result.isSuccess) {
|
|
||||||
if (actionNodeValues.newNode != null)
|
|
||||||
mListNodesFragment?.addNode(actionNodeValues.newNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inner class AfterUpdateNodeRunnable : AfterActionNodeFinishRunnable() {
|
|
||||||
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
|
|
||||||
runOnUiThread {
|
|
||||||
if (actionNodeValues.result.isSuccess) {
|
|
||||||
if (actionNodeValues.oldNode!= null && actionNodeValues.newNode != null)
|
|
||||||
mListNodesFragment?.updateNode(actionNodeValues.oldNode, actionNodeValues.newNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inner class AfterDeleteNodeRunnable : AfterActionNodeFinishRunnable() {
|
|
||||||
override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) {
|
|
||||||
runOnUiThread {
|
|
||||||
if (actionNodeValues.result.isSuccess) {
|
|
||||||
|
|
||||||
// If the action register the position, use it to remove the entry view
|
|
||||||
val positionNode = actionNodeValues.result.data?.getInt(NODE_POSITION_FOR_ACTION_NATURAL_ORDER_KEY)
|
|
||||||
if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB
|
|
||||||
&& positionNode != null) {
|
|
||||||
mListNodesFragment?.removeNodeAt(positionNode)
|
|
||||||
} else {
|
|
||||||
// else use the old Node that was the entry unchanged with the old parent
|
|
||||||
actionNodeValues.oldNode?.let { oldNode ->
|
|
||||||
mListNodesFragment?.removeNode(oldNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add trash in views list if it doesn't exists
|
|
||||||
val database = Database.getInstance()
|
|
||||||
if (database.isRecycleBinEnabled) {
|
|
||||||
val recycleBin = database.recycleBin
|
|
||||||
if (mCurrentGroup != null && recycleBin != null
|
|
||||||
&& mCurrentGroup!!.parent == null
|
|
||||||
&& mCurrentGroup != recycleBin) {
|
|
||||||
if (mListNodesFragment?.contains(recycleBin) == true)
|
|
||||||
mListNodesFragment?.updateNode(recycleBin)
|
|
||||||
else
|
|
||||||
mListNodesFragment?.addNode(recycleBin)
|
|
||||||
}
|
}
|
||||||
|
// If group updated save it in the database
|
||||||
|
mProgressDialogThread?.startDatabaseUpdateGroup(
|
||||||
|
oldGroupToUpdate,
|
||||||
|
updateGroup,
|
||||||
|
!mReadOnly && mAutoSaveEnable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
|
override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?,
|
||||||
name: String?,
|
name: String?,
|
||||||
icon: PwIcon?) {
|
icon: IconImage?) {
|
||||||
// Do nothing here
|
// Do nothing here
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,14 +855,6 @@ class GroupActivity : LockingActivity(),
|
|||||||
.iconPicked(bundle)
|
.iconPicked(bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
|
||||||
mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom)
|
mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom)
|
||||||
}
|
}
|
||||||
@@ -902,25 +913,29 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
// Normal way when we are not in root
|
if (mListNodesFragment?.nodeActionSelectionMode == true) {
|
||||||
if (mRootGroup != null && mRootGroup != mCurrentGroup)
|
finishNodeAction()
|
||||||
super.onBackPressed()
|
} else {
|
||||||
// Else lock if needed
|
// Normal way when we are not in root
|
||||||
else {
|
if (mRootGroup != null && mRootGroup != mCurrentGroup)
|
||||||
if (PreferencesUtil.isLockDatabaseWhenBackButtonOnRootClicked(this)) {
|
|
||||||
lockAndExit()
|
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
} else {
|
// Else lock if needed
|
||||||
moveTaskToBack(true)
|
else {
|
||||||
|
if (PreferencesUtil.isLockDatabaseWhenBackButtonOnRootClicked(this)) {
|
||||||
|
lockAndExit()
|
||||||
|
super.onBackPressed()
|
||||||
|
} else {
|
||||||
|
moveTaskToBack(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment
|
mListNodesFragment = supportFragmentManager.findFragmentByTag(LIST_NODES_FRAGMENT_TAG) as ListNodesFragment
|
||||||
// to refresh fragment
|
// to refresh fragment
|
||||||
mListNodesFragment?.rebuildList()
|
mListNodesFragment?.rebuildList()
|
||||||
mCurrentGroup = mListNodesFragment?.mainGroup
|
mCurrentGroup = mListNodesFragment?.mainGroup
|
||||||
removeSearchInIntent(intent)
|
removeSearchInIntent(intent)
|
||||||
assignGroupViewElements()
|
assignGroupViewElements()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -931,13 +946,15 @@ class GroupActivity : LockingActivity(),
|
|||||||
private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG"
|
private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG"
|
||||||
private const val SEARCH_FRAGMENT_TAG = "SEARCH_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 const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
|
||||||
private const val NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY"
|
|
||||||
private const val NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY"
|
|
||||||
|
|
||||||
private fun buildAndLaunchIntent(activity: Activity, group: GroupVersioned?, readOnly: Boolean,
|
private fun buildAndLaunchIntent(context: Context, group: Group?, readOnly: Boolean,
|
||||||
intentBuildLauncher: (Intent) -> Unit) {
|
intentBuildLauncher: (Intent) -> Unit) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
val checkTime = if (context is Activity)
|
||||||
val intent = Intent(activity, GroupActivity::class.java)
|
TimeoutHelper.checkTimeAndLockIfTimeout(context)
|
||||||
|
else
|
||||||
|
TimeoutHelper.checkTime(context)
|
||||||
|
if (checkTime) {
|
||||||
|
val intent = Intent(context, GroupActivity::class.java)
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
intent.putExtra(GROUP_ID_KEY, group.nodeId)
|
intent.putExtra(GROUP_ID_KEY, group.nodeId)
|
||||||
}
|
}
|
||||||
@@ -953,10 +970,10 @@ class GroupActivity : LockingActivity(),
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun launch(activity: Activity, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
|
fun launch(context: Context, readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
||||||
TimeoutHelper.recordTime(activity)
|
TimeoutHelper.recordTime(context)
|
||||||
buildAndLaunchIntent(activity, null, readOnly) { intent ->
|
buildAndLaunchIntent(context, null, readOnly) { intent ->
|
||||||
activity.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,10 +984,10 @@ class GroupActivity : LockingActivity(),
|
|||||||
*/
|
*/
|
||||||
// TODO implement pre search to directly open the direct group
|
// TODO implement pre search to directly open the direct group
|
||||||
|
|
||||||
fun launchForKeyboarSelection(activity: Activity, readOnly: Boolean) {
|
fun launchForKeyboardSelection(context: Context, readOnly: Boolean) {
|
||||||
TimeoutHelper.recordTime(activity)
|
TimeoutHelper.recordTime(context)
|
||||||
buildAndLaunchIntent(activity, null, readOnly) { intent ->
|
buildAndLaunchIntent(context, null, readOnly) { intent ->
|
||||||
EntrySelectionHelper.startActivityForEntrySelection(activity, intent)
|
EntrySelectionHelper.startActivityForEntrySelection(context, intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.activities
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -14,30 +33,39 @@ import android.view.MenuInflater
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.adapters.NodeAdapter
|
import com.kunzisoft.keepass.adapters.NodeAdapter
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.element.NodeVersioned
|
import com.kunzisoft.keepass.database.element.node.Node
|
||||||
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
|
class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener {
|
||||||
|
|
||||||
private var nodeClickCallback: NodeAdapter.NodeClickCallback? = null
|
private var nodeClickListener: NodeClickListener? = null
|
||||||
private var nodeMenuListener: NodeAdapter.NodeMenuListener? = null
|
|
||||||
private var onScrollListener: OnScrollListener? = null
|
private var onScrollListener: OnScrollListener? = null
|
||||||
|
|
||||||
private var listView: RecyclerView? = null
|
private var listView: RecyclerView? = null
|
||||||
var mainGroup: GroupVersioned? = null
|
var mainGroup: Group? = null
|
||||||
private set
|
private set
|
||||||
private var mAdapter: NodeAdapter? = null
|
private var mAdapter: NodeAdapter? = null
|
||||||
|
|
||||||
|
var nodeActionSelectionMode = false
|
||||||
|
private set
|
||||||
|
var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED
|
||||||
|
private set
|
||||||
|
private val listActionNodes = LinkedList<Node>()
|
||||||
|
private val listPasteNodes = LinkedList<Node>()
|
||||||
|
|
||||||
private var notFoundView: View? = null
|
private var notFoundView: View? = null
|
||||||
private var isASearchResult: Boolean = false
|
private var isASearchResult: Boolean = false
|
||||||
|
|
||||||
@@ -56,22 +84,13 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
try {
|
try {
|
||||||
nodeClickCallback = context as NodeAdapter.NodeClickCallback
|
nodeClickListener = context as NodeClickListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
// The activity doesn't implement the interface, throw exception
|
// The activity doesn't implement the interface, throw exception
|
||||||
throw ClassCastException(context.toString()
|
throw ClassCastException(context.toString()
|
||||||
+ " must implement " + NodeAdapter.NodeClickCallback::class.java.name)
|
+ " must implement " + NodeAdapter.NodeClickCallback::class.java.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
nodeMenuListener = context as NodeAdapter.NodeMenuListener
|
|
||||||
} catch (e: ClassCastException) {
|
|
||||||
nodeMenuListener = null
|
|
||||||
// Context menu can be omit
|
|
||||||
Log.w(TAG, context.toString()
|
|
||||||
+ " must implement " + NodeAdapter.NodeMenuListener::class.java.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
onScrollListener = context as OnScrollListener
|
onScrollListener = context as OnScrollListener
|
||||||
} catch (e: ClassCastException) {
|
} catch (e: ClassCastException) {
|
||||||
@@ -85,33 +104,58 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
activity?.let { currentActivity ->
|
setHasOptionsMenu(true)
|
||||||
setHasOptionsMenu(true)
|
|
||||||
|
|
||||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments)
|
||||||
|
|
||||||
arguments?.let { args ->
|
arguments?.let { args ->
|
||||||
// Contains all the group in element
|
// Contains all the group in element
|
||||||
if (args.containsKey(GROUP_KEY)) {
|
if (args.containsKey(GROUP_KEY)) {
|
||||||
mainGroup = args.getParcelable(GROUP_KEY)
|
mainGroup = args.getParcelable(GROUP_KEY)
|
||||||
}
|
|
||||||
if (args.containsKey(IS_SEARCH)) {
|
|
||||||
isASearchResult = args.getBoolean(IS_SEARCH)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (args.containsKey(IS_SEARCH)) {
|
||||||
contextThemed?.let { context ->
|
isASearchResult = args.getBoolean(IS_SEARCH)
|
||||||
mAdapter = NodeAdapter(context, currentActivity.menuInflater)
|
|
||||||
mAdapter?.apply {
|
|
||||||
setReadOnly(readOnly)
|
|
||||||
setIsASearchResult(isASearchResult)
|
|
||||||
setOnNodeClickListener(nodeClickCallback)
|
|
||||||
setActivateContextMenu(true)
|
|
||||||
setNodeMenuListener(nodeMenuListener)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contextThemed?.let { context ->
|
||||||
|
mAdapter = NodeAdapter(context)
|
||||||
|
mAdapter?.apply {
|
||||||
|
setOnNodeClickListener(object : NodeAdapter.NodeClickCallback {
|
||||||
|
override fun onNodeClick(node: Node) {
|
||||||
|
if (nodeActionSelectionMode) {
|
||||||
|
if (listActionNodes.contains(node)) {
|
||||||
|
// Remove selected item if already selected
|
||||||
|
listActionNodes.remove(node)
|
||||||
|
} else {
|
||||||
|
// Add selected item if not already selected
|
||||||
|
listActionNodes.add(node)
|
||||||
|
}
|
||||||
|
nodeClickListener?.onNodeSelected(listActionNodes)
|
||||||
|
setActionNodes(listActionNodes)
|
||||||
|
notifyNodeChanged(node)
|
||||||
|
} else {
|
||||||
|
nodeClickListener?.onNodeClick(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNodeLongClick(node: Node): Boolean {
|
||||||
|
if (nodeActionPasteMode == PasteMode.UNDEFINED) {
|
||||||
|
// Select the first item after a long click
|
||||||
|
if (!listActionNodes.contains(node))
|
||||||
|
listActionNodes.add(node)
|
||||||
|
|
||||||
|
nodeClickListener?.onNodeSelected(listActionNodes)
|
||||||
|
|
||||||
|
setActionNodes(listActionNodes)
|
||||||
|
notifyNodeChanged(node)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@@ -148,10 +192,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
activity?.intent?.let {
|
activity?.intent?.let {
|
||||||
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(it)
|
selectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(it)
|
||||||
}
|
}
|
||||||
// Force read only mode if selection mode
|
|
||||||
mAdapter?.apply {
|
|
||||||
setReadOnly(readOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh data
|
// Refresh data
|
||||||
mAdapter?.notifyDataSetChanged()
|
mAdapter?.notifyDataSetChanged()
|
||||||
@@ -207,8 +247,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
R.id.menu_sort -> {
|
R.id.menu_sort -> {
|
||||||
context?.let { context ->
|
context?.let { context ->
|
||||||
val sortDialogFragment: SortDialogFragment =
|
val sortDialogFragment: SortDialogFragment =
|
||||||
if (Database.getInstance().isRecycleBinAvailable
|
if (Database.getInstance().isRecycleBinEnabled) {
|
||||||
&& Database.getInstance().isRecycleBinEnabled) {
|
|
||||||
SortDialogFragment.getInstance(
|
SortDialogFragment.getInstance(
|
||||||
PreferencesUtil.getListSort(context),
|
PreferencesUtil.getListSort(context),
|
||||||
PreferencesUtil.getAscendingSort(context),
|
PreferencesUtil.getAscendingSort(context),
|
||||||
@@ -230,6 +269,103 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun actionNodesCallback(nodes: List<Node>,
|
||||||
|
menuListener: NodesActionMenuListener?) : ActionMode.Callback {
|
||||||
|
|
||||||
|
return object : ActionMode.Callback {
|
||||||
|
|
||||||
|
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
|
||||||
|
menu?.clear()
|
||||||
|
|
||||||
|
if (nodeActionPasteMode != PasteMode.UNDEFINED) {
|
||||||
|
mode?.menuInflater?.inflate(R.menu.node_paste_menu, menu)
|
||||||
|
} else {
|
||||||
|
nodeActionSelectionMode = true
|
||||||
|
mode?.menuInflater?.inflate(R.menu.node_menu, menu)
|
||||||
|
|
||||||
|
val database = Database.getInstance()
|
||||||
|
|
||||||
|
// Open and Edit for a single item
|
||||||
|
if (nodes.size == 1) {
|
||||||
|
// Edition
|
||||||
|
if (readOnly
|
||||||
|
|| (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) {
|
||||||
|
menu?.removeItem(R.id.menu_edit)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
menu?.removeItem(R.id.menu_open)
|
||||||
|
menu?.removeItem(R.id.menu_edit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and Move (not for groups)
|
||||||
|
if (readOnly
|
||||||
|
|| isASearchResult
|
||||||
|
|| nodes.any { it.type == Type.GROUP }) {
|
||||||
|
// TODO COPY For Group
|
||||||
|
menu?.removeItem(R.id.menu_copy)
|
||||||
|
menu?.removeItem(R.id.menu_move)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletion
|
||||||
|
if (readOnly
|
||||||
|
|| (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) {
|
||||||
|
menu?.removeItem(R.id.menu_delete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the number of items selected in title
|
||||||
|
mode?.title = nodes.size.toString()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
|
||||||
|
if (menuListener == null)
|
||||||
|
return false
|
||||||
|
return when (item?.itemId) {
|
||||||
|
R.id.menu_open -> menuListener.onOpenMenuClick(nodes[0])
|
||||||
|
R.id.menu_edit -> menuListener.onEditMenuClick(nodes[0])
|
||||||
|
R.id.menu_copy -> {
|
||||||
|
nodeActionPasteMode = PasteMode.PASTE_FROM_COPY
|
||||||
|
mAdapter?.unselectActionNodes()
|
||||||
|
val returnValue = menuListener.onCopyMenuClick(nodes)
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
returnValue
|
||||||
|
}
|
||||||
|
R.id.menu_move -> {
|
||||||
|
nodeActionPasteMode = PasteMode.PASTE_FROM_MOVE
|
||||||
|
mAdapter?.unselectActionNodes()
|
||||||
|
val returnValue = menuListener.onMoveMenuClick(nodes)
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
returnValue
|
||||||
|
}
|
||||||
|
R.id.menu_delete -> menuListener.onDeleteMenuClick(nodes)
|
||||||
|
R.id.menu_paste -> {
|
||||||
|
val returnValue = menuListener.onPasteMenuClick(nodeActionPasteMode, nodes)
|
||||||
|
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
returnValue
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
|
listActionNodes.clear()
|
||||||
|
listPasteNodes.clear()
|
||||||
|
mAdapter?.unselectActionNodes()
|
||||||
|
nodeActionPasteMode = PasteMode.UNDEFINED
|
||||||
|
nodeActionSelectionMode = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
@@ -237,7 +373,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> {
|
||||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE
|
||||||
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
|| resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||||
data?.getParcelableExtra<NodeVersioned>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode ->
|
data?.getParcelableExtra<Node>(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode ->
|
||||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
||||||
mAdapter?.addNode(newNode)
|
mAdapter?.addNode(newNode)
|
||||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||||
@@ -252,26 +388,66 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contains(node: NodeVersioned): Boolean {
|
fun contains(node: Node): Boolean {
|
||||||
return mAdapter?.contains(node) ?: false
|
return mAdapter?.contains(node) ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addNode(newNode: NodeVersioned) {
|
fun addNode(newNode: Node) {
|
||||||
mAdapter?.addNode(newNode)
|
mAdapter?.addNode(newNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) {
|
fun addNodes(newNodes: List<Node>) {
|
||||||
|
mAdapter?.addNodes(newNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateNode(oldNode: Node, newNode: Node? = null) {
|
||||||
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
|
mAdapter?.updateNode(oldNode, newNode ?: oldNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeNode(pwNode: NodeVersioned) {
|
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||||
|
mAdapter?.updateNodes(oldNodes, newNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeNode(pwNode: Node) {
|
||||||
mAdapter?.removeNode(pwNode)
|
mAdapter?.removeNode(pwNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeNodes(nodes: List<Node>) {
|
||||||
|
mAdapter?.removeNodes(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
fun removeNodeAt(position: Int) {
|
fun removeNodeAt(position: Int) {
|
||||||
mAdapter?.removeNodeAt(position)
|
mAdapter?.removeNodeAt(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeNodesAt(positions: IntArray) {
|
||||||
|
mAdapter?.removeNodesAt(positions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class PasteMode {
|
||||||
|
UNDEFINED, PASTE_FROM_COPY, PASTE_FROM_MOVE
|
||||||
|
}
|
||||||
|
|
||||||
interface OnScrollListener {
|
interface OnScrollListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -290,7 +466,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
private const val GROUP_KEY = "GROUP_KEY"
|
private const val GROUP_KEY = "GROUP_KEY"
|
||||||
private const val IS_SEARCH = "IS_SEARCH"
|
private const val IS_SEARCH = "IS_SEARCH"
|
||||||
|
|
||||||
fun newInstance(group: GroupVersioned?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
|
fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
bundle.putParcelable(GROUP_KEY, group)
|
bundle.putParcelable(GROUP_KEY, group)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities
|
package com.kunzisoft.keepass.activities
|
||||||
@@ -22,17 +22,14 @@ package com.kunzisoft.keepass.activities
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
import android.app.backup.BackupManager
|
import android.app.backup.BackupManager
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -42,40 +39,47 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.biometric.BiometricManager
|
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.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.FingerPrintExplanationDialog
|
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||||
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
|
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable
|
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
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
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.KEY_FILE_KEY
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.MASTER_PASSWORD_KEY
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.READ_ONLY_KEY
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||||
import com.kunzisoft.keepass.view.asError
|
import com.kunzisoft.keepass.view.asError
|
||||||
import kotlinx.android.synthetic.main.activity_password.*
|
import kotlinx.android.synthetic.main.activity_password.*
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
|
|
||||||
class PasswordActivity : StylishActivity() {
|
class PasswordActivity : StylishActivity() {
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
|
|
||||||
|
private var containerView: View? = null
|
||||||
private var filenameView: TextView? = null
|
private var filenameView: TextView? = null
|
||||||
private var passwordView: EditText? = null
|
private var passwordView: EditText? = null
|
||||||
private var keyFileView: EditText? = null
|
private var keyFileView: EditText? = null
|
||||||
@@ -87,13 +91,18 @@ class PasswordActivity : StylishActivity() {
|
|||||||
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||||
|
|
||||||
private var mDatabaseFileUri: Uri? = null
|
private var mDatabaseFileUri: Uri? = null
|
||||||
|
private var mDatabaseKeyFileUri: Uri? = null
|
||||||
|
|
||||||
private var prefs: SharedPreferences? = null
|
private var prefs: SharedPreferences? = null
|
||||||
|
|
||||||
private var mRememberKeyFile: Boolean = false
|
private var mRememberKeyFile: Boolean = false
|
||||||
private var mOpenFileHelper: OpenFileHelper? = null
|
private var mOpenFileHelper: OpenFileHelper? = null
|
||||||
|
|
||||||
|
private var mPermissionAsked = false
|
||||||
private var readOnly: Boolean = false
|
private var readOnly: Boolean = false
|
||||||
|
|
||||||
|
private var mProgressDialogThread: ProgressDialogThread? = null
|
||||||
|
|
||||||
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
|
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -101,8 +110,7 @@ class PasswordActivity : StylishActivity() {
|
|||||||
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
|
||||||
mRememberKeyFile = prefs!!.getBoolean(getString(R.string.keyfile_key),
|
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
|
||||||
resources.getBoolean(R.bool.keyfile_default))
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_password)
|
setContentView(R.layout.activity_password)
|
||||||
|
|
||||||
@@ -112,6 +120,7 @@ class PasswordActivity : StylishActivity() {
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
|
containerView = findViewById(R.id.container)
|
||||||
confirmButtonView = findViewById(R.id.pass_ok)
|
confirmButtonView = findViewById(R.id.pass_ok)
|
||||||
filenameView = findViewById(R.id.filename)
|
filenameView = findViewById(R.id.filename)
|
||||||
passwordView = findViewById(R.id.password)
|
passwordView = findViewById(R.id.password)
|
||||||
@@ -119,11 +128,12 @@ class PasswordActivity : StylishActivity() {
|
|||||||
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
||||||
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
||||||
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
|
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
|
||||||
advancedUnlockInfoView = findViewById(R.id.fingerprint_info)
|
advancedUnlockInfoView = findViewById(R.id.biometric_info)
|
||||||
|
|
||||||
|
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
|
||||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||||
|
|
||||||
val browseView = findViewById<View>(R.id.browse_button)
|
val browseView = findViewById<View>(R.id.open_database_button)
|
||||||
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
||||||
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
|
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
|
||||||
|
|
||||||
@@ -153,6 +163,93 @@ class PasswordActivity : StylishActivity() {
|
|||||||
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||||
enableOrNotTheConfirmationButton()
|
enableOrNotTheConfirmationButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the password in view in all cases
|
||||||
|
removePassword()
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
launchGroupActivity()
|
||||||
|
} else {
|
||||||
|
var resultError = ""
|
||||||
|
val resultException = result.exception
|
||||||
|
val resultMessage = result.message
|
||||||
|
|
||||||
|
if (resultException != null) {
|
||||||
|
resultError = resultException.getLocalizedMessage(resources)
|
||||||
|
|
||||||
|
// Relaunch loading if we need to fix UUID
|
||||||
|
if (resultException is DuplicateUuidDatabaseException) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchGroupActivity() {
|
||||||
|
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||||
|
{
|
||||||
|
GroupActivity.launch(this@PasswordActivity, readOnly)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
GroupActivity.launchForKeyboardSelection(this@PasswordActivity, readOnly)
|
||||||
|
// Do not keep history
|
||||||
|
finish()
|
||||||
|
},
|
||||||
|
{ assistStructure ->
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private val onEditorActionListener = object : TextView.OnEditorActionListener {
|
private val onEditorActionListener = object : TextView.OnEditorActionListener {
|
||||||
@@ -166,6 +263,9 @@ class PasswordActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
if (Database.getInstance().loaded)
|
||||||
|
launchGroupActivity()
|
||||||
|
|
||||||
// If the database isn't accessible make sure to clear the password field, if it
|
// If the database isn't accessible make sure to clear the password field, if it
|
||||||
// was saved in the instance state
|
// was saved in the instance state
|
||||||
if (Database.getInstance().loaded) {
|
if (Database.getInstance().loaded) {
|
||||||
@@ -175,10 +275,13 @@ class PasswordActivity : StylishActivity() {
|
|||||||
// For check shutdown
|
// For check shutdown
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
mProgressDialogThread?.registerProgressTask()
|
||||||
|
|
||||||
initUriFromIntent()
|
initUriFromIntent()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
|
||||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
@@ -190,17 +293,10 @@ class PasswordActivity : StylishActivity() {
|
|||||||
|
|
||||||
// If is a view intent
|
// If is a view intent
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
if (action != null && action == VIEW_INTENT) {
|
if (action != null
|
||||||
|
&& action == VIEW_INTENT) {
|
||||||
val databaseUriRetrieve = intent.data
|
databaseUri = intent.data
|
||||||
// Stop activity here if we can't verify database URI
|
|
||||||
if (!UriUtil.verifyFileUri(databaseUriRetrieve)) {
|
|
||||||
Log.e(TAG, "File URI not validate")
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
databaseUri = databaseUriRetrieve
|
|
||||||
keyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
|
keyFileUri = UriUtil.getUriFromIntent(intent, KEY_KEYFILE)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
databaseUri = intent.getParcelableExtra(KEY_FILENAME)
|
databaseUri = intent.getParcelableExtra(KEY_FILENAME)
|
||||||
keyFileUri = intent.getParcelableExtra(KEY_KEYFILE)
|
keyFileUri = intent.getParcelableExtra(KEY_KEYFILE)
|
||||||
@@ -222,6 +318,7 @@ class PasswordActivity : StylishActivity() {
|
|||||||
|
|
||||||
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
|
private fun onPostInitUri(databaseFileUri: Uri?, keyFileUri: Uri?) {
|
||||||
mDatabaseFileUri = databaseFileUri
|
mDatabaseFileUri = databaseFileUri
|
||||||
|
mDatabaseKeyFileUri = keyFileUri
|
||||||
|
|
||||||
// Define title
|
// Define title
|
||||||
databaseFileUri?.let {
|
databaseFileUri?.let {
|
||||||
@@ -243,11 +340,13 @@ class PasswordActivity : StylishActivity() {
|
|||||||
newDefaultFileName = databaseFileUri ?: newDefaultFileName
|
newDefaultFileName = databaseFileUri ?: newDefaultFileName
|
||||||
}
|
}
|
||||||
|
|
||||||
newDefaultFileName?.let {
|
prefs?.edit()?.apply {
|
||||||
prefs?.edit()?.apply {
|
newDefaultFileName?.let {
|
||||||
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
|
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
|
||||||
apply()
|
} ?: kotlin.run {
|
||||||
|
remove(KEY_DEFAULT_DATABASE_PATH)
|
||||||
}
|
}
|
||||||
|
apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
val backupManager = BackupManager(this@PasswordActivity)
|
val backupManager = BackupManager(this@PasswordActivity)
|
||||||
@@ -273,15 +372,11 @@ class PasswordActivity : StylishActivity() {
|
|||||||
if (launchImmediately) {
|
if (launchImmediately) {
|
||||||
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
|
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
|
||||||
} else {
|
} else {
|
||||||
// Init FingerPrint elements
|
// Init Biometric elements
|
||||||
var fingerPrintInit = false
|
var biometricInitialize = false
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (PreferencesUtil.isBiometricUnlockEnable(this)) {
|
if (PreferencesUtil.isBiometricUnlockEnable(this)) {
|
||||||
|
|
||||||
advancedUnlockInfoView?.setOnClickListener {
|
|
||||||
FingerPrintExplanationDialog().show(supportFragmentManager, "fingerPrintExplanationDialog")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (advancedUnlockedManager == null && databaseFileUri != null) {
|
if (advancedUnlockedManager == null && databaseFileUri != null) {
|
||||||
advancedUnlockedManager = AdvancedUnlockedManager(this,
|
advancedUnlockedManager = AdvancedUnlockedManager(this,
|
||||||
databaseFileUri,
|
databaseFileUri,
|
||||||
@@ -303,18 +398,18 @@ class PasswordActivity : StylishActivity() {
|
|||||||
{ passwordDecrypted ->
|
{ passwordDecrypted ->
|
||||||
// Load the database if password is retrieve from biometric
|
// Load the database if password is retrieve from biometric
|
||||||
passwordDecrypted?.let {
|
passwordDecrypted?.let {
|
||||||
// Retrieve from fingerprint
|
// Retrieve from biometric
|
||||||
verifyKeyFileCheckboxAndLoadDatabase(it)
|
verifyKeyFileCheckboxAndLoadDatabase(it)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
advancedUnlockedManager?.initBiometric()
|
advancedUnlockedManager?.checkBiometricAvailability()
|
||||||
fingerPrintInit = true
|
biometricInitialize = true
|
||||||
} else {
|
} else {
|
||||||
advancedUnlockedManager?.destroy()
|
advancedUnlockedManager?.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!fingerPrintInit) {
|
if (!biometricInitialize) {
|
||||||
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||||
}
|
}
|
||||||
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||||
@@ -368,9 +463,8 @@ class PasswordActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
mProgressDialogThread?.unregisterProgressTask()
|
||||||
advancedUnlockedManager?.pause()
|
|
||||||
}
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,14 +485,18 @@ class PasswordActivity : StylishActivity() {
|
|||||||
keyFile: Uri?,
|
keyFile: Uri?,
|
||||||
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||||
val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
|
val keyPassword = if (checkboxPasswordView?.isChecked != true) null else password
|
||||||
val keyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
verifyKeyFileCheckbox(keyFile)
|
||||||
loadDatabase(keyPassword, keyFileUri, cipherDatabaseEntity)
|
loadDatabase(mDatabaseFileUri, keyPassword, mDatabaseKeyFileUri, cipherDatabaseEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
|
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
|
||||||
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
||||||
val keyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
verifyKeyFileCheckbox(keyFile)
|
||||||
loadDatabase(password, keyFileUri)
|
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun verifyKeyFileCheckbox(keyFile: Uri?) {
|
||||||
|
mDatabaseKeyFileUri = if (checkboxKeyFileView?.isChecked != true) null else keyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removePassword() {
|
private fun removePassword() {
|
||||||
@@ -406,103 +504,47 @@ class PasswordActivity : StylishActivity() {
|
|||||||
checkboxPasswordView?.isChecked = false
|
checkboxPasswordView?.isChecked = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDatabase(password: String?, keyFile: Uri?, cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
private fun loadDatabase(databaseFileUri: Uri?,
|
||||||
|
password: String?,
|
||||||
|
keyFileUri: Uri?,
|
||||||
|
cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||||
|
|
||||||
runOnUiThread {
|
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
|
||||||
if (PreferencesUtil.deletePasswordAfterConnexionAttempt(this)) {
|
removePassword()
|
||||||
removePassword()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear before we load
|
databaseFileUri?.let { databaseUri ->
|
||||||
val database = Database.getInstance()
|
|
||||||
database.closeAndClear(applicationContext.filesDir)
|
|
||||||
|
|
||||||
mDatabaseFileUri?.let { databaseUri ->
|
|
||||||
// Show the progress dialog and load the database
|
// Show the progress dialog and load the database
|
||||||
ProgressDialogThread(this,
|
showProgressDialogAndLoadDatabase(
|
||||||
{ progressTaskUpdater ->
|
databaseUri,
|
||||||
LoadDatabaseRunnable(
|
password,
|
||||||
WeakReference(this@PasswordActivity),
|
keyFileUri,
|
||||||
database,
|
readOnly,
|
||||||
databaseUri,
|
cipherDatabaseEntity,
|
||||||
password,
|
false)
|
||||||
keyFile,
|
|
||||||
progressTaskUpdater,
|
|
||||||
AfterLoadingDatabase(database, password, cipherDatabaseEntity))
|
|
||||||
},
|
|
||||||
R.string.loading_database).start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun showProgressDialogAndLoadDatabase(databaseUri: Uri,
|
||||||
* Called after verify and try to opening the database
|
password: String?,
|
||||||
*/
|
keyFile: Uri?,
|
||||||
private inner class AfterLoadingDatabase(val database: Database, val password: String?,
|
readOnly: Boolean,
|
||||||
val cipherDatabaseEntity: CipherDatabaseEntity? = null)
|
cipherDatabaseEntity: CipherDatabaseEntity?,
|
||||||
: ActionRunnable() {
|
fixDuplicateUUID: Boolean) {
|
||||||
|
mProgressDialogThread?.startDatabaseLoad(
|
||||||
override fun onFinishRun(result: Result) {
|
databaseUri,
|
||||||
runOnUiThread {
|
password,
|
||||||
// Recheck fingerprint if error
|
keyFile,
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
readOnly,
|
||||||
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
|
cipherDatabaseEntity,
|
||||||
// Stay with the same mode and init it
|
fixDuplicateUUID
|
||||||
advancedUnlockedManager?.initBiometricMode()
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.isSuccess) {
|
|
||||||
// Remove the password in view in all cases
|
|
||||||
removePassword()
|
|
||||||
|
|
||||||
// Register the biometric
|
|
||||||
if (cipherDatabaseEntity != null) {
|
|
||||||
CipherDatabaseAction.getInstance(this@PasswordActivity)
|
|
||||||
.addOrUpdateCipherDatabase(cipherDatabaseEntity) {
|
|
||||||
checkAndLaunchGroupActivity(database, password)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
checkAndLaunchGroupActivity(database, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if (result.message != null && result.message!!.isNotEmpty()) {
|
|
||||||
Snackbar.make(activity_password_coordinator_layout, result.message!!, Snackbar.LENGTH_LONG).asError().show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkAndLaunchGroupActivity(database: Database, password: String?) {
|
private fun showLoadDatabaseDuplicateUuidMessage(loadDatabaseWithFix: (() -> Unit)? = null) {
|
||||||
if (database.validatePasswordEncoding(password)) {
|
DuplicateUuidDialog().apply {
|
||||||
launchGroupActivity()
|
positiveAction = loadDatabaseWithFix
|
||||||
} else {
|
}.show(supportFragmentManager, "duplicateUUIDDialog")
|
||||||
PasswordEncodingDialogFragment().apply {
|
|
||||||
positiveButtonClickListener = DialogInterface.OnClickListener { _, _ ->
|
|
||||||
launchGroupActivity()
|
|
||||||
}
|
|
||||||
show(supportFragmentManager, "passwordEncodingTag")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun launchGroupActivity() {
|
|
||||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
|
||||||
{
|
|
||||||
GroupActivity.launch(this@PasswordActivity, readOnly)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
GroupActivity.launchForKeyboarSelection(this@PasswordActivity, readOnly)
|
|
||||||
// Do not keep history
|
|
||||||
finish()
|
|
||||||
},
|
|
||||||
{ assistStructure ->
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
@@ -514,53 +556,102 @@ class PasswordActivity : StylishActivity() {
|
|||||||
MenuUtil.defaultMenuInflater(inflater, menu)
|
MenuUtil.defaultMenuInflater(inflater, menu)
|
||||||
|
|
||||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
// Fingerprint menu
|
// biometric menu
|
||||||
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
|
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
|
|
||||||
// Show education views
|
launchEducation(menu) {
|
||||||
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
|
launchCheckPermission()
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
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) {
|
||||||
|
if (!performedEductionInProgress) {
|
||||||
|
performedEductionInProgress = true
|
||||||
|
// Show education views
|
||||||
|
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
|
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
|
||||||
menu: Menu) {
|
menu: Menu,
|
||||||
val unlockEducationPerformed = toolbar != null
|
onEducationFinished: ()-> Unit) {
|
||||||
|
val educationToolbar = toolbar
|
||||||
|
val unlockEducationPerformed = educationToolbar != null
|
||||||
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
|
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
|
||||||
toolbar!!,
|
educationToolbar,
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu)
|
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu)
|
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||||
})
|
})
|
||||||
if (!unlockEducationPerformed) {
|
if (!unlockEducationPerformed) {
|
||||||
|
val readOnlyEducationPerformed =
|
||||||
val readOnlyEducationPerformed = toolbar != null
|
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
|
||||||
&& toolbar!!.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
|
|
||||||
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
|
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
|
||||||
toolbar!!.findViewById(R.id.menu_open_file_read_mode_key),
|
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
|
||||||
{
|
{
|
||||||
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
|
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
|
||||||
performedNextEducation(passwordActivityEducation, menu)
|
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu)
|
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!readOnlyEducationPerformed) {
|
if (!readOnlyEducationPerformed) {
|
||||||
|
|
||||||
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
|
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
|
||||||
// fingerprintEducationPerformed
|
val biometricEducationPerformed =
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||||
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
|
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
|
||||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||||
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
|
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
|
||||||
&& passwordActivityEducation.checkAndPerformedFingerprintEducation(advancedUnlockInfoView?.unlockIconImageView!!)
|
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
|
||||||
|
{
|
||||||
|
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!biometricEducationPerformed) {
|
||||||
|
onEducationFinished.invoke()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,7 +674,7 @@ class PasswordActivity : StylishActivity() {
|
|||||||
readOnly = !readOnly
|
readOnly = !readOnly
|
||||||
changeOpenFileReadIcon(item)
|
changeOpenFileReadIcon(item)
|
||||||
}
|
}
|
||||||
R.id.menu_fingerprint_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
advancedUnlockedManager?.deleteEntryKey()
|
advancedUnlockedManager?.deleteEntryKey()
|
||||||
}
|
}
|
||||||
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||||
@@ -636,6 +727,10 @@ class PasswordActivity : StylishActivity() {
|
|||||||
private const val KEY_PASSWORD = "password"
|
private const val KEY_PASSWORD = "password"
|
||||||
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
|
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?,
|
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
|
||||||
intentBuildLauncher: (Intent) -> Unit) {
|
intentBuildLauncher: (Intent) -> Unit) {
|
||||||
val intent = Intent(activity, PasswordActivity::class.java)
|
val intent = Intent(activity, PasswordActivity::class.java)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -45,6 +45,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
private var rootView: View? = null
|
private var rootView: View? = null
|
||||||
|
|
||||||
private var passwordCheckBox: CompoundButton? = null
|
private var passwordCheckBox: CompoundButton? = null
|
||||||
|
|
||||||
|
private var passwordTextInputLayout: TextInputLayout? = null
|
||||||
private var passwordView: TextView? = null
|
private var passwordView: TextView? = null
|
||||||
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
||||||
private var passwordRepeatView: TextView? = null
|
private var passwordRepeatView: TextView? = null
|
||||||
@@ -96,6 +98,13 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
|
|
||||||
|
var allowNoMasterKey = false
|
||||||
|
arguments?.apply {
|
||||||
|
if (containsKey(ALLOW_NO_MASTER_KEY_ARG))
|
||||||
|
allowNoMasterKey = getBoolean(ALLOW_NO_MASTER_KEY_ARG, false)
|
||||||
|
}
|
||||||
|
|
||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
val inflater = activity.layoutInflater
|
val inflater = activity.layoutInflater
|
||||||
|
|
||||||
@@ -104,9 +113,10 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
.setTitle(R.string.assign_master_key)
|
.setTitle(R.string.assign_master_key)
|
||||||
// Add action buttons
|
// Add action buttons
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
|
||||||
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
passwordCheckBox = rootView?.findViewById(R.id.password_checkbox)
|
||||||
|
passwordTextInputLayout = rootView?.findViewById(R.id.password_input_layout)
|
||||||
passwordView = rootView?.findViewById(R.id.pass_password)
|
passwordView = rootView?.findViewById(R.id.pass_password)
|
||||||
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
||||||
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
||||||
@@ -116,7 +126,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
|
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
|
||||||
|
|
||||||
mOpenFileHelper = OpenFileHelper(this)
|
mOpenFileHelper = OpenFileHelper(this)
|
||||||
rootView?.findViewById<View>(R.id.browse_button)?.setOnClickListener { view ->
|
rootView?.findViewById<View>(R.id.open_database_button)?.setOnClickListener { view ->
|
||||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
@@ -132,7 +142,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
var error = verifyPassword() || verifyFile()
|
var error = verifyPassword() || verifyFile()
|
||||||
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
if (!passwordCheckBox!!.isChecked && !keyFileCheckBox!!.isChecked) {
|
||||||
error = true
|
error = true
|
||||||
showNoKeyConfirmationDialog()
|
if (allowNoMasterKey)
|
||||||
|
showNoKeyConfirmationDialog()
|
||||||
|
else {
|
||||||
|
passwordTextInputLayout?.error = getString(R.string.error_disallow_no_credentials)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!error) {
|
if (!error) {
|
||||||
mListener?.onAssignKeyDialogPositiveClick(
|
mListener?.onAssignKeyDialogPositiveClick(
|
||||||
@@ -193,6 +207,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
showEmptyPasswordConfirmationDialog()
|
showEmptyPasswordConfirmationDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +238,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
this@AssignMasterKeyDialogFragment.dismiss()
|
this@AssignMasterKeyDialogFragment.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
builder.create().show()
|
builder.create().show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,7 +253,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
keyFileCheckBox!!.isChecked, mKeyFile)
|
keyFileCheckBox!!.isChecked, mKeyFile)
|
||||||
this@AssignMasterKeyDialogFragment.dismiss()
|
this@AssignMasterKeyDialogFragment.dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
builder.create().show()
|
builder.create().show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,4 +270,17 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val ALLOW_NO_MASTER_KEY_ARG = "ALLOW_NO_MASTER_KEY_ARG"
|
||||||
|
|
||||||
|
fun getInstance(allowNoMasterKey: Boolean): AssignMasterKeyDialogFragment {
|
||||||
|
val fragment = AssignMasterKeyDialogFragment()
|
||||||
|
val args = Bundle()
|
||||||
|
args.putBoolean(ALLOW_NO_MASTER_KEY_ARG, allowNoMasterKey)
|
||||||
|
fragment.arguments = args
|
||||||
|
return fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -36,7 +36,7 @@ class BrowserDialogFragment : DialogFragment() {
|
|||||||
// Get the layout inflater
|
// Get the layout inflater
|
||||||
val root = activity.layoutInflater.inflate(R.layout.fragment_browser_install, null)
|
val root = activity.layoutInflater.inflate(R.layout.fragment_browser_install, null)
|
||||||
builder.setView(root)
|
builder.setView(root)
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
|
||||||
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
|
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
|
||||||
textDescription.text = getString(R.string.file_manager_install_description)
|
textDescription.text = getString(R.string.file_manager_install_description)
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +1,54 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.PreferenceManager
|
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
class ReadOnlyDialog : DialogFragment() {
|
class DuplicateUuidDialog : DialogFragment() {
|
||||||
|
|
||||||
|
var positiveAction: (() -> Unit)? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
// Use the Builder class for convenient dialog construction
|
// Use the Builder class for convenient dialog construction
|
||||||
val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
|
val builder = androidx.appcompat.app.AlertDialog.Builder(activity).apply {
|
||||||
|
val message = getString(R.string.contains_duplicate_uuid) +
|
||||||
var warning = getString(R.string.read_only_warning)
|
"\n\n" + getString(R.string.contains_duplicate_uuid_procedure)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
setMessage(message)
|
||||||
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
|
setPositiveButton(getString(android.R.string.ok)) { _, _ ->
|
||||||
|
positiveAction?.invoke()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
setNegativeButton(getString(android.R.string.cancel)) { _, _ -> dismiss() }
|
||||||
}
|
}
|
||||||
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
|
// Create the AlertDialog object and return it
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
return super.onCreateDialog(savedInstanceState)
|
return super.onCreateDialog(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
this.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -114,7 +114,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
mListener?.cancelPassword(bundle)
|
mListener?.cancelPassword(bundle)
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
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.CREATION
|
||||||
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
|
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.GroupVersioned
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.element.PwIcon
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
|
|
||||||
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
|
class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener {
|
||||||
@@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
|
|
||||||
private var editGroupDialogAction: EditGroupDialogAction? = null
|
private var editGroupDialogAction: EditGroupDialogAction? = null
|
||||||
private var nameGroup: String? = null
|
private var nameGroup: String? = null
|
||||||
private var iconGroup: PwIcon? = null
|
private var iconGroup: IconImage? = null
|
||||||
|
|
||||||
private var nameTextLayoutView: TextInputLayout? = null
|
private var nameTextLayoutView: TextInputLayout? = null
|
||||||
private var nameTextView: TextView? = null
|
private var nameTextView: TextView? = null
|
||||||
@@ -122,7 +122,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
builder.setView(root)
|
builder.setView(root)
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
editGroupListener?.cancelEditGroup(
|
editGroupListener?.cancelEditGroup(
|
||||||
editGroupDialogAction,
|
editGroupDialogAction,
|
||||||
nameTextView?.text?.toString(),
|
nameTextView?.text?.toString(),
|
||||||
@@ -186,8 +186,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EditGroupListener {
|
interface EditGroupListener {
|
||||||
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
|
fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
|
||||||
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?)
|
fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -206,7 +206,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(group: GroupVersioned): GroupEditDialogFragment {
|
fun build(group: Group): GroupEditDialogFragment {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(KEY_NAME, group.title)
|
bundle.putString(KEY_NAME, group.title)
|
||||||
bundle.putParcelable(KEY_ICON, group.icon)
|
bundle.putParcelable(KEY_ICON, group.icon)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -35,7 +35,7 @@ import android.widget.GridView
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
import com.kunzisoft.keepass.database.element.PwIconStandard
|
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||||
import com.kunzisoft.keepass.icons.IconPack
|
import com.kunzisoft.keepass.icons.IconPack
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||||
|
|
||||||
@@ -72,12 +72,12 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
currIconGridView.setOnItemClickListener { _, _, position, _ ->
|
currIconGridView.setOnItemClickListener { _, _, position, _ ->
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position))
|
bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position))
|
||||||
iconPickerListener?.iconPicked(bundle)
|
iconPickerListener?.iconPicked(bundle)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setNegativeButton(R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog?.cancel() }
|
builder.setNegativeButton(android.R.string.cancel) { _, _ -> this@IconPickerDialogFragment.dialog?.cancel() }
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ class IconPickerDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD"
|
private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD"
|
||||||
|
|
||||||
fun getIconStandardFromBundle(bundle: Bundle): PwIconStandard? {
|
fun getIconStandardFromBundle(bundle: Bundle): IconImageStandard? {
|
||||||
return bundle.getParcelable(KEY_ICON_STANDARD)
|
return bundle.getParcelable(KEY_ICON_STANDARD)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -35,7 +35,7 @@ class PasswordEncodingDialogFragment : DialogFragment() {
|
|||||||
val builder = AlertDialog.Builder(activity)
|
val builder = AlertDialog.Builder(activity)
|
||||||
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
|
builder.setMessage(activity.getString(R.string.warning_password_encoding)).setTitle(R.string.warning)
|
||||||
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
|
builder.setPositiveButton(android.R.string.ok, positiveButtonClickListener)
|
||||||
builder.setNegativeButton(R.string.cancel) { dialog, _ -> dialog.cancel() }
|
builder.setNegativeButton(android.R.string.cancel) { dialog, _ -> dialog.cancel() }
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|||||||
@@ -0,0 +1,401 @@
|
|||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
@@ -29,7 +29,7 @@ import android.view.View
|
|||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.RadioGroup
|
import android.widget.RadioGroup
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||||
|
|
||||||
class SortDialogFragment : DialogFragment() {
|
class SortDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ class SortDialogFragment : DialogFragment() {
|
|||||||
// Add action buttons
|
// Add action buttons
|
||||||
.setPositiveButton(android.R.string.ok
|
.setPositiveButton(android.R.string.ok
|
||||||
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) }
|
) { _, _ -> mListener?.onSortSelected(mSortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom) }
|
||||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
|
|
||||||
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
|
val ascendingView = rootView.findViewById<CompoundButton>(R.id.sort_selection_ascending)
|
||||||
// Check if is ascending or descending
|
// Check if is ascending or descending
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|||||||
@@ -1,7 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.activities.helpers
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
@@ -11,10 +30,10 @@ object EntrySelectionHelper {
|
|||||||
private const val EXTRA_ENTRY_SELECTION_MODE = "com.kunzisoft.keepass.extra.ENTRY_SELECTION_MODE"
|
private const val EXTRA_ENTRY_SELECTION_MODE = "com.kunzisoft.keepass.extra.ENTRY_SELECTION_MODE"
|
||||||
private const val DEFAULT_ENTRY_SELECTION_MODE = false
|
private const val DEFAULT_ENTRY_SELECTION_MODE = false
|
||||||
|
|
||||||
fun startActivityForEntrySelection(activity: Activity, intent: Intent) {
|
fun startActivityForEntrySelection(context: Context, intent: Intent) {
|
||||||
addEntrySelectionModeExtraInIntent(intent)
|
addEntrySelectionModeExtraInIntent(intent)
|
||||||
// only to avoid visible flickering when redirecting
|
// only to avoid visible flickering when redirecting
|
||||||
activity.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addEntrySelectionModeExtraInIntent(intent: Intent) {
|
fun addEntrySelectionModeExtraInIntent(intent: Intent) {
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.helpers
|
package com.kunzisoft.keepass.activities.helpers
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -26,10 +27,10 @@ import android.content.Intent
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ class OpenFileHelper {
|
|||||||
private var fragment: Fragment? = null
|
private var fragment: Fragment? = null
|
||||||
|
|
||||||
val openFileOnClickViewListener: OpenFileOnClickViewListener
|
val openFileOnClickViewListener: OpenFileOnClickViewListener
|
||||||
get() = OpenFileOnClickViewListener(null)
|
get() = OpenFileOnClickViewListener()
|
||||||
|
|
||||||
constructor(context: Activity) {
|
constructor(context: Activity) {
|
||||||
this.activity = context
|
this.activity = context
|
||||||
@@ -51,7 +52,7 @@ class OpenFileHelper {
|
|||||||
this.fragment = context
|
this.fragment = context
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class OpenFileOnClickViewListener(private val dataUri: (() -> Uri?)?) : View.OnClickListener {
|
inner class OpenFileOnClickViewListener : View.OnClickListener {
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
try {
|
try {
|
||||||
@@ -62,58 +63,50 @@ class OpenFileHelper {
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Enable to start the file picker activity", e)
|
Log.e(TAG, "Enable to start the file picker activity", e)
|
||||||
|
// Open browser dialog
|
||||||
// Open File picker if can't open activity
|
if (lookForOpenIntentsFilePicker())
|
||||||
if (lookForOpenIntentsFilePicker(dataUri?.invoke()))
|
|
||||||
showBrowserDialog()
|
showBrowserDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
private fun openActivityWithActionOpenDocument() {
|
private fun openActivityWithActionOpenDocument() {
|
||||||
val i = Intent(ACTION_OPEN_DOCUMENT)
|
val intentOpenDocument = Intent(APP_ACTION_OPEN_DOCUMENT).apply {
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
i.type = "*/*"
|
type = "*/*"
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||||
i.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
} else {
|
|
||||||
i.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
}
|
}
|
||||||
if (fragment != null)
|
if (fragment != null)
|
||||||
fragment?.startActivityForResult(i, OPEN_DOC)
|
fragment?.startActivityForResult(intentOpenDocument, OPEN_DOC)
|
||||||
else
|
else
|
||||||
activity?.startActivityForResult(i, OPEN_DOC)
|
activity?.startActivityForResult(intentOpenDocument, OPEN_DOC)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
private fun openActivityWithActionGetContent() {
|
private fun openActivityWithActionGetContent() {
|
||||||
val i = Intent(Intent.ACTION_GET_CONTENT)
|
val intentGetContent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
i.type = "*/*"
|
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)
|
if (fragment != null)
|
||||||
fragment?.startActivityForResult(i, GET_CONTENT)
|
fragment?.startActivityForResult(intentGetContent, GET_CONTENT)
|
||||||
else
|
else
|
||||||
activity?.startActivityForResult(i, GET_CONTENT)
|
activity?.startActivityForResult(intentGetContent, GET_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOpenFileOnClickViewListener(dataUri: () -> Uri?): OpenFileOnClickViewListener {
|
private fun lookForOpenIntentsFilePicker(): Boolean {
|
||||||
return OpenFileOnClickViewListener(dataUri)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun lookForOpenIntentsFilePicker(dataUri: Uri?): Boolean {
|
|
||||||
var showBrowser = false
|
var showBrowser = false
|
||||||
try {
|
try {
|
||||||
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
|
if (isIntentAvailable(activity!!, OPEN_INTENTS_FILE_BROWSE)) {
|
||||||
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
|
val intent = Intent(OPEN_INTENTS_FILE_BROWSE)
|
||||||
// Get file path parent if possible
|
|
||||||
if (dataUri != null
|
|
||||||
&& dataUri.toString().isNotEmpty()
|
|
||||||
&& dataUri.scheme == "file") {
|
|
||||||
intent.data = dataUri
|
|
||||||
} else {
|
|
||||||
Log.w(javaClass.name, "Unable to read the URI")
|
|
||||||
}
|
|
||||||
if (fragment != null)
|
if (fragment != null)
|
||||||
fragment?.startActivityForResult(intent, FILE_BROWSE)
|
fragment?.startActivityForResult(intent, FILE_BROWSE)
|
||||||
else
|
else
|
||||||
@@ -190,22 +183,19 @@ class OpenFileHelper {
|
|||||||
GET_CONTENT, OPEN_DOC -> {
|
GET_CONTENT, OPEN_DOC -> {
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
var uri = data.data
|
val uri = data.data
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
try {
|
try {
|
||||||
// try to persist read and write permissions
|
// try to persist read and write permissions
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
activity?.contentResolver?.apply {
|
activity?.contentResolver?.apply {
|
||||||
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
takePersistableUriPermission(uri!!, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// nop
|
// nop
|
||||||
}
|
}
|
||||||
if (requestCode == GET_CONTENT) {
|
|
||||||
uri = UriUtil.translateUri(activity!!, uri)
|
|
||||||
}
|
|
||||||
keyFileCallback?.invoke(uri)
|
keyFileCallback?.invoke(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,15 +210,10 @@ class OpenFileHelper {
|
|||||||
|
|
||||||
private const val TAG = "OpenFileHelper"
|
private const val TAG = "OpenFileHelper"
|
||||||
|
|
||||||
private var ACTION_OPEN_DOCUMENT: String
|
private var APP_ACTION_OPEN_DOCUMENT: String = try {
|
||||||
|
Intent::class.java.getField("ACTION_OPEN_DOCUMENT").get(null) as String
|
||||||
init {
|
} catch (e: Exception) {
|
||||||
ACTION_OPEN_DOCUMENT = try {
|
"android.intent.action.OPEN_DOCUMENT"
|
||||||
val openDocument = Intent::class.java.getField("ACTION_OPEN_DOCUMENT")
|
|
||||||
openDocument.get(null) as String
|
|
||||||
} catch (e: Exception) {
|
|
||||||
"android.intent.action.OPEN_DOCUMENT"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
|
const val OPEN_INTENTS_FILE_BROWSE = "org.openintents.action.PICK_FILE"
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.activities.helpers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.lock
|
package com.kunzisoft.keepass.activities.lock
|
||||||
@@ -32,9 +32,11 @@ import android.view.ViewGroup
|
|||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
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.database.element.Database
|
||||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||||
|
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
||||||
@@ -62,6 +64,10 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
return field || mSelectionMode
|
return field || mSelectionMode
|
||||||
}
|
}
|
||||||
protected var mSelectionMode: Boolean = false
|
protected var mSelectionMode: Boolean = false
|
||||||
|
protected var mAutoSaveEnable: Boolean = true
|
||||||
|
|
||||||
|
var mProgressDialogThread: ProgressDialogThread? = null
|
||||||
|
private set
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -85,6 +91,8 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
|
|
||||||
mExitLock = false
|
mExitLock = false
|
||||||
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
|
mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent)
|
||||||
|
|
||||||
|
mProgressDialogThread = ProgressDialogThread(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
@@ -100,8 +108,13 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
mProgressDialogThread?.registerProgressTask()
|
||||||
|
|
||||||
// To refresh when back to normal workflow from selection workflow
|
// To refresh when back to normal workflow from selection workflow
|
||||||
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
|
mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent)
|
||||||
|
mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this)
|
||||||
|
|
||||||
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
if (mTimeoutEnable) {
|
if (mTimeoutEnable) {
|
||||||
// End activity if database not loaded
|
// End activity if database not loaded
|
||||||
@@ -118,8 +131,6 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
if (!mExitLock)
|
if (!mExitLock)
|
||||||
TimeoutHelper.recordTime(this)
|
TimeoutHelper.recordTime(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateOptionsMenu()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@@ -129,6 +140,8 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
|
mProgressDialogThread?.unregisterProgressTask()
|
||||||
|
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
if (mTimeoutEnable) {
|
if (mTimeoutEnable) {
|
||||||
@@ -199,6 +212,9 @@ fun Activity.lock() {
|
|||||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||||
MagikIME.removeEntry(this)
|
MagikIME.removeEntry(this)
|
||||||
|
|
||||||
|
// Stop the notification service
|
||||||
|
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||||
|
|
||||||
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
|
Log.i(Activity::class.java.name, "Shutdown " + localClassName +
|
||||||
" after inactivity or manual lock")
|
" after inactivity or manual lock")
|
||||||
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
|
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply {
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.stylish
|
package com.kunzisoft.keepass.activities.stylish
|
||||||
@@ -61,6 +61,7 @@ object Stylish {
|
|||||||
|
|
||||||
return when (themeString) {
|
return when (themeString) {
|
||||||
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
|
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_dark) -> R.style.KeepassDXStyle_Dark
|
||||||
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
|
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
|
||||||
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red
|
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red
|
||||||
|
|||||||
@@ -1,38 +1,63 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.stylish
|
package com.kunzisoft.keepass.activities.stylish
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.util.Log
|
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() {
|
abstract class StylishActivity : AppCompatActivity() {
|
||||||
|
|
||||||
@StyleRes
|
@StyleRes
|
||||||
private var themeId: Int = 0
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
this.themeId = Stylish.getThemeId(this)
|
this.themeId = Stylish.getThemeId(this)
|
||||||
setTheme(themeId)
|
setTheme(themeId)
|
||||||
|
|
||||||
|
// Several gingerbread devices have problems with FLAG_SECURE
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.stylish
|
package com.kunzisoft.keepass.activities.stylish
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.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
|
||||||
|
|
||||||
|
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 (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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder {
|
||||||
|
return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: EntryHistoryViewHolder, position: Int) {
|
||||||
|
val entryHistory = entryHistoryList[position]
|
||||||
|
|
||||||
|
holder.lastModifiedView.text = entryHistory.lastModificationTime.getDateTimeString(context.resources)
|
||||||
|
holder.titleView.text = entryHistory.title
|
||||||
|
holder.usernameView.text = entryHistory.username
|
||||||
|
holder.urlView.text = entryHistory.url
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener {
|
||||||
|
onItemClickListener?.invoke(entryHistory, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return entryHistoryList.size
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
entryHistoryList.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class EntryHistoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|
||||||
|
var lastModifiedView: TextView = itemView.findViewById(R.id.entry_history_last_modified)
|
||||||
|
var titleView: TextView = itemView.findViewById(R.id.entry_history_title)
|
||||||
|
var usernameView: TextView = itemView.findViewById(R.id.entry_history_username)
|
||||||
|
var urlView: TextView = itemView.findViewById(R.id.entry_history_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@@ -15,10 +34,9 @@ import java.util.ArrayList
|
|||||||
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
|
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
|
||||||
|
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
var fields: MutableList<Field> = ArrayList()
|
private var fields: MutableList<Field> = ArrayList()
|
||||||
var onItemClickListener: OnItemClickListener? = null
|
var onItemClickListener: OnItemClickListener? = null
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
|
||||||
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
|
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
|
||||||
return FieldViewHolder(view)
|
return FieldViewHolder(view)
|
||||||
@@ -34,6 +52,11 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
|
|||||||
return fields.size
|
return fields.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setFields(fieldsToAdd: List<Field>) {
|
||||||
|
fields.clear()
|
||||||
|
fields.addAll(fieldsToAdd)
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
fields.clear()
|
fields.clear()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.adapters
|
package com.kunzisoft.keepass.adapters
|
||||||
|
|||||||
@@ -1,50 +1,59 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.adapters
|
package com.kunzisoft.keepass.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.recyclerview.widget.SortedList
|
import android.graphics.Paint
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.recyclerview.widget.SortedListAdapterCallback
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.*
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
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.R
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.*
|
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.Type
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
import com.kunzisoft.keepass.view.strikeOut
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class NodeAdapter
|
class NodeAdapter
|
||||||
/**
|
/**
|
||||||
* Create node list adapter with contextMenu or not
|
* Create node list adapter with contextMenu or not
|
||||||
* @param context Context to use
|
* @param context Context to use
|
||||||
*/
|
*/
|
||||||
(private val context: Context, private val menuInflater: MenuInflater)
|
(private val context: Context)
|
||||||
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
|
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
|
||||||
|
|
||||||
private val nodeSortedList: SortedList<NodeVersioned>
|
private val nodeSortedList: SortedList<Node>
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
|
||||||
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
|
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
|
||||||
@@ -60,12 +69,10 @@ class NodeAdapter
|
|||||||
private var recycleBinBottomSort: Boolean = true
|
private var recycleBinBottomSort: Boolean = true
|
||||||
private var showUserNames: Boolean = true
|
private var showUserNames: Boolean = true
|
||||||
private var showNumberEntries: Boolean = true
|
private var showNumberEntries: Boolean = true
|
||||||
|
private var entryFilters = arrayOf<Group.ChildFilter>()
|
||||||
|
|
||||||
|
private var actionNodesList = LinkedList<Node>()
|
||||||
private var nodeClickCallback: NodeClickCallback? = null
|
private var nodeClickCallback: NodeClickCallback? = null
|
||||||
private var nodeMenuListener: NodeMenuListener? = null
|
|
||||||
private var activateContextMenu: Boolean = false
|
|
||||||
private var readOnly: Boolean = false
|
|
||||||
private var isASearchResult: Boolean = false
|
|
||||||
|
|
||||||
private val mDatabase: Database
|
private val mDatabase: Database
|
||||||
|
|
||||||
@@ -81,22 +88,19 @@ class NodeAdapter
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
assignPreferences()
|
assignPreferences()
|
||||||
this.activateContextMenu = false
|
|
||||||
this.readOnly = false
|
|
||||||
this.isASearchResult = false
|
|
||||||
|
|
||||||
this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback<NodeVersioned>(this) {
|
this.nodeSortedList = SortedList(Node::class.java, object : SortedListAdapterCallback<Node>(this) {
|
||||||
override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int {
|
override fun compare(item1: Node, item2: Node): Int {
|
||||||
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
|
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean {
|
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
|
||||||
return oldItem.type == newItem.type
|
return oldItem.type == newItem.type
|
||||||
&& oldItem.title == newItem.title
|
&& oldItem.title == newItem.title
|
||||||
&& oldItem.icon == newItem.icon
|
&& oldItem.icon == newItem.icon
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun areItemsTheSame(item1: NodeVersioned, item2: NodeVersioned): Boolean {
|
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
|
||||||
return item1 == item2
|
return item1 == item2
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -114,18 +118,6 @@ class NodeAdapter
|
|||||||
taTextColor.recycle()
|
taTextColor.recycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setReadOnly(readOnly: Boolean) {
|
|
||||||
this.readOnly = readOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setIsASearchResult(isASearchResult: Boolean) {
|
|
||||||
this.isASearchResult = isASearchResult
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setActivateContextMenu(activate: Boolean) {
|
|
||||||
this.activateContextMenu = activate
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun assignPreferences() {
|
private fun assignPreferences() {
|
||||||
this.prefTextSize = PreferencesUtil.getListTextSize(context)
|
this.prefTextSize = PreferencesUtil.getListTextSize(context)
|
||||||
this.infoTextSize = context.resources.getDimension(R.dimen.list_medium_size_default) * prefTextSize
|
this.infoTextSize = context.resources.getDimension(R.dimen.list_medium_size_default) * prefTextSize
|
||||||
@@ -140,6 +132,8 @@ class NodeAdapter
|
|||||||
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
|
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
|
||||||
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
|
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
|
||||||
|
|
||||||
|
this.entryFilters = Group.ChildFilter.getDefaults(context)
|
||||||
|
|
||||||
// Reinit textSize for all view type
|
// Reinit textSize for all view type
|
||||||
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
|
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
|
||||||
}
|
}
|
||||||
@@ -147,18 +141,19 @@ class NodeAdapter
|
|||||||
/**
|
/**
|
||||||
* Rebuild the list by clear and build children from the group
|
* Rebuild the list by clear and build children from the group
|
||||||
*/
|
*/
|
||||||
fun rebuildList(group: GroupVersioned) {
|
fun rebuildList(group: Group) {
|
||||||
this.nodeSortedList.clear()
|
this.nodeSortedList.clear()
|
||||||
assignPreferences()
|
assignPreferences()
|
||||||
try {
|
try {
|
||||||
this.nodeSortedList.addAll(group.getChildren())
|
this.nodeSortedList.addAll(group.getChildren(*entryFilters))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Can't add node elements to the list", e)
|
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()
|
Toast.makeText(context, "Can't add node elements to the list : " + e.message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contains(node: NodeVersioned): Boolean {
|
fun contains(node: Node): Boolean {
|
||||||
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
|
return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,23 +161,55 @@ class NodeAdapter
|
|||||||
* Add a node to the list
|
* Add a node to the list
|
||||||
* @param node Node to add
|
* @param node Node to add
|
||||||
*/
|
*/
|
||||||
fun addNode(node: NodeVersioned) {
|
fun addNode(node: Node) {
|
||||||
nodeSortedList.add(node)
|
nodeSortedList.add(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add nodes to the list
|
||||||
|
* @param nodes Nodes to add
|
||||||
|
*/
|
||||||
|
fun addNodes(nodes: List<Node>) {
|
||||||
|
nodeSortedList.addAll(nodes)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a node in the list
|
* Remove a node in the list
|
||||||
* @param node Node to delete
|
* @param node Node to delete
|
||||||
*/
|
*/
|
||||||
fun removeNode(node: NodeVersioned) {
|
fun removeNode(node: Node) {
|
||||||
nodeSortedList.remove(node)
|
nodeSortedList.remove(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove nodes in the list
|
||||||
|
* @param nodes Nodes to delete
|
||||||
|
*/
|
||||||
|
fun removeNodes(nodes: List<Node>) {
|
||||||
|
nodes.forEach { node ->
|
||||||
|
nodeSortedList.remove(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a node at [position] in the list
|
* Remove a node at [position] in the list
|
||||||
*/
|
*/
|
||||||
fun removeNodeAt(position: Int) {
|
fun removeNodeAt(position: Int) {
|
||||||
nodeSortedList.removeItemAt(position)
|
nodeSortedList.removeItemAt(position)
|
||||||
|
// Refresh all the next items
|
||||||
|
notifyItemRangeChanged(position, nodeSortedList.size() - position)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove nodes in the list by [positions]
|
||||||
|
* Note : algorithm remove the higher position at each iteration
|
||||||
|
*/
|
||||||
|
fun removeNodesAt(positions: IntArray) {
|
||||||
|
val positionsSortDescending = positions.toMutableList()
|
||||||
|
positionsSortDescending.sortDescending()
|
||||||
|
positionsSortDescending.forEach {
|
||||||
|
removeNodeAt(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,13 +217,47 @@ class NodeAdapter
|
|||||||
* @param oldNode Node before the update
|
* @param oldNode Node before the update
|
||||||
* @param newNode Node after the update
|
* @param newNode Node after the update
|
||||||
*/
|
*/
|
||||||
fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) {
|
fun updateNode(oldNode: Node, newNode: Node) {
|
||||||
nodeSortedList.beginBatchedUpdates()
|
nodeSortedList.beginBatchedUpdates()
|
||||||
nodeSortedList.remove(oldNode)
|
nodeSortedList.remove(oldNode)
|
||||||
nodeSortedList.add(newNode)
|
nodeSortedList.add(newNode)
|
||||||
nodeSortedList.endBatchedUpdates()
|
nodeSortedList.endBatchedUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update nodes in the list
|
||||||
|
* @param oldNodes Nodes before the update
|
||||||
|
* @param newNodes Node after the update
|
||||||
|
*/
|
||||||
|
fun updateNodes(oldNodes: List<Node>, newNodes: List<Node>) {
|
||||||
|
nodeSortedList.beginBatchedUpdates()
|
||||||
|
oldNodes.forEach { oldNode ->
|
||||||
|
nodeSortedList.remove(oldNode)
|
||||||
|
}
|
||||||
|
nodeSortedList.addAll(newNodes)
|
||||||
|
nodeSortedList.endBatchedUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyNodeChanged(node: Node) {
|
||||||
|
notifyItemChanged(nodeSortedList.indexOf(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setActionNodes(actionNodes: List<Node>) {
|
||||||
|
this.actionNodesList.apply {
|
||||||
|
clear()
|
||||||
|
addAll(actionNodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unselectActionNodes() {
|
||||||
|
actionNodesList.forEach {
|
||||||
|
notifyItemChanged(nodeSortedList.indexOf(it))
|
||||||
|
}
|
||||||
|
this.actionNodesList.apply {
|
||||||
|
clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify a change sort of the list
|
* Notify a change sort of the list
|
||||||
*/
|
*/
|
||||||
@@ -234,46 +295,45 @@ class NodeAdapter
|
|||||||
width = iconSize.toInt()
|
width = iconSize.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assign text
|
// Assign text
|
||||||
holder.text.apply {
|
holder.text.apply {
|
||||||
text = subNode.title
|
text = subNode.title
|
||||||
setTextSize(textSizeUnit, infoTextSize)
|
setTextSize(textSizeUnit, infoTextSize)
|
||||||
|
strikeOut(subNode.isCurrentlyExpires)
|
||||||
}
|
}
|
||||||
// Assign click
|
|
||||||
holder.container.setOnClickListener { nodeClickCallback?.onNodeClick(subNode) }
|
|
||||||
// Context menu
|
|
||||||
if (activateContextMenu) {
|
|
||||||
holder.container.setOnCreateContextMenuListener(
|
|
||||||
ContextMenuBuilder(menuInflater, subNode, readOnly, isASearchResult, nodeMenuListener))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add subText with username
|
// Add subText with username
|
||||||
holder.subText.apply {
|
holder.subText.apply {
|
||||||
text = ""
|
text = ""
|
||||||
|
strikeOut(subNode.isCurrentlyExpires)
|
||||||
visibility = View.GONE
|
visibility = View.GONE
|
||||||
if (subNode.type == Type.ENTRY) {
|
}
|
||||||
val entry = subNode as EntryVersioned
|
|
||||||
|
|
||||||
mDatabase.startManageEntry(entry)
|
// Specific elements for entry
|
||||||
|
if (subNode.type == Type.ENTRY) {
|
||||||
holder.text.text = entry.getVisualTitle()
|
val entry = subNode as Entry
|
||||||
|
mDatabase.startManageEntry(entry)
|
||||||
|
|
||||||
|
holder.text.text = entry.getVisualTitle()
|
||||||
|
holder.subText.apply {
|
||||||
val username = entry.username
|
val username = entry.username
|
||||||
if (showUserNames && username.isNotEmpty()) {
|
if (showUserNames && username.isNotEmpty()) {
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
text = username
|
text = username
|
||||||
setTextSize(textSizeUnit, subtextSize)
|
setTextSize(textSizeUnit, subtextSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
mDatabase.stopManageEntry(entry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mDatabase.stopManageEntry(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add number of entries in groups
|
// Add number of entries in groups
|
||||||
if (subNode.type == Type.GROUP) {
|
if (subNode.type == Type.GROUP) {
|
||||||
if (showNumberEntries) {
|
if (showNumberEntries) {
|
||||||
holder.numberChildren?.apply {
|
holder.numberChildren?.apply {
|
||||||
text = (subNode as GroupVersioned).getChildEntries(true).size.toString()
|
text = (subNode as Group)
|
||||||
|
.getChildEntries(*entryFilters)
|
||||||
|
.size.toString()
|
||||||
setTextSize(textSizeUnit, numberChildrenTextSize)
|
setTextSize(textSizeUnit, numberChildrenTextSize)
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
@@ -281,8 +341,19 @@ class NodeAdapter
|
|||||||
holder.numberChildren?.visibility = View.GONE
|
holder.numberChildren?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign click
|
||||||
|
holder.container.setOnClickListener {
|
||||||
|
nodeClickCallback?.onNodeClick(subNode)
|
||||||
|
}
|
||||||
|
holder.container.setOnLongClickListener {
|
||||||
|
nodeClickCallback?.onNodeLongClick(subNode) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.container.isSelected = actionNodesList.contains(subNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return nodeSortedList.size()
|
return nodeSortedList.size()
|
||||||
}
|
}
|
||||||
@@ -294,103 +365,12 @@ class NodeAdapter
|
|||||||
this.nodeClickCallback = nodeClickCallback
|
this.nodeClickCallback = nodeClickCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign a listener when an element of menu is clicked
|
|
||||||
*/
|
|
||||||
fun setNodeMenuListener(nodeMenuListener: NodeMenuListener?) {
|
|
||||||
this.nodeMenuListener = nodeMenuListener
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback listener to redefine to do an action when a node is click
|
* Callback listener to redefine to do an action when a node is click
|
||||||
*/
|
*/
|
||||||
interface NodeClickCallback {
|
interface NodeClickCallback {
|
||||||
fun onNodeClick(node: NodeVersioned)
|
fun onNodeClick(node: Node)
|
||||||
}
|
fun onNodeLongClick(node: Node): Boolean
|
||||||
|
|
||||||
/**
|
|
||||||
* Menu listener to redefine to do an action in menu
|
|
||||||
*/
|
|
||||||
interface NodeMenuListener {
|
|
||||||
fun onOpenMenuClick(node: NodeVersioned): Boolean
|
|
||||||
fun onEditMenuClick(node: NodeVersioned): Boolean
|
|
||||||
fun onCopyMenuClick(node: NodeVersioned): Boolean
|
|
||||||
fun onMoveMenuClick(node: NodeVersioned): Boolean
|
|
||||||
fun onDeleteMenuClick(node: NodeVersioned): Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility class for menu listener
|
|
||||||
*/
|
|
||||||
private class ContextMenuBuilder(val menuInflater: MenuInflater,
|
|
||||||
val node: NodeVersioned,
|
|
||||||
val readOnly: Boolean,
|
|
||||||
val isASearchResult: Boolean,
|
|
||||||
val menuListener: NodeMenuListener?)
|
|
||||||
: View.OnCreateContextMenuListener {
|
|
||||||
|
|
||||||
private val mOnMyActionClickListener = MenuItem.OnMenuItemClickListener { item ->
|
|
||||||
if (menuListener == null)
|
|
||||||
return@OnMenuItemClickListener false
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.menu_open -> menuListener.onOpenMenuClick(node)
|
|
||||||
R.id.menu_edit -> menuListener.onEditMenuClick(node)
|
|
||||||
R.id.menu_copy -> menuListener.onCopyMenuClick(node)
|
|
||||||
R.id.menu_move -> menuListener.onMoveMenuClick(node)
|
|
||||||
R.id.menu_delete -> menuListener.onDeleteMenuClick(node)
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateContextMenu(contextMenu: ContextMenu?,
|
|
||||||
view: View?,
|
|
||||||
contextMenuInfo: ContextMenu.ContextMenuInfo?) {
|
|
||||||
menuInflater.inflate(R.menu.node_menu, contextMenu)
|
|
||||||
|
|
||||||
// Opening
|
|
||||||
var menuItem = contextMenu?.findItem(R.id.menu_open)
|
|
||||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
|
||||||
|
|
||||||
val database = Database.getInstance()
|
|
||||||
|
|
||||||
// Edition
|
|
||||||
if (readOnly || node == database.recycleBin) {
|
|
||||||
contextMenu?.removeItem(R.id.menu_edit)
|
|
||||||
} else {
|
|
||||||
menuItem = contextMenu?.findItem(R.id.menu_edit)
|
|
||||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy (not for group)
|
|
||||||
if (readOnly
|
|
||||||
|| isASearchResult
|
|
||||||
|| node == database.recycleBin
|
|
||||||
|| node.type == Type.GROUP) {
|
|
||||||
// TODO COPY For Group
|
|
||||||
contextMenu?.removeItem(R.id.menu_copy)
|
|
||||||
} else {
|
|
||||||
menuItem = contextMenu?.findItem(R.id.menu_copy)
|
|
||||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move
|
|
||||||
if (readOnly
|
|
||||||
|| isASearchResult
|
|
||||||
|| node == database.recycleBin) {
|
|
||||||
contextMenu?.removeItem(R.id.menu_move)
|
|
||||||
} else {
|
|
||||||
menuItem = contextMenu?.findItem(R.id.menu_move)
|
|
||||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deletion
|
|
||||||
if (readOnly || node == database.recycleBin) {
|
|
||||||
contextMenu?.removeItem(R.id.menu_delete)
|
|
||||||
} else {
|
|
||||||
menuItem = contextMenu?.findItem(R.id.menu_delete)
|
|
||||||
menuItem?.setOnMenuItemClickListener(mOnMyActionClickListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.adapters
|
package com.kunzisoft.keepass.adapters
|
||||||
@@ -22,22 +22,20 @@ package com.kunzisoft.keepass.adapters
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.cursoradapter.widget.CursorAdapter
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.database.cursor.EntryCursor
|
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.EntryVersioned
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.PwIcon
|
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import java.util.*
|
import com.kunzisoft.keepass.view.strikeOut
|
||||||
|
|
||||||
class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
class SearchEntryCursorAdapter(private val context: Context,
|
||||||
|
private val database: Database)
|
||||||
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
|
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
|
||||||
|
|
||||||
private val cursorInflater: LayoutInflater = context.getSystemService(
|
private val cursorInflater: LayoutInflater = context.getSystemService(
|
||||||
@@ -72,34 +70,31 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
|||||||
|
|
||||||
override fun bindView(view: View, context: Context, cursor: Cursor) {
|
override fun bindView(view: View, context: Context, cursor: Cursor) {
|
||||||
|
|
||||||
// Retrieve elements from cursor
|
database.getEntryFrom(cursor)?.let { currentEntry ->
|
||||||
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
|
val viewHolder = view.tag as ViewHolder
|
||||||
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))
|
|
||||||
|
|
||||||
val viewHolder = view.tag as ViewHolder
|
// Assign image
|
||||||
|
viewHolder.imageViewIcon?.assignDatabaseIcon(
|
||||||
|
database.drawFactory,
|
||||||
|
currentEntry.icon,
|
||||||
|
iconColor)
|
||||||
|
|
||||||
// Assign image
|
// Assign title
|
||||||
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
|
viewHolder.textViewTitle?.apply {
|
||||||
|
text = currentEntry.getVisualTitle()
|
||||||
|
strikeOut(currentEntry.isCurrentlyExpires)
|
||||||
|
}
|
||||||
|
|
||||||
// Assign title
|
// Assign subtitle
|
||||||
val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString())
|
viewHolder.textViewSubTitle?.apply {
|
||||||
viewHolder.textViewTitle?.text = showTitle
|
val entryUsername = currentEntry.username
|
||||||
if (displayUsername && username.isNotEmpty()) {
|
text = if (displayUsername && entryUsername.isNotEmpty()) {
|
||||||
viewHolder.textViewSubTitle?.text = String.format("(%s)", username)
|
String.format("(%s)", entryUsername)
|
||||||
} else {
|
} else {
|
||||||
viewHolder.textViewSubTitle?.text = ""
|
""
|
||||||
|
}
|
||||||
|
strikeOut(currentEntry.isCurrentlyExpires)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,11 +105,11 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
|
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
|
||||||
return database.searchEntries(constraint.toString())
|
return database.searchEntries(context, constraint.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getEntryFromPosition(position: Int): EntryVersioned? {
|
fun getEntryFromPosition(position: Int): Entry? {
|
||||||
var pwEntry: EntryVersioned? = null
|
var pwEntry: Entry? = null
|
||||||
|
|
||||||
val cursor = this.cursor
|
val cursor = this.cursor
|
||||||
if (cursor.moveToFirst() && cursor.move(position)) {
|
if (cursor.moveToFirst() && cursor.move(position)) {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.app
|
package com.kunzisoft.keepass.app
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|
||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
@@ -15,7 +36,33 @@ data class CipherDatabaseEntity(
|
|||||||
|
|
||||||
@ColumnInfo(name = "specs_parameters")
|
@ColumnInfo(name = "specs_parameters")
|
||||||
var specParameters: String
|
var specParameters: String
|
||||||
) {
|
): Parcelable {
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readString()!!,
|
||||||
|
parcel.readString()!!,
|
||||||
|
parcel.readString()!!)
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(databaseUri)
|
||||||
|
parcel.writeString(encryptedValue)
|
||||||
|
parcel.writeString(specParameters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<CipherDatabaseEntity> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): CipherDatabaseEntity {
|
||||||
|
return CipherDatabaseEntity(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<CipherDatabaseEntity?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.app.database
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|
||||||
import androidx.room.*
|
import androidx.room.*
|
||||||
@@ -19,7 +38,7 @@ interface FileDatabaseHistoryDao {
|
|||||||
@Delete
|
@Delete
|
||||||
fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int
|
fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int
|
||||||
|
|
||||||
@Query("REPLACE INTO file_database_history(keyfile_uri) VALUES(null)")
|
@Query("UPDATE file_database_history SET keyfile_uri=null")
|
||||||
fun deleteAllKeyFiles()
|
fun deleteAllKeyFiles()
|
||||||
|
|
||||||
@Query("DELETE FROM file_database_history")
|
@Query("DELETE FROM file_database_history")
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.app.database
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.autofill
|
package com.kunzisoft.keepass.autofill
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.autofill
|
package com.kunzisoft.keepass.autofill
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.autofill
|
package com.kunzisoft.keepass.autofill
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.autofill
|
package com.kunzisoft.keepass.autofill
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.backup
|
package com.kunzisoft.keepass.backup
|
||||||
|
|||||||
@@ -1,13 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
package com.kunzisoft.keepass.biometric
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.biometric.BiometricConstants
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
import androidx.biometric.BiometricPrompt
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
@@ -19,12 +41,12 @@ import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
|||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
class AdvancedUnlockedManager(var context: FragmentActivity,
|
class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||||
var databaseFileUri: Uri,
|
var databaseFileUri: Uri,
|
||||||
var advancedUnlockInfoView: AdvancedUnlockInfoView?,
|
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
|
||||||
var checkboxPasswordView: CompoundButton?,
|
private var checkboxPasswordView: CompoundButton?,
|
||||||
var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
|
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
|
||||||
var passwordView: TextView?,
|
var passwordView: TextView?,
|
||||||
var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
|
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
|
||||||
var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
|
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
|
||||||
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
|
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
|
||||||
|
|
||||||
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
|
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
|
||||||
@@ -34,65 +56,59 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
|
|
||||||
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
|
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
|
||||||
|
|
||||||
// fingerprint related code here
|
init {
|
||||||
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!!.isFingerprintInitialized) {
|
|
||||||
|
|
||||||
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context, this)
|
|
||||||
// callback for fingerprint findings
|
|
||||||
biometricUnlockDatabaseHelper?.setAuthenticationCallback(biometricCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a check listener to change fingerprint mode
|
// Add a check listener to change fingerprint mode
|
||||||
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
|
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
|
||||||
|
|
||||||
checkBiometricAvailability()
|
checkBiometricAvailability()
|
||||||
|
|
||||||
// Add old listener to enable the button, only be call here because of onCheckedChange bug
|
// Add old listener to enable the button, only be call here because of onCheckedChange bug
|
||||||
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
|
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
checkBiometricAvailability()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
/**
|
||||||
|
* Check biometric availability and change the current mode depending of device's state
|
||||||
|
*/
|
||||||
fun checkBiometricAvailability() {
|
fun checkBiometricAvailability() {
|
||||||
|
|
||||||
// fingerprint not supported (by API level or hardware) so keep option hidden
|
// biometric not supported (by API level or hardware) so keep option hidden
|
||||||
// or manually disable
|
// or manually disable
|
||||||
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate()
|
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate()
|
||||||
|
|
||||||
if (!PreferencesUtil.isBiometricUnlockEnable(context)
|
if (!PreferencesUtil.isBiometricUnlockEnable(context)
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||||
|
|
||||||
toggleMode(Mode.UNAVAILABLE)
|
toggleMode(Mode.UNAVAILABLE)
|
||||||
|
|
||||||
} else {
|
} 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) {
|
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
|
||||||
|
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
|
||||||
toggleMode(Mode.NOT_CONFIGURED)
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (checkboxPasswordView?.isChecked == true) {
|
// Check if fingerprint well init (be called the first time the fingerprint is configured
|
||||||
// listen for encryption
|
// and the activity still active)
|
||||||
toggleMode(Mode.STORE)
|
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)
|
||||||
} else {
|
} else {
|
||||||
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
|
if (checkboxPasswordView?.isChecked == true) {
|
||||||
|
// listen for encryption
|
||||||
// fingerprint available but no stored password found yet for this DB so show info don't listen
|
toggleMode(Mode.STORE_CREDENTIAL)
|
||||||
toggleMode( if (it) {
|
} else {
|
||||||
// listen for decryption
|
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
|
||||||
Mode.OPEN
|
// biometric available but no stored password found yet for this DB so show info don't listen
|
||||||
} else {
|
toggleMode( if (containsCipher) {
|
||||||
// wait for typing
|
// listen for decryption
|
||||||
Mode.WAIT_CREDENTIAL
|
Mode.EXTRACT_CREDENTIAL
|
||||||
})
|
} else {
|
||||||
|
// wait for typing
|
||||||
|
Mode.WAIT_CREDENTIAL
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,35 +122,41 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val biometricCallback = object : BiometricPrompt.AuthenticationCallback () {
|
private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
|
||||||
|
|
||||||
override fun onAuthenticationError(
|
override fun onAuthenticationError(
|
||||||
errorCode: Int,
|
errorCode: Int,
|
||||||
errString: CharSequence) {
|
errString: CharSequence) {
|
||||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
context.runOnUiThread {
|
||||||
setAdvancedUnlockedMessageView(errString.toString())
|
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||||
|
setAdvancedUnlockedMessageView(errString.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
override fun onAuthenticationFailed() {
|
||||||
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
context.runOnUiThread {
|
||||||
setAdvancedUnlockedMessageView(R.string.biometric_not_recognized)
|
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
||||||
|
setAdvancedUnlockedMessageView(R.string.biometric_not_recognized)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
when (biometricMode) {
|
context.runOnUiThread {
|
||||||
Mode.UNAVAILABLE -> {}
|
when (biometricMode) {
|
||||||
Mode.PAUSE -> {}
|
Mode.UNAVAILABLE -> {}
|
||||||
Mode.NOT_CONFIGURED -> {}
|
Mode.BIOMETRIC_NOT_CONFIGURED -> {}
|
||||||
Mode.WAIT_CREDENTIAL -> {}
|
Mode.KEY_MANAGER_UNAVAILABLE -> {}
|
||||||
Mode.STORE -> {
|
Mode.WAIT_CREDENTIAL -> {}
|
||||||
// newly store the entered password in encrypted way
|
Mode.STORE_CREDENTIAL -> {
|
||||||
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
|
// newly store the entered password in encrypted way
|
||||||
}
|
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
|
||||||
Mode.OPEN -> {
|
}
|
||||||
// retrieve the encrypted value from preferences
|
Mode.EXTRACT_CREDENTIAL -> {
|
||||||
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
|
// retrieve the encrypted value from preferences
|
||||||
it?.encryptedValue?.let { value ->
|
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
|
||||||
biometricUnlockDatabaseHelper?.decryptData(value)
|
it?.encryptedValue?.let { value ->
|
||||||
|
biometricUnlockDatabaseHelper?.decryptData(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,11 +167,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
private fun initNotAvailable() {
|
private fun initNotAvailable() {
|
||||||
showFingerPrintViews(false)
|
showFingerPrintViews(false)
|
||||||
|
|
||||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
advancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||||
}
|
|
||||||
|
|
||||||
private fun initPause() {
|
|
||||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initNotConfigured() {
|
private fun initNotConfigured() {
|
||||||
@@ -157,7 +175,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
setAdvancedUnlockedTitleView(R.string.configure_biometric)
|
setAdvancedUnlockedTitleView(R.string.configure_biometric)
|
||||||
setAdvancedUnlockedMessageView("")
|
setAdvancedUnlockedMessageView("")
|
||||||
|
|
||||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initWaitData() {
|
private fun initWaitData() {
|
||||||
@@ -165,7 +195,19 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
|
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
|
||||||
setAdvancedUnlockedMessageView("")
|
setAdvancedUnlockedMessageView("")
|
||||||
|
|
||||||
advancedUnlockInfoView?.setIconViewClickListener(null)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initEncryptData() {
|
private fun initEncryptData() {
|
||||||
@@ -178,9 +220,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
cryptoObject?.let { crypto ->
|
cryptoObject?.let { crypto ->
|
||||||
// Set listener to open the biometric dialog and save credential
|
// Set listener to open the biometric dialog and save credential
|
||||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||||
context.runOnUiThread {
|
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
|
||||||
biometricPrompt?.authenticate(promptInfo, crypto)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,17 +241,13 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
cryptoObject?.let { crypto ->
|
cryptoObject?.let { crypto ->
|
||||||
// Set listener to open the biometric dialog and check credential
|
// Set listener to open the biometric dialog and check credential
|
||||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||||
context.runOnUiThread {
|
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
|
||||||
biometricPrompt?.authenticate(promptInfo, crypto)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto open the biometric prompt
|
// Auto open the biometric prompt
|
||||||
if (isBiometricPromptAutoOpenEnable) {
|
if (isBiometricPromptAutoOpenEnable) {
|
||||||
isBiometricPromptAutoOpenEnable = false
|
isBiometricPromptAutoOpenEnable = false
|
||||||
context.runOnUiThread {
|
openBiometricPrompt(biometricPrompt, crypto, promptInfo)
|
||||||
biometricPrompt?.authenticate(promptInfo, crypto)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,42 +261,40 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
fun initBiometricMode() {
|
fun initBiometricMode() {
|
||||||
when (biometricMode) {
|
when (biometricMode) {
|
||||||
Mode.UNAVAILABLE -> initNotAvailable()
|
Mode.UNAVAILABLE -> initNotAvailable()
|
||||||
Mode.PAUSE -> initPause()
|
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
|
||||||
Mode.NOT_CONFIGURED -> initNotConfigured()
|
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
|
||||||
Mode.WAIT_CREDENTIAL -> initWaitData()
|
Mode.WAIT_CREDENTIAL -> initWaitData()
|
||||||
Mode.STORE -> initEncryptData()
|
Mode.STORE_CREDENTIAL -> initEncryptData()
|
||||||
Mode.OPEN -> initDecryptData()
|
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||||
}
|
}
|
||||||
// Show fingerprint key deletion
|
// Show fingerprint key deletion
|
||||||
context.invalidateOptionsMenu()
|
context.invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pause() {
|
|
||||||
biometricMode = Mode.PAUSE
|
|
||||||
initBiometricMode()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
// Restore the checked listener
|
// Restore the checked listener
|
||||||
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
|
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
|
||||||
|
|
||||||
biometricMode = Mode.UNAVAILABLE
|
|
||||||
initBiometricMode()
|
|
||||||
biometricUnlockDatabaseHelper = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only to fix multiple fingerprint menu #332
|
||||||
|
private var addBiometricMenuInProgress = false
|
||||||
fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) {
|
fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) {
|
||||||
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
|
if (!addBiometricMenuInProgress) {
|
||||||
if ((biometricMode != Mode.UNAVAILABLE
|
addBiometricMenuInProgress = true
|
||||||
&& biometricMode != Mode.NOT_CONFIGURED) && it)
|
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) {
|
||||||
menuInflater.inflate(R.menu.advanced_unlock, menu)
|
if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.BIOMETRIC_NOT_CONFIGURED)
|
||||||
|
&& it) {
|
||||||
|
menuInflater.inflate(R.menu.advanced_unlock, menu)
|
||||||
|
addBiometricMenuInProgress = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteEntryKey() {
|
fun deleteEntryKey() {
|
||||||
biometricUnlockDatabaseHelper?.deleteEntryKey()
|
biometricUnlockDatabaseHelper?.deleteEntryKey()
|
||||||
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri)
|
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri)
|
||||||
biometricMode = Mode.NOT_CONFIGURED
|
biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED
|
||||||
checkBiometricAvailability()
|
checkBiometricAvailability()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,7 +340,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
UNAVAILABLE, PAUSE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN
|
UNAVAILABLE, BIOMETRIC_NOT_CONFIGURED, KEY_MANAGER_UNAVAILABLE, WAIT_CREDENTIAL, STORE_CREDENTIAL, EXTRACT_CREDENTIAL
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.biometric
|
package com.kunzisoft.keepass.biometric
|
||||||
@@ -42,8 +42,7 @@ import javax.crypto.SecretKey
|
|||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@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
|
private var biometricPrompt: BiometricPrompt? = null
|
||||||
|
|
||||||
@@ -53,35 +52,48 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
private var keyguardManager: KeyguardManager? = null
|
private var keyguardManager: KeyguardManager? = null
|
||||||
private var cryptoObject: BiometricPrompt.CryptoObject? = null
|
private var cryptoObject: BiometricPrompt.CryptoObject? = null
|
||||||
|
|
||||||
private var isBiometricInit = false
|
private var isKeyManagerInit = false
|
||||||
private var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
|
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
|
||||||
|
var biometricUnlockCallback: BiometricUnlockCallback? = null
|
||||||
|
|
||||||
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder()
|
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||||
.setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
|
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
|
||||||
.setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
|
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
|
||||||
//.setDeviceCredentialAllowed(true) TODO device credential
|
setConfirmationRequired(true)
|
||||||
.setNegativeButtonText(context.getString(android.R.string.cancel))
|
// TODO device credential
|
||||||
.build()
|
/*
|
||||||
|
if (keyguardManager?.isDeviceSecure == true)
|
||||||
|
setDeviceCredentialAllowed(true)
|
||||||
|
else
|
||||||
|
*/
|
||||||
|
setNegativeButtonText(context.getString(android.R.string.cancel))
|
||||||
|
}.build()
|
||||||
|
|
||||||
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder()
|
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||||
.setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
|
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
|
||||||
.setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
||||||
//.setDeviceCredentialAllowed(true)
|
setConfirmationRequired(false)
|
||||||
.setNegativeButtonText(context.getString(android.R.string.cancel))
|
// TODO device credential
|
||||||
.build()
|
/*
|
||||||
|
if (keyguardManager?.isDeviceSecure == true)
|
||||||
|
setDeviceCredentialAllowed(true)
|
||||||
|
else
|
||||||
|
*/
|
||||||
|
setNegativeButtonText(context.getString(android.R.string.cancel))
|
||||||
|
}.build()
|
||||||
|
|
||||||
val isFingerprintInitialized: Boolean
|
val isKeyManagerInitialized: Boolean
|
||||||
get() {
|
get() {
|
||||||
if (!isBiometricInit && biometricUnlockCallback != null) {
|
if (!isKeyManagerInit) {
|
||||||
biometricUnlockCallback.onBiometricException(Exception("FingerPrint not initialized"))
|
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
|
||||||
}
|
}
|
||||||
return isBiometricInit
|
return isKeyManagerInit
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) {
|
if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) {
|
||||||
// really not much to do when no fingerprint support found
|
// really not much to do when no fingerprint support found
|
||||||
isBiometricInit = false
|
isKeyManagerInit = false
|
||||||
} else {
|
} else {
|
||||||
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||||
|
|
||||||
@@ -93,17 +105,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
+ BIOMETRIC_BLOCKS_MODES + "/"
|
+ BIOMETRIC_BLOCKS_MODES + "/"
|
||||||
+ BIOMETRIC_ENCRYPTION_PADDING)
|
+ BIOMETRIC_ENCRYPTION_PADDING)
|
||||||
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
|
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
|
||||||
isBiometricInit = true
|
isKeyManagerInit = (keyStore != null
|
||||||
|
&& keyGenerator != null
|
||||||
|
&& cipher != null)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Unable to initialize the keystore", e)
|
Log.e(TAG, "Unable to initialize the keystore", e)
|
||||||
isBiometricInit = false
|
isKeyManagerInit = false
|
||||||
biometricUnlockCallback?.onBiometricException(e)
|
biometricUnlockCallback?.onBiometricException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSecretKey(): SecretKey? {
|
private fun getSecretKey(): SecretKey? {
|
||||||
if (!isFingerprintInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -145,7 +159,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
: (biometricPrompt: BiometricPrompt?,
|
: (biometricPrompt: BiometricPrompt?,
|
||||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
cryptoObject: BiometricPrompt.CryptoObject?,
|
||||||
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
|
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
|
||||||
if (!isFingerprintInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -158,7 +172,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
|
|
||||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
||||||
deleteEntryKey()
|
biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
|
||||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||||
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
|
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
|
||||||
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||||
@@ -166,11 +180,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
Log.e(TAG, "Unable to initialize encrypt data", e)
|
Log.e(TAG, "Unable to initialize encrypt data", e)
|
||||||
biometricUnlockCallback?.onBiometricException(e)
|
biometricUnlockCallback?.onBiometricException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encryptData(value: String) {
|
fun encryptData(value: String) {
|
||||||
if (!isFingerprintInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -187,14 +200,13 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
Log.e(TAG, "Unable to encrypt data", e)
|
Log.e(TAG, "Unable to encrypt data", e)
|
||||||
biometricUnlockCallback?.onBiometricException(e)
|
biometricUnlockCallback?.onBiometricException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
|
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
|
||||||
: (biometricPrompt: BiometricPrompt?,
|
: (biometricPrompt: BiometricPrompt?,
|
||||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
cryptoObject: BiometricPrompt.CryptoObject?,
|
||||||
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
|
promptInfo: BiometricPrompt.PromptInfo)->Unit) {
|
||||||
if (!isFingerprintInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -219,11 +231,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
Log.e(TAG, "Unable to initialize decrypt data", e)
|
Log.e(TAG, "Unable to initialize decrypt data", e)
|
||||||
biometricUnlockCallback?.onBiometricException(e)
|
biometricUnlockCallback?.onBiometricException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decryptData(encryptedValue: String) {
|
fun decryptData(encryptedValue: String) {
|
||||||
if (!isFingerprintInitialized) {
|
if (!isKeyManagerInitialized) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -239,7 +250,6 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
Log.e(TAG, "Unable to decrypt data", e)
|
Log.e(TAG, "Unable to decrypt data", e)
|
||||||
biometricUnlockCallback?.onBiometricException(e)
|
biometricUnlockCallback?.onBiometricException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteEntryKey() {
|
fun deleteEntryKey() {
|
||||||
@@ -252,10 +262,6 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAuthenticationCallback(authenticationCallback: BiometricPrompt.AuthenticationCallback) {
|
|
||||||
this.authenticationCallback = authenticationCallback
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun initBiometricPrompt() {
|
fun initBiometricPrompt() {
|
||||||
if (biometricPrompt == null) {
|
if (biometricPrompt == null) {
|
||||||
@@ -289,22 +295,24 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity,
|
|||||||
* Remove entry key in keystore
|
* Remove entry key in keystore
|
||||||
*/
|
*/
|
||||||
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
|
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
|
||||||
biometricUnlockCallback: BiometricUnlockErrorCallback) {
|
biometricCallback: BiometricUnlockErrorCallback) {
|
||||||
val fingerPrintHelper = BiometricUnlockDatabaseHelper(context, object : BiometricUnlockCallback {
|
BiometricUnlockDatabaseHelper(context).apply {
|
||||||
|
biometricUnlockCallback = 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) {
|
override fun onInvalidKeyException(e: Exception) {
|
||||||
biometricUnlockCallback.onInvalidKeyException(e)
|
biometricCallback.onInvalidKeyException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBiometricException(e: Exception) {
|
||||||
|
biometricCallback.onBiometricException(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
deleteEntryKey()
|
||||||
override fun onBiometricException(e: Exception) {
|
}
|
||||||
biometricUnlockCallback.onBiometricException(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
fingerPrintHelper.deleteEntryKey()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,62 +1,63 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.biometric
|
package com.kunzisoft.keepass.biometric
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Animatable2
|
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||||
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
|
class FingerPrintAnimatedVector(context: Context, imageView: ImageView) {
|
||||||
|
|
||||||
private val scanFingerprint: AnimatedVectorDrawable =
|
private val scanFingerprint: AnimatedVectorDrawableCompat? =
|
||||||
context.getDrawable(R.drawable.scan_fingerprint) as AnimatedVectorDrawable
|
AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
imageView.setImageDrawable(scanFingerprint)
|
imageView.setImageDrawable(scanFingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var animationCallback = object : Animatable2.AnimationCallback() {
|
private var animationCallback = object : Animatable2Compat.AnimationCallback() {
|
||||||
override fun onAnimationEnd(drawable: Drawable) {
|
override fun onAnimationEnd(drawable: Drawable) {
|
||||||
if (!scanFingerprint.isRunning)
|
imageView.post {
|
||||||
scanFingerprint.start()
|
scanFingerprint?.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startScan() {
|
fun startScan() {
|
||||||
scanFingerprint.registerAnimationCallback(animationCallback)
|
scanFingerprint?.registerAnimationCallback(animationCallback)
|
||||||
|
|
||||||
if (!scanFingerprint.isRunning)
|
if (scanFingerprint?.isRunning != true)
|
||||||
scanFingerprint.start()
|
scanFingerprint?.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopScan() {
|
fun stopScan() {
|
||||||
scanFingerprint.unregisterAnimationCallback(animationCallback)
|
scanFingerprint?.unregisterAnimationCallback(animationCallback)
|
||||||
|
|
||||||
if (scanFingerprint.isRunning)
|
if (scanFingerprint?.isRunning == true)
|
||||||
scanFingerprint.stop()
|
scanFingerprint.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|||||||
@@ -1,33 +1,31 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|
||||||
import com.kunzisoft.keepass.stream.LEDataOutputStream
|
|
||||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||||
|
import com.kunzisoft.keepass.stream.longTo8Bytes
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.DigestOutputStream
|
import java.security.DigestOutputStream
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.util.Arrays
|
import java.util.*
|
||||||
|
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@@ -60,7 +58,7 @@ object CryptoUtil {
|
|||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pbR = LEDataOutputStream.writeLongBuf(r)
|
val pbR = longTo8Bytes(r)
|
||||||
val part = hmac.doFinal(pbR)
|
val part = hmac.doFinal(pbR)
|
||||||
|
|
||||||
val copy = min(cbOut - pos, part.size)
|
val copy = min(cbOut - pos, part.size)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto;
|
package com.kunzisoft.keepass.crypto;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.engine
|
package com.kunzisoft.keepass.crypto.engine
|
||||||
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.utils.Types
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
@@ -41,13 +41,28 @@ class AesEngine : CipherEngine() {
|
|||||||
return cipher
|
return cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
|
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
|
||||||
return PwEncryptionAlgorithm.AESRijndael
|
return EncryptionAlgorithm.AESRijndael
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
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()))
|
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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.engine
|
package com.kunzisoft.keepass.crypto.engine
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.utils.Types
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider
|
import org.spongycastle.jce.provider.BouncyCastleProvider
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
@@ -44,13 +44,28 @@ class ChaCha20Engine : CipherEngine() {
|
|||||||
return cipher
|
return cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
|
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
|
||||||
return PwEncryptionAlgorithm.ChaCha20
|
return EncryptionAlgorithm.ChaCha20
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
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()))
|
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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.engine
|
package com.kunzisoft.keepass.crypto.engine
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
@@ -46,6 +46,6 @@ abstract class CipherEngine {
|
|||||||
return getCipher(opmode, key, IV, false)
|
return getCipher(opmode, key, IV, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm
|
abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,31 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.engine
|
package com.kunzisoft.keepass.crypto.engine
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory
|
import com.kunzisoft.keepass.crypto.CipherFactory
|
||||||
import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.utils.Types
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.util.UUID
|
import java.util.*
|
||||||
|
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.NoSuchPaddingException
|
import javax.crypto.NoSuchPaddingException
|
||||||
import javax.crypto.spec.IvParameterSpec
|
import javax.crypto.spec.IvParameterSpec
|
||||||
@@ -47,13 +45,28 @@ class TwofishEngine : CipherEngine() {
|
|||||||
return cipher
|
return cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm {
|
override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm {
|
||||||
return PwEncryptionAlgorithm.Twofish
|
return EncryptionAlgorithm.Twofish
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
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()))
|
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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey;
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.keyDerivation
|
package com.kunzisoft.keepass.crypto.keyDerivation
|
||||||
@@ -23,7 +23,7 @@ import android.content.res.Resources
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.crypto.CryptoUtil
|
import com.kunzisoft.keepass.crypto.CryptoUtil
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
|
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
|
||||||
import com.kunzisoft.keepass.utils.Types
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -32,12 +32,10 @@ class AesKdf internal constructor() : KdfEngine() {
|
|||||||
|
|
||||||
override val defaultParameters: KdfParameters
|
override val defaultParameters: KdfParameters
|
||||||
get() {
|
get() {
|
||||||
val p = KdfParameters(uuid)
|
return KdfParameters(uuid!!).apply {
|
||||||
|
setParamUUID()
|
||||||
p.setParamUUID()
|
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
|
||||||
p.setUInt32(ParamRounds, DEFAULT_ROUNDS.toLong())
|
}
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultKeyRounds: Long
|
override val defaultKeyRounds: Long
|
||||||
@@ -54,8 +52,8 @@ class AesKdf internal constructor() : KdfEngine() {
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
||||||
var currentMasterKey = masterKey
|
var currentMasterKey = masterKey
|
||||||
val rounds = p.getUInt64(ParamRounds)
|
val rounds = p.getUInt64(PARAM_ROUNDS)
|
||||||
var seed = p.getByteArray(ParamSeed)
|
var seed = p.getByteArray(PARAM_SEED)
|
||||||
|
|
||||||
if (currentMasterKey.size != 32) {
|
if (currentMasterKey.size != 32) {
|
||||||
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
|
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
|
||||||
@@ -75,25 +73,40 @@ class AesKdf internal constructor() : KdfEngine() {
|
|||||||
val seed = ByteArray(32)
|
val seed = ByteArray(32)
|
||||||
random.nextBytes(seed)
|
random.nextBytes(seed)
|
||||||
|
|
||||||
p.setByteArray(ParamSeed, seed)
|
p.setByteArray(PARAM_SEED, seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeyRounds(p: KdfParameters): Long {
|
override fun getKeyRounds(p: KdfParameters): Long {
|
||||||
return p.getUInt64(ParamRounds)
|
return p.getUInt64(PARAM_ROUNDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
||||||
p.setUInt64(ParamRounds, keyRounds)
|
p.setUInt64(PARAM_ROUNDS, keyRounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val DEFAULT_ROUNDS = 6000
|
private const val DEFAULT_ROUNDS = 6000
|
||||||
|
|
||||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||||
byteArrayOf(0xC9.toByte(), 0xD9.toByte(), 0xF3.toByte(), 0x9A.toByte(), 0x62.toByte(), 0x8A.toByte(), 0x44.toByte(), 0x60.toByte(), 0xBF.toByte(), 0x74.toByte(), 0x0D.toByte(), 0x08.toByte(), 0xC1.toByte(), 0x8A.toByte(), 0x4F.toByte(), 0xEA.toByte()))
|
byteArrayOf(0xC9.toByte(),
|
||||||
|
0xD9.toByte(),
|
||||||
|
0xF3.toByte(),
|
||||||
|
0x9A.toByte(),
|
||||||
|
0x62.toByte(),
|
||||||
|
0x8A.toByte(),
|
||||||
|
0x44.toByte(),
|
||||||
|
0x60.toByte(),
|
||||||
|
0xBF.toByte(),
|
||||||
|
0x74.toByte(),
|
||||||
|
0x0D.toByte(),
|
||||||
|
0x08.toByte(),
|
||||||
|
0xC1.toByte(),
|
||||||
|
0x8A.toByte(),
|
||||||
|
0x4F.toByte(),
|
||||||
|
0xEA.toByte()))
|
||||||
|
|
||||||
const val ParamRounds = "R"
|
const val PARAM_ROUNDS = "R"
|
||||||
const val ParamSeed = "S"
|
const val PARAM_SEED = "S"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.keyDerivation
|
package com.kunzisoft.keepass.crypto.keyDerivation
|
||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.utils.Types
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -30,19 +30,19 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
|
|
||||||
override val defaultParameters: KdfParameters
|
override val defaultParameters: KdfParameters
|
||||||
get() {
|
get() {
|
||||||
val p = KdfParameters(uuid)
|
val p = KdfParameters(uuid!!)
|
||||||
|
|
||||||
p.setParamUUID()
|
p.setParamUUID()
|
||||||
p.setUInt32(ParamParallelism, DefaultParallelism)
|
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
|
||||||
p.setUInt64(ParamMemory, DefaultMemory)
|
p.setUInt64(PARAM_MEMORY, DEFAULT_MEMORY)
|
||||||
p.setUInt64(ParamIterations, DefaultIterations)
|
p.setUInt64(PARAM_ITERATIONS, DEFAULT_ITERATIONS)
|
||||||
p.setUInt32(ParamVersion, MaxVersion)
|
p.setUInt32(PARAM_VERSION, MAX_VERSION)
|
||||||
|
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultKeyRounds: Long
|
override val defaultKeyRounds: Long
|
||||||
get() = DefaultIterations
|
get() = DEFAULT_ITERATIONS
|
||||||
|
|
||||||
init {
|
init {
|
||||||
uuid = CIPHER_UUID
|
uuid = CIPHER_UUID
|
||||||
@@ -55,13 +55,13 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
||||||
|
|
||||||
val salt = p.getByteArray(ParamSalt)
|
val salt = p.getByteArray(PARAM_SALT)
|
||||||
val parallelism = p.getUInt32(ParamParallelism).toInt()
|
val parallelism = p.getUInt32(PARAM_PARALLELISM).toInt()
|
||||||
val memory = p.getUInt64(ParamMemory)
|
val memory = p.getUInt64(PARAM_MEMORY)
|
||||||
val iterations = p.getUInt64(ParamIterations)
|
val iterations = p.getUInt64(PARAM_ITERATIONS)
|
||||||
val version = p.getUInt32(ParamVersion)
|
val version = p.getUInt32(PARAM_VERSION)
|
||||||
val secretKey = p.getByteArray(ParamSecretKey)
|
val secretKey = p.getByteArray(PARAM_SECRET_KEY)
|
||||||
val assocData = p.getByteArray(ParamAssocData)
|
val assocData = p.getByteArray(PARAM_ASSOC_DATA)
|
||||||
|
|
||||||
return Argon2Native.transformKey(masterKey, salt, parallelism, memory, iterations,
|
return Argon2Native.transformKey(masterKey, salt, parallelism, memory, iterations,
|
||||||
secretKey, assocData, version)
|
secretKey, assocData, version)
|
||||||
@@ -73,71 +73,102 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
val salt = ByteArray(32)
|
val salt = ByteArray(32)
|
||||||
random.nextBytes(salt)
|
random.nextBytes(salt)
|
||||||
|
|
||||||
p.setByteArray(ParamSalt, salt)
|
p.setByteArray(PARAM_SALT, salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeyRounds(p: KdfParameters): Long {
|
override fun getKeyRounds(p: KdfParameters): Long {
|
||||||
return p.getUInt64(ParamIterations)
|
return p.getUInt64(PARAM_ITERATIONS)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
||||||
p.setUInt64(ParamIterations, keyRounds)
|
p.setUInt64(PARAM_ITERATIONS, keyRounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val minKeyRounds: Long
|
||||||
|
get() = MIN_ITERATIONS
|
||||||
|
|
||||||
|
override val maxKeyRounds: Long
|
||||||
|
get() = MAX_ITERATIONS
|
||||||
|
|
||||||
override fun getMemoryUsage(p: KdfParameters): Long {
|
override fun getMemoryUsage(p: KdfParameters): Long {
|
||||||
return p.getUInt64(ParamMemory)
|
return p.getUInt64(PARAM_MEMORY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
||||||
p.setUInt64(ParamMemory, memory)
|
p.setUInt64(PARAM_MEMORY, memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDefaultMemoryUsage(): Long {
|
override val defaultMemoryUsage: Long
|
||||||
return DefaultMemory
|
get() = DEFAULT_MEMORY
|
||||||
}
|
|
||||||
|
override val minMemoryUsage: Long
|
||||||
|
get() = MIN_MEMORY
|
||||||
|
|
||||||
|
override val maxMemoryUsage: Long
|
||||||
|
get() = MAX_MEMORY
|
||||||
|
|
||||||
override fun getParallelism(p: KdfParameters): Int {
|
override fun getParallelism(p: KdfParameters): Int {
|
||||||
return p.getUInt32(ParamParallelism).toInt() // TODO Verify
|
return p.getUInt32(PARAM_PARALLELISM).toInt() // TODO Verify
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setParallelism(p: KdfParameters, parallelism: Int) {
|
override fun setParallelism(p: KdfParameters, parallelism: Int) {
|
||||||
p.setUInt32(ParamParallelism, parallelism.toLong())
|
p.setUInt32(PARAM_PARALLELISM, parallelism.toLong())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDefaultParallelism(): Int {
|
override val defaultParallelism: Int
|
||||||
return DefaultParallelism.toInt() // TODO Verify
|
get() = DEFAULT_PARALLELISM.toInt()
|
||||||
}
|
|
||||||
|
override val minParallelism: Int
|
||||||
|
get() = MIN_PARALLELISM
|
||||||
|
|
||||||
|
override val maxParallelism: Int
|
||||||
|
get() = MAX_PARALLELISM
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val CIPHER_UUID: UUID = Types.bytestoUUID(
|
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||||
byteArrayOf(0xEF.toByte(), 0x63.toByte(), 0x6D.toByte(), 0xDF.toByte(), 0x8C.toByte(), 0x29.toByte(), 0x44.toByte(), 0x4B.toByte(), 0x91.toByte(), 0xF7.toByte(), 0xA9.toByte(), 0xA4.toByte(), 0x03.toByte(), 0xE3.toByte(), 0x0A.toByte(), 0x0C.toByte()))
|
byteArrayOf(0xEF.toByte(),
|
||||||
|
0x63.toByte(),
|
||||||
|
0x6D.toByte(),
|
||||||
|
0xDF.toByte(),
|
||||||
|
0x8C.toByte(),
|
||||||
|
0x29.toByte(),
|
||||||
|
0x44.toByte(),
|
||||||
|
0x4B.toByte(),
|
||||||
|
0x91.toByte(),
|
||||||
|
0xF7.toByte(),
|
||||||
|
0xA9.toByte(),
|
||||||
|
0xA4.toByte(),
|
||||||
|
0x03.toByte(),
|
||||||
|
0xE3.toByte(),
|
||||||
|
0x0A.toByte(),
|
||||||
|
0x0C.toByte()))
|
||||||
|
|
||||||
private const val ParamSalt = "S" // byte[]
|
private const val PARAM_SALT = "S" // byte[]
|
||||||
private const val ParamParallelism = "P" // UInt32
|
private const val PARAM_PARALLELISM = "P" // UInt32
|
||||||
private const val ParamMemory = "M" // UInt64
|
private const val PARAM_MEMORY = "M" // UInt64
|
||||||
private const val ParamIterations = "I" // UInt64
|
private const val PARAM_ITERATIONS = "I" // UInt64
|
||||||
private const val ParamVersion = "V" // UInt32
|
private const val PARAM_VERSION = "V" // UInt32
|
||||||
private const val ParamSecretKey = "K" // byte[]
|
private const val PARAM_SECRET_KEY = "K" // byte[]
|
||||||
private const val ParamAssocData = "A" // byte[]
|
private const val PARAM_ASSOC_DATA = "A" // byte[]
|
||||||
|
|
||||||
private const val MinVersion: Long = 0x10
|
private const val MIN_VERSION: Long = 0x10
|
||||||
private const val MaxVersion: Long = 0x13
|
private const val MAX_VERSION: Long = 0x13
|
||||||
|
|
||||||
private const val MinSalt = 8
|
private const val MIN_SALT = 8
|
||||||
private const val MaxSalt = Integer.MAX_VALUE
|
private const val MAX_SALT = Integer.MAX_VALUE
|
||||||
|
|
||||||
private const val MinIterations: Long = 1
|
private const val MIN_ITERATIONS: Long = 1
|
||||||
private const val MaxIterations = 4294967295L
|
private const val MAX_ITERATIONS = 4294967295L
|
||||||
|
|
||||||
private const val MinMemory = (1024 * 8).toLong()
|
private const val MIN_MEMORY = (1024 * 8).toLong()
|
||||||
private const val MaxMemory = Integer.MAX_VALUE.toLong()
|
private const val MAX_MEMORY = Integer.MAX_VALUE.toLong()
|
||||||
|
|
||||||
private const val MinParallelism = 1
|
private const val MIN_PARALLELISM = 1
|
||||||
private const val MaxParallelism = (1 shl 24) - 1
|
private const val MAX_PARALLELISM = (1 shl 24) - 1
|
||||||
|
|
||||||
private const val DefaultIterations: Long = 2
|
private const val DEFAULT_ITERATIONS: Long = 2
|
||||||
private const val DefaultMemory = (1024 * 1024).toLong()
|
private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
|
||||||
private const val DefaultParallelism: Long = 2
|
private const val DEFAULT_PARALLELISM: Long = 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.keyDerivation;
|
package com.kunzisoft.keepass.crypto.keyDerivation;
|
||||||
|
|||||||
@@ -1,46 +1,62 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.keyDerivation
|
package com.kunzisoft.keepass.crypto.keyDerivation
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.ObjectNameResource
|
import com.kunzisoft.keepass.utils.ObjectNameResource
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.io.Serializable
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
abstract class KdfEngine : ObjectNameResource {
|
// TODO Parcelable
|
||||||
|
abstract class KdfEngine : ObjectNameResource, Serializable {
|
||||||
|
|
||||||
var uuid: UUID? = null
|
var uuid: UUID? = null
|
||||||
|
|
||||||
abstract val defaultParameters: KdfParameters
|
abstract val defaultParameters: KdfParameters
|
||||||
|
|
||||||
abstract val defaultKeyRounds: Long
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray
|
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray
|
||||||
|
|
||||||
abstract fun randomize(p: KdfParameters)
|
abstract fun randomize(p: KdfParameters)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ITERATIONS
|
||||||
|
*/
|
||||||
|
|
||||||
abstract fun getKeyRounds(p: KdfParameters): Long
|
abstract fun getKeyRounds(p: KdfParameters): Long
|
||||||
|
|
||||||
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long)
|
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long)
|
||||||
|
|
||||||
|
abstract val defaultKeyRounds: Long
|
||||||
|
|
||||||
|
open val minKeyRounds: Long
|
||||||
|
get() = 1
|
||||||
|
|
||||||
|
open val maxKeyRounds: Long
|
||||||
|
get() = Int.MAX_VALUE.toLong()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* MEMORY
|
||||||
|
*/
|
||||||
|
|
||||||
open fun getMemoryUsage(p: KdfParameters): Long {
|
open fun getMemoryUsage(p: KdfParameters): Long {
|
||||||
return UNKNOWN_VALUE.toLong()
|
return UNKNOWN_VALUE.toLong()
|
||||||
}
|
}
|
||||||
@@ -49,9 +65,18 @@ abstract class KdfEngine : ObjectNameResource {
|
|||||||
// Do nothing by default
|
// Do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getDefaultMemoryUsage(): Long {
|
open val defaultMemoryUsage: Long
|
||||||
return UNKNOWN_VALUE.toLong()
|
get() = UNKNOWN_VALUE.toLong()
|
||||||
}
|
|
||||||
|
open val minMemoryUsage: Long
|
||||||
|
get() = 1
|
||||||
|
|
||||||
|
open val maxMemoryUsage: Long
|
||||||
|
get() = Int.MAX_VALUE.toLong()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PARALLELISM
|
||||||
|
*/
|
||||||
|
|
||||||
open fun getParallelism(p: KdfParameters): Int {
|
open fun getParallelism(p: KdfParameters): Int {
|
||||||
return UNKNOWN_VALUE
|
return UNKNOWN_VALUE
|
||||||
@@ -61,13 +86,16 @@ abstract class KdfEngine : ObjectNameResource {
|
|||||||
// Do nothing by default
|
// Do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun getDefaultParallelism(): Int {
|
open val defaultParallelism: Int
|
||||||
return UNKNOWN_VALUE
|
get() = UNKNOWN_VALUE
|
||||||
}
|
|
||||||
|
open val minParallelism: Int
|
||||||
|
get() = 1
|
||||||
|
|
||||||
|
open val maxParallelism: Int
|
||||||
|
get() = Int.MAX_VALUE
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val UNKNOWN_VALUE = -1
|
const val UNKNOWN_VALUE = -1
|
||||||
const val UNKNOWN_VALUE_STRING = (-1).toString()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,55 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.keyDerivation
|
package com.kunzisoft.keepass.crypto.keyDerivation
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
|
||||||
|
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
object KdfFactory {
|
object KdfFactory {
|
||||||
|
|
||||||
var aesKdf = AesKdf()
|
var aesKdf = AesKdf()
|
||||||
var argon2Kdf = Argon2Kdf()
|
var argon2Kdf = Argon2Kdf()
|
||||||
|
|
||||||
var kdfListV3: MutableList<KdfEngine> = ArrayList()
|
|
||||||
var kdfListV4: MutableList<KdfEngine> = ArrayList()
|
|
||||||
|
|
||||||
init {
|
|
||||||
kdfListV3.add(aesKdf)
|
|
||||||
|
|
||||||
kdfListV4.add(aesKdf)
|
|
||||||
kdfListV4.add(argon2Kdf)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(UnknownKDF::class)
|
|
||||||
fun getEngineV4(kdfParameters: KdfParameters?): KdfEngine {
|
|
||||||
val unknownKDFException = UnknownKDF()
|
|
||||||
if (kdfParameters == null) {
|
|
||||||
throw unknownKDFException
|
|
||||||
}
|
|
||||||
for (engine in kdfListV4) {
|
|
||||||
if (engine.uuid == kdfParameters.uuid) {
|
|
||||||
return engine
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw unknownKDFException
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,45 +1,42 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.action
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException
|
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
open class AssignPasswordInDatabaseRunnable (
|
||||||
context: Context,
|
context: Context,
|
||||||
database: Database,
|
database: Database,
|
||||||
|
protected val mDatabaseUri: Uri,
|
||||||
withMasterPassword: Boolean,
|
withMasterPassword: Boolean,
|
||||||
masterPassword: String?,
|
masterPassword: String?,
|
||||||
withKeyFile: Boolean,
|
withKeyFile: Boolean,
|
||||||
keyFile: Uri?,
|
keyFile: Uri?)
|
||||||
save: Boolean,
|
: SaveDatabaseRunnable(context, database, true) {
|
||||||
actionRunnable: ActionRunnable? = null)
|
|
||||||
: SaveDatabaseRunnable(context, database, save, actionRunnable) {
|
|
||||||
|
|
||||||
private var mMasterPassword: String? = null
|
private var mMasterPassword: String? = null
|
||||||
private var mKeyFile: Uri? = null
|
protected var mKeyFile: Uri? = null
|
||||||
|
|
||||||
private var mBackupKey: ByteArray? = null
|
private var mBackupKey: ByteArray? = null
|
||||||
|
|
||||||
@@ -50,7 +47,7 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
|||||||
this.mKeyFile = keyFile
|
this.mKeyFile = keyFile
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun onStartRun() {
|
||||||
// Set key
|
// Set key
|
||||||
try {
|
try {
|
||||||
// TODO move master key methods
|
// TODO move master key methods
|
||||||
@@ -59,20 +56,21 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
|||||||
|
|
||||||
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFile)
|
val uriInputStream = UriUtil.getUriInputStream(context.contentResolver, mKeyFile)
|
||||||
database.retrieveMasterKey(mMasterPassword, uriInputStream)
|
database.retrieveMasterKey(mMasterPassword, uriInputStream)
|
||||||
|
} catch (e: Exception) {
|
||||||
// To save the database
|
|
||||||
super.run()
|
|
||||||
finishRun(true)
|
|
||||||
} catch (e: InvalidKeyFileException) {
|
|
||||||
erase(mBackupKey)
|
erase(mBackupKey)
|
||||||
finishRun(false, e.message)
|
setError(e)
|
||||||
} catch (e: IOException) {
|
|
||||||
erase(mBackupKey)
|
|
||||||
finishRun(false, e.message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onStartRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinishRun(result: Result) {
|
override fun onFinishRun() {
|
||||||
|
super.onFinishRun()
|
||||||
|
|
||||||
|
// Erase the biometric
|
||||||
|
CipherDatabaseAction.getInstance(context)
|
||||||
|
.deleteByDatabaseUri(mDatabaseUri)
|
||||||
|
|
||||||
if (!result.isSuccess) {
|
if (!result.isSuccess) {
|
||||||
// Erase the current master key
|
// Erase the current master key
|
||||||
erase(database.masterKey)
|
erase(database.masterKey)
|
||||||
@@ -80,8 +78,6 @@ open class AssignPasswordInDatabaseRunnable @JvmOverloads constructor(
|
|||||||
database.masterKey = it
|
database.masterKey = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onFinishRun(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,58 +1,66 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.action
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
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.database.element.Database
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
|
||||||
|
|
||||||
class CreateDatabaseRunnable(context: Context,
|
class CreateDatabaseRunnable(context: Context,
|
||||||
private val mDatabaseUri: Uri,
|
|
||||||
private val mDatabase: Database,
|
private val mDatabase: Database,
|
||||||
|
databaseUri: Uri,
|
||||||
|
private val databaseName: String,
|
||||||
|
private val rootName: String,
|
||||||
withMasterPassword: Boolean,
|
withMasterPassword: Boolean,
|
||||||
masterPassword: String?,
|
masterPassword: String?,
|
||||||
withKeyFile: Boolean,
|
withKeyFile: Boolean,
|
||||||
keyFile: Uri?,
|
keyFile: Uri?)
|
||||||
save: Boolean,
|
: AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) {
|
||||||
actionRunnable: ActionRunnable? = null)
|
|
||||||
: AssignPasswordInDatabaseRunnable(context, mDatabase, withMasterPassword, masterPassword, withKeyFile, keyFile, save, actionRunnable) {
|
|
||||||
|
|
||||||
override fun run() {
|
override fun onStartRun() {
|
||||||
try {
|
try {
|
||||||
// Create new database record
|
// Create new database record
|
||||||
mDatabase.apply {
|
mDatabase.apply {
|
||||||
createData(mDatabaseUri)
|
createData(mDatabaseUri, databaseName, rootName)
|
||||||
// Set Database state
|
// Set Database state
|
||||||
loaded = true
|
loaded = true
|
||||||
// Commit changes
|
|
||||||
super.run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finishRun(true)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
||||||
mDatabase.closeAndClear()
|
mDatabase.closeAndClear()
|
||||||
finishRun(false, e.message)
|
setError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onStartRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinishRun(result: Result) {}
|
override fun onFinishRun() {
|
||||||
|
super.onFinishRun()
|
||||||
|
|
||||||
|
if (result.isSuccess) {
|
||||||
|
// Add database to recent files
|
||||||
|
FileDatabaseHistoryAction.getInstance(context.applicationContext)
|
||||||
|
.addOrUpdateDatabaseUri(mDatabaseUri, mKeyFile)
|
||||||
|
} else {
|
||||||
|
Log.e("CreateDatabaseRunnable", "Unable to create the database")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,136 +1,99 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.action
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.preference.PreferenceManager
|
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||||
import androidx.annotation.StringRes
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
import android.util.Log
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
|
||||||
import com.kunzisoft.keepass.database.exception.*
|
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
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.ActionRunnable
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.io.IOException
|
|
||||||
import java.lang.ref.WeakReference
|
|
||||||
|
|
||||||
class LoadDatabaseRunnable(private val mWeakContext: WeakReference<Context>,
|
class LoadDatabaseRunnable(private val context: Context,
|
||||||
private val mDatabase: Database,
|
private val mDatabase: Database,
|
||||||
private val mUri: Uri,
|
private val mUri: Uri,
|
||||||
private val mPass: String?,
|
private val mPass: String?,
|
||||||
private val mKey: Uri?,
|
private val mKey: Uri?,
|
||||||
|
private val mReadonly: Boolean,
|
||||||
|
private val mCipherEntity: CipherDatabaseEntity?,
|
||||||
|
private val mOmitBackup: Boolean,
|
||||||
|
private val mFixDuplicateUUID: Boolean,
|
||||||
private val progressTaskUpdater: ProgressTaskUpdater?,
|
private val progressTaskUpdater: ProgressTaskUpdater?,
|
||||||
nestedAction: ActionRunnable)
|
private val mDuplicateUuidAction: ((Result) -> Unit)?)
|
||||||
: ActionRunnable(nestedAction, executeNestedActionIfResultFalse = true) {
|
: ActionRunnable() {
|
||||||
|
|
||||||
private val mRememberKeyFile: Boolean
|
private val cacheDirectory = context.applicationContext.filesDir
|
||||||
get() {
|
|
||||||
return mWeakContext.get()?.let {
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(it)
|
|
||||||
.getBoolean(it.getString(R.string.keyfile_key),
|
|
||||||
it.resources.getBoolean(R.bool.keyfile_default))
|
|
||||||
} ?: true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun run() {
|
override fun onStartRun() {
|
||||||
|
// Clear before we load
|
||||||
|
mDatabase.closeAndClear(cacheDirectory)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionRun() {
|
||||||
try {
|
try {
|
||||||
mWeakContext.get()?.let {
|
mDatabase.loadData(mUri, mPass, mKey,
|
||||||
mDatabase.loadData(it, mUri, mPass, mKey, progressTaskUpdater)
|
mReadonly,
|
||||||
saveFileData(mUri, mKey)
|
context.contentResolver,
|
||||||
finishRun(true)
|
cacheDirectory,
|
||||||
} ?: finishRun(false, "Context null")
|
mOmitBackup,
|
||||||
} catch (e: ArcFourException) {
|
mFixDuplicateUUID,
|
||||||
catchError(e, R.string.error_arc4)
|
progressTaskUpdater)
|
||||||
return
|
}
|
||||||
} catch (e: InvalidPasswordException) {
|
catch (e: DuplicateUuidDatabaseException) {
|
||||||
catchError(e, R.string.invalid_password)
|
mDuplicateUuidAction?.invoke(result)
|
||||||
return
|
setError(e)
|
||||||
} catch (e: ContentFileNotFoundException) {
|
}
|
||||||
catchError(e, R.string.file_not_found_content)
|
catch (e: LoadDatabaseException) {
|
||||||
return
|
setError(e)
|
||||||
} catch (e: FileNotFoundException) {
|
}
|
||||||
catchError(e, R.string.file_not_found)
|
}
|
||||||
return
|
|
||||||
} catch (e: IOException) {
|
override fun onFinishRun() {
|
||||||
var messageId = R.string.error_load_database
|
if (result.isSuccess) {
|
||||||
e.message?.let {
|
// Save keyFile in app database
|
||||||
if (it.contains("Hash failed with code"))
|
val rememberKeyFile = PreferencesUtil.rememberKeyFiles(context)
|
||||||
messageId = R.string.error_load_database_KDF_memory
|
if (rememberKeyFile) {
|
||||||
|
var keyUri = mKey
|
||||||
|
if (!rememberKeyFile) {
|
||||||
|
keyUri = null
|
||||||
|
}
|
||||||
|
FileDatabaseHistoryAction.getInstance(context)
|
||||||
|
.addOrUpdateDatabaseUri(mUri, keyUri)
|
||||||
}
|
}
|
||||||
catchError(e, messageId, true)
|
|
||||||
return
|
|
||||||
} catch (e: KeyFileEmptyException) {
|
|
||||||
catchError(e, R.string.keyfile_is_empty)
|
|
||||||
return
|
|
||||||
} catch (e: InvalidAlgorithmException) {
|
|
||||||
catchError(e, R.string.invalid_algorithm)
|
|
||||||
return
|
|
||||||
} catch (e: InvalidKeyFileException) {
|
|
||||||
catchError(e, R.string.keyfile_does_not_exist)
|
|
||||||
return
|
|
||||||
} catch (e: InvalidDBSignatureException) {
|
|
||||||
catchError(e, R.string.invalid_db_sig)
|
|
||||||
return
|
|
||||||
} catch (e: InvalidDBVersionException) {
|
|
||||||
catchError(e, R.string.unsupported_db_version)
|
|
||||||
return
|
|
||||||
} catch (e: InvalidDBException) {
|
|
||||||
catchError(e, R.string.error_invalid_db)
|
|
||||||
return
|
|
||||||
} catch (e: OutOfMemoryError) {
|
|
||||||
catchError(e, R.string.error_out_of_memory)
|
|
||||||
return
|
|
||||||
} catch (e: Exception) {
|
|
||||||
catchError(e, R.string.error_load_database, true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun catchError(e: Throwable, @StringRes messageId: Int, addThrowableMessage: Boolean = false) {
|
// Register the biometric
|
||||||
var errorMessage = mWeakContext.get()?.getString(messageId)
|
mCipherEntity?.let { cipherDatabaseEntity ->
|
||||||
Log.e(TAG, errorMessage, e)
|
CipherDatabaseAction.getInstance(context)
|
||||||
if (addThrowableMessage)
|
.addOrUpdateCipherDatabase(cipherDatabaseEntity) // return value not called
|
||||||
errorMessage = errorMessage + " " + e.localizedMessage
|
}
|
||||||
finishRun(false, errorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveFileData(uri: Uri, key: Uri?) {
|
// Start the opening notification
|
||||||
var keyFileUri = key
|
DatabaseOpenNotificationService.startIfAllowed(context)
|
||||||
if (!mRememberKeyFile) {
|
} else {
|
||||||
keyFileUri = null
|
mDatabase.closeAndClear(cacheDirectory)
|
||||||
}
|
}
|
||||||
mWeakContext.get()?.let {
|
|
||||||
FileDatabaseHistoryAction.getInstance(it).addOrUpdateDatabaseUri(uri, keyFileUri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinishRun(result: Result) {
|
|
||||||
if (!result.isSuccess) {
|
|
||||||
mDatabase.closeAndClear(mWeakContext.get()?.filesDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val TAG = LoadDatabaseRunnable::class.java.name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.kunzisoft.keepass.database.action
|
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import com.kunzisoft.keepass.R
|
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
|
||||||
|
|
||||||
class ProgressDialogSaveDatabaseThread(activity: FragmentActivity,
|
|
||||||
actionRunnable: (ProgressTaskUpdater?)-> ActionRunnable)
|
|
||||||
: ProgressDialogThread(activity,
|
|
||||||
actionRunnable,
|
|
||||||
R.string.saving_database,
|
|
||||||
null,
|
|
||||||
R.string.do_not_kill_app)
|
|
||||||
@@ -1,86 +1,557 @@
|
|||||||
|
/*
|
||||||
|
* 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.database.action
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.*
|
||||||
import android.os.AsyncTask
|
import android.content.Context.BIND_ABOVE_CLIENT
|
||||||
|
import android.content.Context.BIND_NOT_FOREGROUND
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.StringRes
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
|
import com.kunzisoft.keepass.database.element.*
|
||||||
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
|
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.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_TASK_TITLE_KEY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
|
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.retrieveProgressDialog
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
|
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
||||||
|
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
open class ProgressDialogThread(private val activity: FragmentActivity,
|
class ProgressDialogThread(private val activity: FragmentActivity) {
|
||||||
private val actionRunnable: (ProgressTaskUpdater?)-> ActionRunnable,
|
|
||||||
@StringRes private val titleId: Int,
|
|
||||||
@StringRes private val messageId: Int? = null,
|
|
||||||
@StringRes private val warningId: Int? = null) {
|
|
||||||
|
|
||||||
private val progressTaskDialogFragment = ProgressTaskDialogFragment.build(
|
var onActionFinish: ((actionTask: String,
|
||||||
titleId,
|
result: ActionRunnable.Result) -> Unit)? = null
|
||||||
messageId,
|
|
||||||
warningId)
|
|
||||||
private var actionRunnableAsyncTask: ActionRunnableAsyncTask? = null
|
|
||||||
var actionFinishInUIThread: ActionRunnable? = null
|
|
||||||
|
|
||||||
private var intentDatabaseTask:Intent = Intent(activity, DatabaseTaskNotificationService::class.java)
|
private var intentDatabaseTask = Intent(activity, DatabaseTaskNotificationService::class.java)
|
||||||
|
|
||||||
init {
|
private var databaseTaskBroadcastReceiver: BroadcastReceiver? = null
|
||||||
actionRunnableAsyncTask = ActionRunnableAsyncTask(progressTaskDialogFragment,
|
private var mBinder: DatabaseTaskNotificationService.ActionTaskBinder? = null
|
||||||
{
|
|
||||||
activity.runOnUiThread {
|
|
||||||
intentDatabaseTask.putExtra(DATABASE_TASK_TITLE_KEY, titleId)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
activity.startForegroundService(intentDatabaseTask)
|
|
||||||
} else {
|
|
||||||
activity.startService(intentDatabaseTask)
|
|
||||||
}
|
|
||||||
TimeoutHelper.temporarilyDisableTimeout()
|
|
||||||
// Show the dialog
|
|
||||||
ProgressTaskDialogFragment.start(activity, progressTaskDialogFragment)
|
|
||||||
}
|
|
||||||
}, { result ->
|
|
||||||
activity.runOnUiThread {
|
|
||||||
actionFinishInUIThread?.onFinishRun(result)
|
|
||||||
// Remove the progress task
|
|
||||||
ProgressTaskDialogFragment.stop(activity)
|
|
||||||
TimeoutHelper.releaseTemporarilyDisableTimeoutAndLockIfTimeout(activity)
|
|
||||||
activity.stopService(intentDatabaseTask)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start() {
|
private var serviceConnection: ServiceConnection? = null
|
||||||
actionRunnableAsyncTask?.execute(actionRunnable)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
||||||
private class ActionRunnableAsyncTask(private val progressTaskUpdater: ProgressTaskUpdater,
|
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||||
private val onPreExecute: () -> Unit,
|
TimeoutHelper.temporarilyDisableTimeout(activity)
|
||||||
private val onPostExecute: (result: ActionRunnable.Result) -> Unit)
|
startOrUpdateDialog(titleId, messageId, warningId)
|
||||||
: AsyncTask<((ProgressTaskUpdater?)-> ActionRunnable), Void, ActionRunnable.Result>() {
|
|
||||||
|
|
||||||
override fun onPreExecute() {
|
|
||||||
super.onPreExecute()
|
|
||||||
onPreExecute.invoke()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doInBackground(vararg actionRunnables: ((ProgressTaskUpdater?)-> ActionRunnable)?): ActionRunnable.Result {
|
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||||
var resultTask = ActionRunnable.Result(false)
|
TimeoutHelper.temporarilyDisableTimeout(activity)
|
||||||
actionRunnables.forEach {
|
startOrUpdateDialog(titleId, messageId, warningId)
|
||||||
it?.invoke(progressTaskUpdater)?.apply {
|
}
|
||||||
run()
|
|
||||||
resultTask = result
|
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
|
||||||
|
onActionFinish?.invoke(actionTask, result)
|
||||||
|
// Remove the progress task
|
||||||
|
ProgressTaskDialogFragment.stop(activity)
|
||||||
|
TimeoutHelper.releaseTemporarilyDisableTimeoutAndLockIfTimeout(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||||
|
var progressTaskDialogFragment = retrieveProgressDialog(activity)
|
||||||
|
if (progressTaskDialogFragment == null) {
|
||||||
|
progressTaskDialogFragment = ProgressTaskDialogFragment.build()
|
||||||
|
ProgressTaskDialogFragment.start(activity, progressTaskDialogFragment)
|
||||||
|
}
|
||||||
|
progressTaskDialogFragment.apply {
|
||||||
|
titleId?.let {
|
||||||
|
updateTitle(it)
|
||||||
|
}
|
||||||
|
messageId?.let {
|
||||||
|
updateMessage(it)
|
||||||
|
}
|
||||||
|
warningId?.let {
|
||||||
|
updateWarning(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun initServiceConnection() {
|
||||||
|
if (serviceConnection == null) {
|
||||||
|
serviceConnection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||||
|
mBinder = (serviceBinder as DatabaseTaskNotificationService.ActionTaskBinder).apply {
|
||||||
|
addActionTaskListener(actionTaskListener)
|
||||||
|
getService().checkAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||||
|
mBinder = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return resultTask
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPostExecute(result: ActionRunnable.Result) {
|
|
||||||
super.onPostExecute(result)
|
|
||||||
onPostExecute.invoke(result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun bindService() {
|
||||||
|
initServiceConnection()
|
||||||
|
serviceConnection?.let {
|
||||||
|
activity.bindService(intentDatabaseTask, it, BIND_NOT_FOREGROUND or BIND_ABOVE_CLIENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unbind the service and assign null to the service connection to check if already unbind or not
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
private fun unBindService() {
|
||||||
|
serviceConnection?.let {
|
||||||
|
activity.unbindService(it)
|
||||||
|
}
|
||||||
|
serviceConnection = null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun registerProgressTask() {
|
||||||
|
ProgressTaskDialogFragment.stop(activity)
|
||||||
|
|
||||||
|
// Register a database task receiver to stop loading dialog when service finish the task
|
||||||
|
databaseTaskBroadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
when (intent?.action) {
|
||||||
|
DATABASE_START_TASK_ACTION -> {
|
||||||
|
// Bind to the service when is starting
|
||||||
|
bindService()
|
||||||
|
}
|
||||||
|
DATABASE_STOP_TASK_ACTION -> {
|
||||||
|
unBindService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activity.registerReceiver(databaseTaskBroadcastReceiver,
|
||||||
|
IntentFilter().apply {
|
||||||
|
addAction(DATABASE_START_TASK_ACTION)
|
||||||
|
addAction(DATABASE_STOP_TASK_ACTION)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if a service is currently running else do nothing
|
||||||
|
bindService()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun unregisterProgressTask() {
|
||||||
|
ProgressTaskDialogFragment.stop(activity)
|
||||||
|
|
||||||
|
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||||
|
mBinder = null
|
||||||
|
|
||||||
|
unBindService()
|
||||||
|
|
||||||
|
try {
|
||||||
|
activity.unregisterReceiver(databaseTaskBroadcastReceiver)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
// If receiver not register, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun start(bundle: Bundle? = null, actionTask: String) {
|
||||||
|
activity.stopService(intentDatabaseTask)
|
||||||
|
if (bundle != null)
|
||||||
|
intentDatabaseTask.putExtras(bundle)
|
||||||
|
activity.runOnUiThread {
|
||||||
|
intentDatabaseTask.action = actionTask
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
activity.startForegroundService(intentDatabaseTask)
|
||||||
|
} else {
|
||||||
|
activity.startService(intentDatabaseTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
----
|
||||||
|
Main methods
|
||||||
|
----
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startDatabaseCreate(databaseUri: Uri,
|
||||||
|
masterPasswordChecked: Boolean,
|
||||||
|
masterPassword: String?,
|
||||||
|
keyFileChecked: Boolean,
|
||||||
|
keyFile: Uri?) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
|
||||||
|
putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.KEY_FILE_KEY, keyFile)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_CREATE_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseLoad(databaseUri: Uri,
|
||||||
|
masterPassword: String?,
|
||||||
|
keyFile: Uri?,
|
||||||
|
readOnly: Boolean,
|
||||||
|
cipherEntity: CipherDatabaseEntity?,
|
||||||
|
fixDuplicateUuid: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||||
|
putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.KEY_FILE_KEY, keyFile)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.READ_ONLY_KEY, readOnly)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.CIPHER_ENTITY_KEY, cipherEntity)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.FIX_DUPLICATE_UUID_KEY, fixDuplicateUuid)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_LOAD_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseAssignPassword(databaseUri: Uri,
|
||||||
|
masterPasswordChecked: Boolean,
|
||||||
|
masterPassword: String?,
|
||||||
|
keyFileChecked: Boolean,
|
||||||
|
keyFile: Uri?) {
|
||||||
|
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.DATABASE_URI_KEY, databaseUri)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.MASTER_PASSWORD_CHECKED_KEY, masterPasswordChecked)
|
||||||
|
putString(DatabaseTaskNotificationService.MASTER_PASSWORD_KEY, masterPassword)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.KEY_FILE_CHECKED_KEY, keyFileChecked)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.KEY_FILE_KEY, keyFile)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_ASSIGN_PASSWORD_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
----
|
||||||
|
Nodes Actions
|
||||||
|
----
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startDatabaseCreateGroup(newGroup: Group,
|
||||||
|
parent: Group,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.GROUP_KEY, newGroup)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, parent.nodeId)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_CREATE_GROUP_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseUpdateGroup(oldGroup: Group,
|
||||||
|
groupToUpdate: Group,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.GROUP_ID_KEY, oldGroup.nodeId)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.GROUP_KEY, groupToUpdate)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_GROUP_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseCreateEntry(newEntry: Entry,
|
||||||
|
parent: Group,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, newEntry)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, parent.nodeId)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_CREATE_ENTRY_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseUpdateEntry(oldEntry: Entry,
|
||||||
|
entryToUpdate: Entry,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, oldEntry.nodeId)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, entryToUpdate)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_ENTRY_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startDatabaseActionListNodes(actionTask: String,
|
||||||
|
nodesPaste: List<Node>,
|
||||||
|
newParent: Group?,
|
||||||
|
save: Boolean) {
|
||||||
|
val groupsIdToCopy = ArrayList<NodeId<*>>()
|
||||||
|
val entriesIdToCopy = ArrayList<NodeId<UUID>>()
|
||||||
|
nodesPaste.forEach { nodeVersioned ->
|
||||||
|
when (nodeVersioned.type) {
|
||||||
|
Type.GROUP -> {
|
||||||
|
(nodeVersioned as Group).nodeId?.let { groupId ->
|
||||||
|
groupsIdToCopy.add(groupId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type.ENTRY -> {
|
||||||
|
entriesIdToCopy.add((nodeVersioned as Entry).nodeId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val newParentId = newParent?.nodeId
|
||||||
|
|
||||||
|
start(Bundle().apply {
|
||||||
|
putAll(getBundleFromListNodes(nodesPaste))
|
||||||
|
putParcelableArrayList(DatabaseTaskNotificationService.GROUPS_ID_KEY, groupsIdToCopy)
|
||||||
|
putParcelableArrayList(DatabaseTaskNotificationService.ENTRIES_ID_KEY, entriesIdToCopy)
|
||||||
|
if (newParentId != null)
|
||||||
|
putParcelable(DatabaseTaskNotificationService.PARENT_ID_KEY, newParentId)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, actionTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseCopyNodes(nodesToCopy: List<Node>,
|
||||||
|
newParent: Group,
|
||||||
|
save: Boolean) {
|
||||||
|
startDatabaseActionListNodes(ACTION_DATABASE_COPY_NODES_TASK, nodesToCopy, newParent, save)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseMoveNodes(nodesToMove: List<Node>,
|
||||||
|
newParent: Group,
|
||||||
|
save: Boolean) {
|
||||||
|
startDatabaseActionListNodes(ACTION_DATABASE_MOVE_NODES_TASK, nodesToMove, newParent, save)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseDeleteNodes(nodesToDelete: List<Node>,
|
||||||
|
save: Boolean) {
|
||||||
|
startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
-----------------
|
||||||
|
Entry History Settings
|
||||||
|
-----------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startDatabaseRestoreEntryHistory(mainEntry: Entry,
|
||||||
|
entryHistoryPosition: Int,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
|
||||||
|
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseDeleteEntryHistory(mainEntry: Entry,
|
||||||
|
entryHistoryPosition: Int,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
|
||||||
|
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_DELETE_ENTRY_HISTORY)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
-----------------
|
||||||
|
Main Settings
|
||||||
|
-----------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startDatabaseSaveName(oldName: String,
|
||||||
|
newName: String,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldName)
|
||||||
|
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_NAME_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveDescription(oldDescription: String,
|
||||||
|
newDescription: String,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDescription)
|
||||||
|
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String,
|
||||||
|
newDefaultUsername: String,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDefaultUsername)
|
||||||
|
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveColor(oldColor: String,
|
||||||
|
newColor: String,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldColor)
|
||||||
|
putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_COLOR_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm,
|
||||||
|
newCompression: CompressionAlgorithm,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldCompression)
|
||||||
|
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_COMPRESSION_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int,
|
||||||
|
newMaxHistoryItems: Int,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistoryItems)
|
||||||
|
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long,
|
||||||
|
newMaxHistorySize: Long,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistorySize)
|
||||||
|
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
-------------------
|
||||||
|
Security Settings
|
||||||
|
-------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun startDatabaseSaveEncryption(oldEncryption: EncryptionAlgorithm,
|
||||||
|
newEncryption: EncryptionAlgorithm,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldEncryption)
|
||||||
|
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_ENCRYPTION_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine,
|
||||||
|
newKeyDerivation: KdfEngine,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldKeyDerivation)
|
||||||
|
putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveIterations(oldIterations: Long,
|
||||||
|
newIterations: Long,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldIterations)
|
||||||
|
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_ITERATIONS_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long,
|
||||||
|
newMemoryUsage: Long,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMemoryUsage)
|
||||||
|
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startDatabaseSaveParallelism(oldParallelism: Int,
|
||||||
|
newParallelism: Int,
|
||||||
|
save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
|
||||||
|
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save Database without parameter
|
||||||
|
*/
|
||||||
|
fun startDatabaseSave(save: Boolean) {
|
||||||
|
start(Bundle().apply {
|
||||||
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
|
}
|
||||||
|
, ACTION_DATABASE_SAVE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,63 +1,50 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* (at your option) any later version.
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
* GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.action
|
package com.kunzisoft.keepass.database.action
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.exception.PwDbOutputException
|
import com.kunzisoft.keepass.database.exception.DatabaseException
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
abstract class SaveDatabaseRunnable(protected var context: Context,
|
open class SaveDatabaseRunnable(protected var context: Context,
|
||||||
protected var database: Database,
|
protected var database: Database,
|
||||||
private val save: Boolean,
|
private var saveDatabase: Boolean)
|
||||||
nestedAction: ActionRunnable? = null) : ActionRunnable(nestedAction) {
|
: ActionRunnable() {
|
||||||
|
|
||||||
// TODO Service to prevent background thread kill
|
var mAfterSaveDatabase: ((Result) -> Unit)? = null
|
||||||
override fun run() {
|
|
||||||
if (save) {
|
override fun onStartRun() {}
|
||||||
|
|
||||||
|
override fun onActionRun() {
|
||||||
|
if (saveDatabase && result.isSuccess) {
|
||||||
try {
|
try {
|
||||||
database.saveData(context.contentResolver)
|
database.saveData(context.contentResolver)
|
||||||
} catch (e: IOException) {
|
} catch (e: DatabaseException) {
|
||||||
finishRun(false, e.message)
|
setError(e)
|
||||||
} catch (e: PwDbOutputException) {
|
|
||||||
finishRun(false, e.message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to call super.run() in child class
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinishRun(result: Result) {
|
override fun onFinishRun() {
|
||||||
// Need to call super.onFinishRun(result) in child class
|
// Need to call super.onFinishRun() in child class
|
||||||
}
|
mAfterSaveDatabase?.invoke(result)
|
||||||
}
|
|
||||||
|
|
||||||
class SaveDatabaseActionRunnable(context: Context,
|
|
||||||
database: Database,
|
|
||||||
save: Boolean,
|
|
||||||
nestedAction: ActionRunnable? = null)
|
|
||||||
: SaveDatabaseRunnable(context, database, save, nestedAction) {
|
|
||||||
|
|
||||||
override fun run() {
|
|
||||||
super.run()
|
|
||||||
finishRun(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.database.action
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
|
|
||||||
|
class UpdateCompressionBinariesDatabaseRunnable (
|
||||||
|
context: Context,
|
||||||
|
database: Database,
|
||||||
|
private val oldCompressionAlgorithm: CompressionAlgorithm,
|
||||||
|
private val newCompressionAlgorithm: CompressionAlgorithm,
|
||||||
|
saveDatabase: Boolean)
|
||||||
|
: SaveDatabaseRunnable(context, database, saveDatabase) {
|
||||||
|
|
||||||
|
override fun onStartRun() {
|
||||||
|
// Set new compression
|
||||||
|
if (database.allowDataCompression) {
|
||||||
|
try {
|
||||||
|
database.apply {
|
||||||
|
updateDataBinaryCompression(oldCompressionAlgorithm, newCompressionAlgorithm)
|
||||||
|
compressionAlgorithm = newCompressionAlgorithm
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
setError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onStartRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFinishRun() {
|
||||||
|
super.onFinishRun()
|
||||||
|
|
||||||
|
if (database.allowDataCompression) {
|
||||||
|
if (!result.isSuccess) {
|
||||||
|
try {
|
||||||
|
database.apply {
|
||||||
|
compressionAlgorithm = oldCompressionAlgorithm
|
||||||
|
updateDataBinaryCompression(newCompressionAlgorithm, oldCompressionAlgorithm)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
setError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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.database.action.history
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
|
||||||
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
|
|
||||||
|
class DeleteEntryHistoryDatabaseRunnable (
|
||||||
|
context: Context,
|
||||||
|
database: Database,
|
||||||
|
private val mainEntry: Entry,
|
||||||
|
private val entryHistoryPosition: Int,
|
||||||
|
saveDatabase: Boolean)
|
||||||
|
: SaveDatabaseRunnable(context, database, saveDatabase) {
|
||||||
|
|
||||||
|
override fun onStartRun() {
|
||||||
|
try {
|
||||||
|
mainEntry.removeEntryFromHistory(entryHistoryPosition)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
setError(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onStartRun()
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user