Compare commits

...

220 Commits

Author SHA1 Message Date
J-Jamet
2cc530cbec Merge branch 'hotfix/fix_compil_translation' 2018-03-09 22:28:47 +01:00
J-Jamet
b540cde623 Fix compilation 2018-03-09 22:24:26 +01:00
J-Jamet
9ffffdceca Merge branch 'release/2.5.0.0beta5' 2018-03-09 20:55:49 +01:00
J-Jamet
257d0ff315 Update ScreenShots 2018-03-09 17:02:02 +01:00
J-Jamet
a935b0a089 Fix flickering 2018-03-09 16:46:58 +01:00
J-Jamet
0236bddffd Restore default transition 2018-03-09 16:36:50 +01:00
J-Jamet
384966641a Fix fingerprint keyfile recognition 2018-03-09 16:26:51 +01:00
J-Jamet
3be9c9161c Fix lock when screen off (back to password) 2018-03-09 16:00:24 +01:00
J-Jamet
a445533ffc Fix orientation in FileSelect and Password 2018-03-09 15:30:07 +01:00
J-Jamet
e073b21544 Fix compilation, flickering and crash with Autofill 2018-03-09 12:45:20 +01:00
J-Jamet
7e746bb01c Add Changelog for 2.5.0.0beta5 2018-03-08 13:42:59 +01:00
J-Jamet
2d8e902a2a Merge branch 'feature/Autofill' into develop #1 2018-03-08 13:11:15 +01:00
J-Jamet
bb10892b76 Exclude Autofill views 2018-03-08 12:59:56 +01:00
J-Jamet
bdfa5ae707 Change form filling setting text 2018-03-08 12:30:33 +01:00
J-Jamet
2091b04a11 Add autofill traduction 2018-03-08 12:28:03 +01:00
J-Jamet
f367c098ec Solve bug of back entry slowdown 2018-03-08 11:57:13 +01:00
J-Jamet
98631d37ae Dialog for Autofill not available 2018-03-08 11:50:07 +01:00
J-Jamet
d504ab7924 Retrieve Dataset, Upgrade Gradle, Add D8 dex 2018-03-08 00:19:32 +01:00
J-Jamet
4fc8c74530 Pass data result through activities 2018-03-06 15:13:17 +01:00
J-Jamet
4876623f86 Refactor autofill preference 2018-03-05 13:19:44 +01:00
J-Jamet
d5b752e4c0 Merge branch 'develop' after refactor lock 2018-03-04 12:53:47 +01:00
J-Jamet
a1ecc5b399 Refactor code to lock 2018-03-04 12:39:21 +01:00
J-Jamet
306e8b17b2 Merge branch 'develop' into feature/Autofill 2018-03-04 01:29:17 +01:00
J-Jamet
f66500f535 Change KeyFileHelper and refactor code 2018-03-03 01:15:22 +01:00
J-Jamet
b92ef7cb1a Change labels and copy toast 2018-03-02 13:15:17 +01:00
J-Jamet
b30c2656e8 Delete unused method 2018-03-02 12:49:18 +01:00
J-Jamet
0899c7fc5a Add Monospace setting for field #5 2018-03-02 12:39:03 +01:00
J-Jamet
800d0eb04d Solve bug of kdb and kdbx association 2018-03-01 23:13:18 +01:00
J-Jamet
b09bc52b51 Merge branch 'feature/Permissions' into develop #34 2018-03-01 22:54:36 +01:00
J-Jamet
657c810c1f Change string for never ask permission 2018-03-01 22:49:34 +01:00
J-Jamet
c501d6bdc6 Change fileSelect view 2018-03-01 22:42:10 +01:00
J-Jamet
f13c595bf9 Add permission to write database and read one without content provider 2018-03-01 19:01:00 +01:00
J-Jamet
60e2209281 Add External permission with permissionDispatcher 2018-03-01 14:13:19 +01:00
J-Jamet
24eeff1d61 Add menu order for each item 2018-02-27 22:11:34 +01:00
J-Jamet
c94291f6e1 Solve bug #25 - Add fingerprint key deletion 2018-02-27 21:15:46 +01:00
J-Jamet
6faee3cef9 Solve bug #25 - Read the fingerprint 2018-02-26 20:53:22 +01:00
J-Jamet
bb4e067394 Remove names from disclaimer 2018-02-26 14:45:32 +01:00
J-Jamet
81503c6934 Add Recycle Bin setting in code 2018-02-26 14:16:14 +01:00
J-Jamet
8858a5cdca Change code for list and solve bugs 2018-02-26 13:16:30 +01:00
J-Jamet
d994fbafcf Add 15 minutes to clipboard timeout 2018-02-26 11:45:34 +01:00
J-Jamet
c3954faa3e Solve bug after sort and prepare recycle bin at the bottom 2018-02-24 21:38:40 +01:00
J-Jamet
0650a6f7db Add ascending sort 2018-02-24 21:10:12 +01:00
J-Jamet
0ddd08bf6d Add dialog for sort and precise setting 2018-02-24 18:09:17 +01:00
J-Jamet
a1720c1c79 Move URL creation below password 2018-02-24 13:48:19 +01:00
J-Jamet
cd9f82696a Change name by title and sort trad 2018-02-24 13:46:20 +01:00
J-Jamet
242427d348 Hide "show password" icon when password not present 2018-02-24 13:04:51 +01:00
J-Jamet
3f1ab3623d Better button animation 2018-02-24 00:00:02 +01:00
J-Jamet
7068b4b4b3 Solve bug of update 2018-02-23 23:41:35 +01:00
J-Jamet
7c81685aa6 New animation for add button 2018-02-23 16:47:35 +01:00
J-Jamet
f7fc7984e2 Add compat for button animation 2018-02-23 14:53:01 +01:00
J-Jamet
b2ef29d131 Solve bug of hide button when scrolling 2018-02-23 14:40:43 +01:00
J-Jamet
d5dcf697f6 Refactor search and hide add button during search 2018-02-23 12:40:15 +01:00
J-Jamet
9731247f2e Lock toolbar 2018-02-23 11:20:36 +01:00
J-Jamet
c2654cd65c Add fingerprint alpha for text 2018-02-22 20:57:43 +01:00
J-Jamet
e032540c0b Add 5, 10 and 20 seconds of clipboard timeout 2018-02-22 20:51:22 +01:00
J-Jamet
6dda7d1e64 Copy as buttons 2018-02-22 20:17:43 +01:00
J-Jamet
f67df77dff Solve crash for empty password in V3 2018-02-22 18:22:43 +01:00
J-Jamet
ecdadaf0bc Solve small bug in code (listener) 2018-02-22 18:16:16 +01:00
J-Jamet
25fb85a5ef Solve bug of metastream 2018-02-22 18:04:40 +01:00
J-Jamet
31426268ea Refactor GroupActivity remove V3 and V4 2018-02-22 17:26:49 +01:00
J-Jamet
f0b5b1bcb5 Refactor EntryEditActivity, delete V3 and V4 2018-02-21 16:26:46 +01:00
J-Jamet
1f2ebf0825 Solve bug of file integration 2018-02-20 13:44:03 +01:00
J-Jamet
fa0afbe947 Solve bug of FileSelect list 2018-02-20 12:09:12 +01:00
J-Jamet
70967a4234 Solve bug of FAB in Entry Edit 2018-02-20 11:49:36 +01:00
J-Jamet
87c9aeeb12 Solve bug for new field 2018-02-19 21:21:23 +01:00
J-Jamet
0a386c3985 Solve visual FABs 2018-02-19 19:30:09 +01:00
J-Jamet
bfcd4b9f00 Solve visual submenu bug 2018-02-19 18:53:48 +01:00
J-Jamet
3d6082a5d9 Solve bug of update entry 2018-02-19 17:50:53 +01:00
J-Jamet
aceeb581d4 Add recycle bin setting, remove version, encapsulate field 2018-02-19 17:20:42 +01:00
J-Jamet
d46a6a2ea8 Hide empty field, move code in view, delete EntryActivityV4 2018-02-18 15:31:56 +01:00
J-Jamet
c1bf96ac5f Add done button for password and solve view bug 2018-02-18 12:16:56 +01:00
J-Jamet
a1bf5f1e70 Solve bug when update entry 2018-02-16 21:11:40 +01:00
J-Jamet
7155e25c1e Update views for material design 2018-02-16 20:53:09 +01:00
J-Jamet
5c4d2e607a Refactor for list of nodes, solve bug of add button 2018-02-16 16:33:25 +01:00
J-Jamet
79750c5320 Remove useless dirty, solve trash bug 2018-02-16 15:14:24 +01:00
J-Jamet
bb353bc9d6 Auto close of add button 2018-02-16 10:56:09 +01:00
J-Jamet
5818762aaf Solve bug of empty new entry 2018-02-16 10:55:48 +01:00
J-Jamet
e5467bc54b Solve search list and add edit for contextmenu in code 2018-02-14 20:21:44 +01:00
J-Jamet
e574fba0a5 Solve VerifyError exception for old device 2018-02-14 17:23:47 +01:00
J-Jamet
82b59662f3 Solve bug of settings not openable link to #19 2018-02-14 12:12:17 +01:00
Jeremy
260ce95509 Solve bug #19 and refactor click listener for fileselectadapter 2018-02-13 20:18:02 +01:00
Jeremy
23df28cb25 Solve bugs of recycle bin and entry view after callback 2018-02-13 18:25:31 +01:00
Jérémy JAMET
a68960a30f Update Readme, Remove JNI 2018-02-12 00:48:20 +01:00
Jeremy
f295aac206 Deleting some TODOs 2018-02-12 00:35:53 +01:00
Jeremy
d568604117 Solve bugs when update node in list, start encapsulate code 2018-02-12 00:22:03 +01:00
Jeremy
795d6fa334 Serializable for Pw, solve bug of entry refresh 2018-02-10 16:08:10 +01:00
Jeremy
d788d16020 New methods to dynamically add and remove node 2018-02-09 19:57:21 +01:00
Jeremy
c8db1f7c5c Add strategy pattern to node
Move sort list preference
2018-02-09 14:44:43 +01:00
Jeremy
ac67ff9f21 Refactor packages 2018-02-09 14:21:48 +01:00
Jeremy
3a71f635f0 ListView as RecyclerView,
Update SearchView,
Create Activity package
2018-02-09 12:45:45 +01:00
Jeremy
a9e12cb518 Merge branch 'develop' of https://github.com/Kunzisoft/KeePassDX into develop 2018-02-08 14:31:15 +01:00
J-Jamet
b0aac43d6c Solve bad fingerprint dialog issue #25 2018-02-02 16:38:09 +01:00
J-Jamet
f0d7249679 Solve search issue when no entry available #26 2018-02-02 16:28:44 +01:00
J-Jamet
32fb536a92 Solve fingerprint null pointer exception issue 2018-02-02 15:58:56 +01:00
J-Jamet
8f8361a176 Solve images in Readme 2018-02-01 19:53:32 +01:00
J-Jamet
06056128e5 Update fastlane scripts 2018-02-01 19:28:11 +01:00
Jeremy
259c31b94c Merge tag '2.5.0.0beta4' into develop
New tag for version 2.5.0.0beta4
2018-02-01 17:45:47 +01:00
Jeremy
c24c18d89e Merge branch 'release/2.5.0.0beta4' 2018-02-01 17:44:29 +01:00
Jeremy
61043b3acb Add fastlane/README.md 2018-02-01 17:18:35 +01:00
Jeremy
52f8862e71 Add changelogs 2018-02-01 15:44:36 +01:00
J-Jamet
8bd32e6605 Update fastlane script 2018-02-01 02:53:05 +01:00
J-Jamet
d430305eb1 Upgrade version 2018-02-01 02:52:45 +01:00
J-Jamet
2cb3972865 Add fastlane 2018-01-31 20:26:43 +01:00
J-Jamet
724bb1fc86 Solve issue for gradle compilation 2018-01-31 20:07:05 +01:00
Jeremy
ffffeb9e85 Better autofill data transfer between each activity,
New Autofill helper,
Add consultation mode
2018-01-30 21:08:30 +01:00
Jeremy
ebe8c90238 Mod files to do a good compilation 2018-01-29 23:26:36 +01:00
Jeremy
25f657e665 Merge branch 'develop' into feature/Autofill (Upgrade with last dev) 2018-01-29 23:24:40 +01:00
J-Jamet
38def26865 Solve crash when keyfile is selected 2018-01-28 22:43:13 +01:00
Jeremy
a08e65733d Upgrade disclaimer 2018-01-28 20:04:45 +01:00
Jeremy
e5bc9bfd1d Merge branch 'JanThomas118-update-german-translation' into develop 2018-01-28 19:31:17 +01:00
Jeremy
767f7b06d6 Merge branch 'upstream-update' issue #23 into develop 2018-01-28 18:55:56 +01:00
Jeremy
a06977cd25 Upgrade CHANGELOG 2018-01-28 18:48:05 +01:00
Jeremy
60be6f1223 Merge branch 'feature/FingerPrint' issue #20 into develop 2018-01-28 18:13:37 +01:00
Jeremy
e9929ed848 Fingerprint decrypt without fill password view 2018-01-28 16:56:26 +01:00
Jeremy
21c657c107 Code factoring 2018-01-28 16:37:18 +01:00
Jan Thomas
a93271401d fixed one punctuation error and removed accidental space at start of file 2018-01-27 22:59:40 +01:00
Jan Thomas
a66cd68ae2 Added more german translation strings 2018-01-27 22:54:33 +01:00
Jeremy
9ac060ea05 New settings to delete fingerprints 2018-01-27 15:49:23 +01:00
Jeremy
c20f453b90 Add fingerprints keystore deletion when disabled 2018-01-27 15:08:10 +01:00
J-Jamet
27d633a1e9 Lock fingerprint settings 2018-01-26 21:36:08 +01:00
J-Jamet
221a851f44 Solve fingerprints bugs 2018-01-26 20:50:37 +01:00
J-Jamet
c091ffb5e1 Remove thread when typing 2018-01-26 17:11:22 +01:00
J-Jamet
d8f81b669d Remove singleInstance in PasswordActivity -> bug in Kitkat 2018-01-24 22:03:53 +01:00
J-Jamet
fb72f37ebb Solve bug KeepassDX #18 2018-01-24 21:11:43 +01:00
Jeremy
6c5936d15d Add singleInstance tag for some activities 2018-01-23 22:31:40 +01:00
Jeremy
e68c682cac Decode FileUri in views 2018-01-23 22:31:01 +01:00
Jeremy
04da145513 Solve DocumentFile bug 2018-01-23 22:19:16 +01:00
J-Jamet
a15a039f2a Merge branch 'feature/ListOpening' into develop 2018-01-17 18:27:06 +01:00
J-Jamet
818c0a769b Update screenshots and CHANGELOG 2018-01-17 18:24:14 +01:00
J-Jamet
cc7b8a3fd8 Add setting to select file path and solve bugs 2018-01-17 17:42:26 +01:00
J-Jamet
43b4d00902 Add file deletion, move information and solve bugs 2018-01-17 15:28:11 +01:00
J-Jamet
06b126469a Add information dialog for file 2018-01-14 22:46:38 +01:00
J-Jamet
2ca4e817e9 Solve bug of titleFileList 2018-01-14 20:11:13 +01:00
J-Jamet
42a52b26bf FileSelect as RecyclerView 2018-01-14 19:34:32 +01:00
J-Jamet
5d8a73080b Change donation url 2018-01-14 17:45:59 +01:00
J-Jamet
0b736ce0b3 Change edit text layout color 2018-01-12 20:44:28 +01:00
J-Jamet
dea6515e90 Update file layout 2018-01-12 20:04:53 +01:00
J-Jamet
1bd1b2a224 Update "Open recent database" text 2018-01-12 19:01:00 +01:00
J-Jamet
b7a76ed2e7 Change file_selection 2018-01-12 18:56:25 +01:00
Jeremy
a1237215cc New layout 2018-01-10 23:20:54 +01:00
J-Jamet
4bb869e288 Merge branch 'develop' into upstream-update 2018-01-03 12:33:41 +01:00
J-Jamet
1d528488d3 Merge branch 'master' 2.2.1 of https://github.com/bpellin/keepassdroid into upstream-update 2018-01-03 12:16:49 +01:00
Brian Pellin
90282d9722 Version bump 2018-01-02 22:37:31 -06:00
bpellin
2c7b19d67d Merge pull request #255 from shanempope/dev
Fixing issue where Search opens new task instead of using same task.
2018-01-02 22:26:35 -06:00
bpellin
981fef8fb1 Merge pull request #256 from EdlerProgrammierer/patch-1
Add DE translation
2018-01-02 22:24:56 -06:00
Brian Pellin
77c8207c73 Fix kdbx4 date corruption 2018-01-02 22:08:22 -06:00
Jérémy JAMET
825a5c7e73 Update ReadMe.md 2018-01-02 19:10:29 +01:00
Jeremy
d921a0ae1a Change fingerprint exceptions 2018-01-02 00:49:11 +01:00
J-Jamet
ffd40a5419 Merge branch 'develop' 2.5.0.0beta3 into feature/Autofill 2017-12-15 16:28:10 +01:00
EdlerProgrammierer
1946844858 Add DE translation 2017-12-13 19:29:31 +01:00
J-Jamet
b62106068b Solve compilation bug for fdroid 2017-12-13 18:55:32 +01:00
Jeremy
1218d89173 Solve preferences bugs 2017-12-12 02:57:53 +01:00
Shane Pope
052641c556 Removing extraneous comment 2017-12-11 18:18:55 -06:00
Shane Pope
7950933d1f Fixing issue where Search opens new task instead of using same task.
If you open KeePassDroid from a file in another app, the KeePassDroid activity opens in that Task. However, if you search the ACTION_SEARCH intent opens a new task putting the app in a weird state that has various bugs. The easy way to fix this is setting PasswordActivity to LaunchMode="singleTask" in AndroidManifest. The other option is to remove the FLAG_ACTIVITY_NEW_TASK intent flagbit on search allowing the search activity to stay in the current task. I've included the second option in this changeset.
2017-12-11 18:10:58 -06:00
J-Jamet
37279af514 Change preferences as compat 2017-12-09 00:48:34 +01:00
J-Jamet
b45a8b5c94 Solve bug for fingerprint layout 2017-12-09 00:08:32 +01:00
J-Jamet
8a344170b3 Change description for rounds fix 2017-12-08 22:36:47 +01:00
J-Jamet
09f6498907 Solve settings style for API < 21 2017-12-08 21:04:02 +01:00
J-Jamet
57b8b9e53b Add features in Changelog 2017-12-08 21:03:33 +01:00
J-Jamet
8edb2c26d4 Merge branch 'upstream-update' 2.2.0.9 into develop #11 2017-12-08 20:30:26 +01:00
J-Jamet
7d7b34b2d0 Merge branch 'feature/EmptyPassword' into develop #2 2017-12-08 20:02:36 +01:00
J-Jamet
73765a7ecb Merge branch 'feature/FingerprintDialog' into develop #4 2017-12-08 19:18:26 +01:00
Jeremy
fb99c32708 Add fix for encryption rounds 2017-12-08 17:08:47 +01:00
Jeremy
6bb0b1a7e7 Add database lock for screen off 2017-12-08 15:53:25 +01:00
Jeremy
7a66c964a7 Add fingerprint setting for API < 23 2017-12-07 19:19:31 +01:00
Jeremy
a5b1535fb8 Add fingerprint setting 2017-12-07 15:57:16 +01:00
J-Jamet
05a5da4c8d Add fingerprint dialog and update icons 2017-12-06 22:28:25 +01:00
J-Jamet
f5314e0a9a Update ReadMe and screens 2017-12-05 21:14:54 +01:00
J-Jamet
332d1e0e06 Solve dialogs bugs (checkboxes) 2017-12-05 21:00:02 +01:00
Jeremy
9173c99928 Solve layout for selections 2017-12-04 21:28:44 +01:00
Jeremy
421bdae0d7 Solve select path bugs 2017-12-04 17:55:54 +01:00
Jeremy
421de6ea49 Add folder picker and solve asynctask 2017-12-04 04:39:52 +01:00
J-Jamet
ec2dd3db64 Add dialog to create database file 2017-12-03 16:45:40 +01:00
J-Jamet
e6cc9b628e New keyfile management 2017-12-01 20:03:22 +01:00
J-Jamet
7cce527e43 New workflow to assign master password 2017-12-01 18:24:20 +01:00
Jeremy
dd35fd508f Merge 2.2.0.9 into upstream-update 2017-11-30 22:40:34 +01:00
Brian Pellin
a7b5fd512a Version bump 2017-11-30 14:37:07 -06:00
Brian Pellin
45b03231dd Update build tools version 2017-11-30 14:34:20 -06:00
bpellin
85357e2168 Merge pull request #248 from pepeEL/patch-1
Update translation PL
2017-11-30 14:30:35 -06:00
bpellin
d6709254e2 Merge pull request #247 from bboa/master
Updated Russian strings.xml
2017-11-30 14:29:41 -06:00
J-Jamet
ef42dcd47f Change views and add checkboxes 2017-11-30 19:16:34 +01:00
J-Jamet
8536586c13 Solve settings title and change box by switch 2017-11-30 01:39:59 +01:00
J-Jamet
4794ccb38a Option to disable notifications #7 2017-11-30 01:32:16 +01:00
J-Jamet
d14dc22918 Solve layout password issue 2017-11-30 00:52:12 +01:00
J-Jamet
79084cee62 Merge branch 'develop' of repo 2017-11-30 00:39:23 +01:00
J-Jamet
f0ec9229cd Add visual representation of characters 2017-11-30 00:37:52 +01:00
J-Jamet
f6b04dff24 Password characters in settings 2017-11-30 00:19:26 +01:00
J-Jamet
b6721b32e7 Password length in settings #3 2017-11-29 20:42:01 +01:00
Jérémy JAMET
b1804a94b6 Update ReadMe.md 2017-11-29 20:41:22 +01:00
J-Jamet
f6bcd51cd3 Update gradle, .gitignore, add art screen and update readme 2017-11-29 20:41:22 +01:00
J-Jamet
a833dfe64a Remove unused libs and upgrade version 2017-11-29 20:41:22 +01:00
J-Jamet
87efd1daa4 Merge keepassdroid 2.2.0.8 and update changelog 2017-11-29 18:47:26 +01:00
J-Jamet
ecdd744b8e Merge keepassdroid 2.2.0.8 and update changelog 2017-11-29 18:46:46 +01:00
J-Jamet
9012fd6da6 Merge branch 'master' into develop 2017-11-28 01:27:31 +01:00
J-Jamet
c6a711dec5 Solve open file bug 2017-11-28 01:27:22 +01:00
pepeEL
9b1a0285c4 Update translation PL 2017-11-27 07:15:08 +01:00
Igor Nedoboy
fd6dd01ee7 Update strings.xml 2017-11-27 03:32:54 +03:00
Jérémy JAMET
bd10fe8542 Update ReadMe.md 2017-11-27 00:08:37 +01:00
J-Jamet
8333a80d88 Update gradle, .gitignore, add art screen and update readme 2017-11-27 00:07:08 +01:00
Brian Pellin
192f116925 Version bump 2017-11-25 23:45:25 -06:00
Brian Pellin
409ffd4f95 Fix UTF8 encoding 2017-11-25 23:42:24 -06:00
Brian Pellin
0add3c76c8 Add rounds corruption fix mode. 2017-11-25 23:42:21 -06:00
Brian Pellin
b4f9e06bd6 Version bump 2017-11-24 21:33:28 -06:00
Brian Pellin
c686ff7156 Fix kdbx4 attachments 2017-11-24 21:28:23 -06:00
Brian Pellin
da1370e7f5 Use the correct number of key encryption rounds on KDBX <4 2017-11-24 20:26:23 -06:00
Brian Pellin
c564253206 Use ACTION_GET_CONTENT when not using the storage access framework 2017-11-24 19:19:33 -06:00
Brian Pellin
3f22bf9e5c Updated Hungarian translations. 2017-11-24 19:18:06 -06:00
Jeremy
8f8b265d97 Add workflow autofill response 2017-11-24 22:01:18 +01:00
Jeremy
d20eef0922 Add autofill service and preference 2017-11-23 19:07:13 +01:00
Brian Pellin
eae321d034 Add additional ndk abis 2017-11-21 20:59:07 -06:00
Brian Pellin
e2022183ea Version bump 2017-11-21 20:43:18 -06:00
Brian Pellin
924db245ec Make DateFormatter threadsafe 2017-11-21 20:40:55 -06:00
Brian Pellin
2dae805ac0 Only show fingerprint prompt on devices with fingerprint hardware 2017-11-21 20:39:40 -06:00
Brian Pellin
9ce0a413ad Update build file versions 2017-11-21 20:38:30 -06:00
Brian Pellin
1ece7b1df7 Version bump 2017-11-19 21:12:38 -06:00
Brian Pellin
b7aec355a2 Fingerprint fixes 2017-11-19 21:09:55 -06:00
Brian Pellin
780a0cd42d Version bump 2017-11-19 19:30:20 -06:00
Brian Pellin
de45421b50 Handle devices with unconfigured fingerprint sensors better 2017-11-19 19:27:47 -06:00
Brian Pellin
c11e17af05 Remove new APIs from fingerprint helper 2017-11-19 19:11:16 -06:00
Brian Pellin
260de4099b Fix crash on empty search results 2017-11-19 16:46:47 -06:00
266 changed files with 12896 additions and 5932 deletions

15
.gitignore vendored
View File

@@ -6,6 +6,7 @@
# Built application files
*.apk
*.ap_
app/free_google/*
# Files for the ART/Dalvik VM
*.dex
@@ -39,7 +40,6 @@ captures/
# Intellij
*.iml
<<<<<<< HEAD
.idea/workspace.xml
.idea/tasks.xml
.idea/gradle.xml
@@ -49,19 +49,16 @@ captures/
# Keystore files
# Uncomment the following line if you do not want to check your keystore files in.
#*.jks
=======
.idea/*
# Keystore files
*.jks
>>>>>>> master
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
# Google Services (e.g. APIs or Firebase)
google-services.json
<<<<<<< HEAD
# Freeline
freeline.py
@@ -70,5 +67,11 @@ freeline_project_description.json
# Iml Files
app/app.iml
=======
>>>>>>> master
# Art
art/screen*.png
art/logo_512.png
# Dir linux
.directory
*/.directory

2
.idea/misc.xml generated
View File

@@ -24,7 +24,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -1,3 +1,47 @@
KeepassDX (2.5.0.0beta5)
* Autofill (Android O)
* Deletion for group
* New sorts with (Asc/Dsc, Groups before or after)
* Better permission management with dialog at runtime
* Setting to change font of field (monospace for a better visibility)
* Open kdbx and kdb files from file browser
* Change sort of fields
* Hide empty fields
* Add copy button for Username / Password and extra field
* Add 5, 10, 20 seconds and 15 minutes of clipboard timeout
* Hide "show password" icon when password not present
* New animations for add button
* New list to add and delete node with animation
* Change view for better cohesion
* Upgrade translations
* Fix crash for API < Kitkat
* Fix fingerprint bugs
* Fix many small bugs
* Add recycle bin setting (not yet accessible)
KeepassDX (2.5.0.0beta4)
* Show only file name
* Setting for full path
* Add information for each database file
* Setting to delete fingerprints
* Solve bugs when change fingerprint
* Delete view assignment for fingerprint opening
* Merge KeePassDroid 2.2.1
KeepassDX (2.5.0.0beta3)
* New database workflow with new screens and folder selection
* Settings for default password generation
* Fingerprint dialog for explanations
* Setting to disable fingerprint
* Directly opening kdbx file
* Setting to disable notifications
* Setting to lock database when screen is shut off
* Merge KeePassDroid 2.2.0.9
* Add corruption fix mode
KeepassDX (2.5.0.0beta2)
* Remove libs for F-Droid
KeepassDX (2.5.0.0beta1)
* Fork KeepassDroid
* Add Material Design
@@ -7,6 +51,37 @@ KeepassDX (2.5.0.0beta1)
* Update French translation
* Change donation (see KeepassDroid to contribute on both projects)
KeePassDroid (2.2.1)
* Fix kdbx4 date corruption
* Updated German translations
KeePassDroid (2.2.0.9)
* Update build tools version to workaround CM/Lineage bug (closes: #249)
* Update Russian translations
* Update Polish translations
KeePassDroid (2.2.0.8)
* Add corruption fix mode
* Update Hungarian translations
KeePassDroid (2.2.0.7)
* Fix KDBX3 encryption rounds corruption
* Fix KDBX4 attachement crashes
KeePassDroid (2.2.0.6)
* Add additional ndk ABIs
KeePassDroid (2.2.0.5)
* Don't show fingerprint prompt on devices without fingerprint hardware
* Fix dateformat crashes
KeePassDroid (2.2.0.4)
* Fingerprint fixes
KeePassDroid (2.2.0.3)
* Search crash fix
* Improve fingerprint handling
KeePassDroid (2.2.0.2)
* Fix non fingerprint password layout

View File

@@ -14,14 +14,14 @@ Tadashi Saito
vhschlenker
bumper314 - Samsung multiwindow support
Hans Cappelle - fingerprint sensor integration
Jeremy Jamet - Material Design - Patches
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 - German
MaWi, rvs2008, meviox, MaDill, EdlerProgrammierer, Jan Thomas - German
yslandro - Norwegian Nynorsk
王科峰 - Chinese
Typhoon - Slovak
@@ -30,7 +30,7 @@ Matsuu Takuto - Japanese
Carlos Schlyter - Portugese (Brazil)
YSmhXQDd6Z - Portugese (Portugal)
andriykopanytsia - Ukranian
intel - Hungarian
intel, Zoltán Antal - Hungarian
H Vanek - Czech
jipanos - Spanish
Erik Fdevriendt, Erik Jan Meijer - Dutch

View File

@@ -1,17 +1,19 @@
# Android Keepass DX
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/logo.png"> Keepass DX is a material design Keepass Client for manage keys and passwords in crypt database for your android device.
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/icon.png"> Keepass DX is a material design Keepass Client for manage keys and passwords in crypt database for your android device.
### Features
- Create database file / entries and groups
- Open database, copy username / password, open URI / URL
- Fingerprint for fast unlocking
- Material design with themes
- Device integration and AutoFill (In progress)
* Create database files / entries and groups
* Support for .kdb and .kdbx files (version 1 to 4)
* Open database, copy username / password, open URI / URL
* Fingerprint for fast unlocking
* Material design with themes
* AutoFill and Integration (Development in progress)
* Precise management of settings
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen0.jpg" width="220">
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen2.jpg" width="220">
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen1.jpg" width="220">
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen2.jpg" width="220">
## What is KeePass?
@@ -27,22 +29,22 @@ Yes, KeePass is really free, and more than that: it is open source (OSI certifie
Even if the application is free, to maintain the application, you can make donations.
[![Donation Paypal](https://4.bp.blogspot.com/-ncaIbUGaHOk/WfhaThYUPGI/AAAAAAAAAVQ/_HidNgdB1q4DaC24ujaKNzH64KUUJiQewCLcBGAs/s1600/pay-with-paypal.png "")](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KM6QMDAXZM3UU "Kunzisoft Paypal Donation")
[![Donation Paypal](https://4.bp.blogspot.com/-ncaIbUGaHOk/WfhaThYUPGI/AAAAAAAAAVQ/_HidNgdB1q4DaC24ujaKNzH64KUUJiQewCLcBGAs/s1600/pay-with-paypal.png)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=KM6QMDAXZM3UU "Kunzisoft Paypal Donation")
[![Donation Liberapay](https://liberapay.com/assets/widgets/donate.svg "")](https://liberapay.com/Kunzisoft/donate "Kunzisoft Liberapay Donation")
[![Donation Liberapay](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Kunzisoft/donate "Kunzisoft Liberapay Donation")
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen4.jpg" width="220">
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen5.jpg" width="220">
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.jpg" width="220">
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen5.jpg" width="220">
### JNI
## Download
Native library build instructions:
1. Make sure you have the latest MIPS Android NDK installed:
https://developer.android.com/tools/sdk/ndk/index.html
2. From KeePassDroid/app/src/main/jni, call prep_build.sh to download and unpack the crypto sources.
3. The standard gradle files build everything now.
[<img src="https://f-droid.org/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/)
This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png"
alt="Get it on Google Play"
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
## License
@@ -62,3 +64,5 @@ This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.

View File

@@ -1,18 +1,24 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion = 25
buildToolsVersion = "26.0.2"
compileSdkVersion = 27
buildToolsVersion = "27.0.3"
defaultConfig {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 25
versionCode = 2
versionName = "2.5.0.0beta2"
targetSdkVersion 27
versionCode = 5
versionName = "2.5.0.0beta5"
multiDexEnabled true
testApplicationId = "com.keepassdroid.tests"
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
ndk {
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
'arm64-v8a', 'mips', 'mips64'
}
}
externalNativeBuild {
@@ -21,6 +27,7 @@ android {
}
}
buildTypes {
release {
minifyEnabled = false
@@ -52,18 +59,34 @@ android {
buildConfigField "boolean", "GOOGLE_PLAY_VERSION", "true"
}
}
compileOptions {
targetCompatibility 1.8
sourceCompatibility 1.8
}
}
def supportVersion = "25.4.0"
def supportVersion = "27.1.0"
def spongycastleVersion = "1.58.0.0"
def permissionDispatcherVersion = "3.1.0"
dependencies {
androidTestCompile "junit:junit:4.12"
compile "com.android.support:appcompat-v7:$supportVersion"
compile "com.android.support:design:$supportVersion"
compile "com.android.support:preference-v7:$supportVersion"
compile "com.android.support:preference-v14:$supportVersion"
compile "com.madgag.spongycastle:core:$spongycastleVersion"
compile "com.madgag.spongycastle:prov:$spongycastleVersion"
compile "joda-time:joda-time:2.9.9"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:preference-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:cardview-v7:$supportVersion"
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
// Time
implementation "joda-time:joda-time:2.9.9"
implementation "org.sufficientlysecure:html-textview:3.5"
implementation "com.nononsenseapps:filepicker:4.1.0"
// Permissions
implementation ("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
// if you don't use android.app.Fragment you can exclude support for them
exclude module: "support-v13"
}
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.1'
implementation group: 'com.google.guava', name: 'guava', version: '23.0-android'
}

View File

@@ -50,7 +50,7 @@ public class PwEntryTestV4 extends TestCase {
entry.icon = new PwIconStandard(5);
entry.overrideURL = "override";
entry.parent = new PwGroupV4();
entry.strings.put("key2", new ProtectedString(false, "value2"));
entry.addField("key2", new ProtectedString(false, "value2"));
entry.url = "http://localhost";
entry.uuid = UUID.randomUUID();

View File

@@ -24,7 +24,7 @@ import java.util.List;
import android.content.Context;
import android.test.AndroidTestCase;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwEntry;
@@ -74,8 +74,8 @@ public class DeleteEntry extends AndroidTestCase {
PwGroup results1 = dbHelp.search(db, ENTRY1_NAME);
PwGroup results2 = dbHelp.search(db, ENTRY2_NAME);
assertEquals("Entry1 was not removed from the search results", 0, results1.childEntries.size());
assertEquals("Entry2 was not removed from the search results", 0, results2.childEntries.size());
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
// Verify the group was deleted
group1 = getGroup(pm, GROUP1_NAME);

View File

@@ -25,12 +25,10 @@ import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabaseV3Debug;
import com.keepassdroid.database.load.Importer;
import com.keepassdroid.tests.TestUtil;
import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.UriUtil;
public class TestData {
private static final String TEST1_KEYFILE = "";

View File

@@ -26,7 +26,7 @@ import android.preference.PreferenceManager;
import android.test.AndroidTestCase;
import com.kunzisoft.keepass.R;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.tests.database.TestData;
@@ -43,7 +43,7 @@ public class SearchTest extends AndroidTestCase {
public void testSearch() {
PwGroup results = mDb.Search("Amazon");
assertTrue("Search result not found.", results.childEntries.size() > 0);
assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
}
@@ -51,14 +51,14 @@ public class SearchTest extends AndroidTestCase {
updateOmitSetting(false);
PwGroup results = mDb.Search("BackupOnly");
assertTrue("Search result not found.", results.childEntries.size() > 0);
assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
}
public void testBackupExcluded() {
updateOmitSetting(true);
PwGroup results = mDb.Search("BackupOnly");
assertFalse("Search result found, but should not have been.", results.childEntries.size() > 0);
assertFalse("Search result found, but should not have been.", results.numbersOfChildEntries() > 0);
}
private void updateOmitSetting(boolean setting) {
@@ -66,7 +66,7 @@ public class SearchTest extends AndroidTestCase {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(ctx.getString(R.string.omitbackup_key), setting);
editor.putBoolean(ctx.getString(R.string.settings_omitbackup_key), setting);
editor.commit();
}

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kunzisoft.keepass"
android:installLocation="auto">
xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass"
android:installLocation="auto">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
@@ -19,10 +20,32 @@
android:allowBackup="true"
android:fullBackupContent="@xml/backup"
android:backupAgent="com.keepassdroid.backup.SettingsBackupAgent"
android:theme="@style/KeepassDXStyle.Light">
android:theme="@style/KeepassDXStyle.Light"
tools:replace="android:theme">
<!-- TODO backup API Key -->
<meta-data android:name="com.google.android.backup.api_key"
android:value="" />
<meta-data
android:name="com.google.android.backup.api_key"
android:value="" />
<!-- Folder picker -->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/nnf_provider_paths" />
</provider>
<activity
android:name="com.keepassdroid.fileselect.FilePickerStylishActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".KeePass"
android:label="@string/app_name">
<intent-filter>
@@ -30,18 +53,26 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.keepassdroid.fileselect.FileSelectActivity"
<activity
android:name="com.keepassdroid.fileselect.FileSelectActivity"
android:launchMode="singleInstance"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
<activity android:name="com.keepassdroid.AboutActivity"
<activity
android:name="com.keepassdroid.activities.AboutActivity"
android:launchMode="singleInstance"
android:label="@string/menu_about" />
<activity android:name="com.keepassdroid.PasswordActivity" android:configChanges="orientation|keyboardHidden">
<activity
android:name="com.keepassdroid.password.PasswordActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:mimeType="*/*" />
<data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:host="*" />
<data android:pathPattern=".*\\.kdb" />
<data android:pathPattern=".*\\..*\\.kdb" />
@@ -64,46 +95,58 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
</intent-filter>
</activity>
<activity android:name="com.keepassdroid.GroupActivityV3" android:configChanges="orientation|keyboardHidden">
<!-- This metadata entry causes .app.SearchQueryResults to be the default context -->
<!-- whenever the user invokes search while in this Activity. -->
<meta-data android:name="android.app.default_searchable"
android:value="com.keepassdroid.search.SearchResults" />
</activity>
<activity android:name="com.keepassdroid.GroupActivityV4" android:configChanges="orientation|keyboardHidden">
<!-- This metadata entry causes .app.SearchQueryResults to be the default context -->
<!-- whenever the user invokes search while in this Activity. -->
<meta-data android:name="android.app.default_searchable"
android:value="com.keepassdroid.search.SearchResults"
android:exported="false" />
<intent-filter tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="application/octet-stream"/>
</intent-filter>
</activity>
<activity
android:name="com.keepassdroid.EntryActivity"
android:name="com.keepassdroid.activities.GroupActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustPan">
<meta-data
android:name="android.app.default_searchable"
android:value="com.keepassdroid.search.SearchResults"
android:exported="false"/>
</activity>
<activity
android:name="com.keepassdroid.activities.EntryActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="com.keepassdroid.EntryActivityV4"
android:name="com.keepassdroid.activities.EntryEditActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="com.keepassdroid.EntryEditActivityV3"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="com.keepassdroid.EntryEditActivityV4"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
<activity android:name="com.keepassdroid.search.SearchResultsActivity" android:launchMode="standard">
android:name="com.keepassdroid.search.SearchResultsActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity android:name="com.keepassdroid.settings.SettingsActivity" />
<activity android:name="com.keepassdroid.autofill.AutoFillAuthActivity"
android:configChanges="orientation|keyboardHidden" />
<service android:name="com.keepassdroid.services.TimeoutService" />
<service
android:name="com.keepassdroid.autofill.KeeAutofillService"
android:label="@string/autofill_service_name"
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
<meta-data
android:name="android.autofill"
android:resource="@xml/dataset_service" />
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>
</service>
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
</application>
</manifest>

View File

@@ -1,42 +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 2 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.keepassdroid;
import android.app.Dialog;
import android.content.Context;
public class CancelDialog extends Dialog {
private boolean mCanceled = false;
public CancelDialog(Context context) {
super(context);
}
public boolean canceled() {
return mCanceled;
}
@Override
public void cancel() {
super.cancel();
mCanceled = true;
}
}

View File

@@ -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 2 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.keepassdroid;
import java.util.Map;
import android.view.View;
import android.view.ViewGroup;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.security.ProtectedString;
import com.keepassdroid.utils.SprEngine;
import com.keepassdroid.utils.SprEngineV4;
import com.keepassdroid.view.EntrySection;
public class EntryActivityV4 extends EntryActivity {
@Override
protected void setEntryView() {
setContentView(R.layout.entry_view);
}
@Override
protected void fillData(boolean trimList) {
super.fillData(trimList);
ViewGroup group = (ViewGroup) findViewById(R.id.extra_strings);
if (trimList) {
group.removeAllViews();
}
PwEntryV4 entry = (PwEntryV4) mEntry;
PwDatabase pm = App.getDB().pm;
SprEngine spr = SprEngineV4.getInstance(pm);
// Display custom strings
if (entry.strings.size() > 0) {
for (Map.Entry<String, ProtectedString> pair : entry.strings.entrySet()) {
String key = pair.getKey();
if (!PwEntryV4.IsStandardString(key)) {
String text = pair.getValue().toString();
View view = new EntrySection(this, null, key, spr.compile(text, entry, pm));
group.addView(view);
}
}
}
}
}

View File

@@ -1,328 +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 2 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.keepassdroid;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.kunzisoft.keepass.KeePass;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV3;
import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwGroupV3;
import com.keepassdroid.database.PwGroupV4;
import com.keepassdroid.database.PwIconStandard;
import com.keepassdroid.database.edit.AddEntry;
import com.keepassdroid.database.edit.OnFinish;
import com.keepassdroid.database.edit.RunnableOnFinish;
import com.keepassdroid.database.edit.UpdateEntry;
import com.keepassdroid.icons.Icons;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.Types;
import com.keepassdroid.utils.Util;
import java.util.Calendar;
import java.util.Date;
import java.util.UUID;
public abstract class EntryEditActivity extends LockCloseHideActivity
implements IconPickerFragment.IconPickerListener,
GeneratePasswordFragment.GeneratePasswordListener {
public static final String KEY_ENTRY = "entry";
public static final String KEY_PARENT = "parent";
protected PwEntry mEntry;
protected boolean mIsNew;
protected int mSelectedIconID = -1;
public static void Launch(Activity act, PwEntry pw) {
Intent i;
if (pw instanceof PwEntryV3) {
i = new Intent(act, EntryEditActivityV3.class);
}
else if (pw instanceof PwEntryV4) {
i = new Intent(act, EntryEditActivityV4.class);
}
else {
throw new RuntimeException("Not yet implemented.");
}
i.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
act.startActivityForResult(i, 0);
}
public static void Launch(Activity act, PwGroup pw) {
Intent i;
if (pw instanceof PwGroupV3) {
i = new Intent(act, EntryEditActivityV3.class);
EntryEditActivityV3.putParentId(i, KEY_PARENT, (PwGroupV3)pw);
}
else if (pw instanceof PwGroupV4) {
i = new Intent(act, EntryEditActivityV4.class);
EntryEditActivityV4.putParentId(i, KEY_PARENT, (PwGroupV4)pw);
}
else {
throw new RuntimeException("Not yet implemented.");
}
act.startActivityForResult(i, 0);
}
protected abstract PwGroupId getParentGroupId(Intent i, String key);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_edit);
setResult(KeePass.EXIT_NORMAL);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
// Likely the app has been killed exit the activity
Database db = App.getDB();
if ( ! db.Loaded() ) {
finish();
return;
}
Intent i = getIntent();
byte[] uuidBytes = i.getByteArrayExtra(KEY_ENTRY);
PwDatabase pm = db.pm;
if ( uuidBytes == null ) {
PwGroupId parentId = getParentGroupId(i, KEY_PARENT);
PwGroup parent = pm.groups.get(parentId);
mEntry = PwEntry.getInstance(parent);
mIsNew = true;
} else {
UUID uuid = Types.bytestoUUID(uuidBytes);
mEntry = pm.entries.get(uuid);
mIsNew = false;
fillData();
}
View scrollView = findViewById(R.id.entry_scroll);
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
View iconButton = findViewById(R.id.icon_button);
iconButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
IconPickerFragment.Launch(EntryEditActivity.this);
}
});
// Generate password button
View generatePassword = findViewById(R.id.generate_button);
generatePassword.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
GeneratePasswordFragment generatePasswordFragment = new GeneratePasswordFragment();
generatePasswordFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
}
});
// Save button
View save = findViewById(R.id.entry_save);
save.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
EntryEditActivity act = EntryEditActivity.this;
if (!validateBeforeSaving()) {
return;
}
PwEntry newEntry = populateNewEntry();
if ( newEntry.getTitle().equals(mEntry.getTitle()) ) {
setResult(KeePass.EXIT_REFRESH);
} else {
setResult(KeePass.EXIT_REFRESH_TITLE);
}
RunnableOnFinish task;
OnFinish onFinish = act.new AfterSave(new Handler());
if ( mIsNew ) {
task = AddEntry.getInstance(EntryEditActivity.this, App.getDB(), newEntry, onFinish);
} else {
task = new UpdateEntry(EntryEditActivity.this, App.getDB(), mEntry, newEntry, onFinish);
}
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
pt.run();
}
});
}
protected boolean validateBeforeSaving() {
// Require title
String title = Util.getEditText(this, R.id.entry_title);
if ( title.length() == 0 ) {
Toast.makeText(this, R.string.error_title_required, Toast.LENGTH_LONG).show();
return false;
}
// Validate password
String pass = Util.getEditText(this, R.id.entry_password);
String conf = Util.getEditText(this, R.id.entry_confpassword);
if ( ! pass.equals(conf) ) {
Toast.makeText(this, R.string.error_pass_match, Toast.LENGTH_LONG).show();
return false;
}
return true;
}
protected PwEntry populateNewEntry() {
return populateNewEntry(null);
}
protected PwEntry populateNewEntry(PwEntry entry) {
PwEntry newEntry;
if (entry == null) {
newEntry = mEntry.clone(true);
}
else {
newEntry = entry;
}
Date now = Calendar.getInstance().getTime();
newEntry.setLastAccessTime(now);
newEntry.setLastModificationTime(now);
PwDatabase db = App.getDB().pm;
newEntry.setTitle(Util.getEditText(this, R.id.entry_title), db);
if(mSelectedIconID != -1)
newEntry.setIcon(new PwIconStandard(mSelectedIconID));
newEntry.setUrl(Util.getEditText(this, R.id.entry_url), db);
newEntry.setUsername(Util.getEditText(this, R.id.entry_user_name), db);
newEntry.setNotes(Util.getEditText(this, R.id.entry_comment), db);
newEntry.setPassword(Util.getEditText(this, R.id.entry_password), db);
return newEntry;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.donationMenuInflater(inflater, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_donate:
return MenuUtil.onDonationItemSelected(this);
case android.R.id.home:
finish();
}
return super.onOptionsItemSelected(item);
}
protected void fillData() {
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
App.getDB().drawFactory.assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
populateText(R.id.entry_title, mEntry.getTitle());
populateText(R.id.entry_user_name, mEntry.getUsername());
populateText(R.id.entry_url, mEntry.getUrl());
String password = mEntry.getPassword();
populateText(R.id.entry_password, password);
populateText(R.id.entry_confpassword, password);
populateText(R.id.entry_comment, mEntry.getNotes());
}
private void populateText(int viewId, String text) {
TextView tv = (TextView) findViewById(viewId);
tv.setText(text);
}
@Override
public void iconPicked(Bundle bundle) {
mSelectedIconID = bundle.getInt(IconPickerFragment.KEY_ICON_ID);
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
}
@Override
public void acceptPassword(Bundle bundle) {
String generatedPassword = bundle.getString(GeneratePasswordFragment.KEY_PASSWORD_ID);
EditText password = (EditText) findViewById(R.id.entry_password);
EditText confPassword = (EditText) findViewById(R.id.entry_confpassword);
password.setText(generatedPassword);
confPassword.setText(generatedPassword);
}
@Override
public void cancelPassword(Bundle bundle) {
// Do nothing here
}
private final class AfterSave extends OnFinish {
AfterSave(Handler handler) {
super(handler);
}
@Override
public void run() {
if ( mSuccess ) {
finish();
} else {
displayMessage(EntryEditActivity.this);
}
}
}
}

View File

@@ -1,62 +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 2 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.keepassdroid;
import android.content.Intent;
import com.keepassdroid.app.App;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwGroupIdV3;
import com.keepassdroid.database.PwGroupV3;
public class EntryEditActivityV3 extends EntryEditActivity {
@Override
protected PwEntry populateNewEntry(PwEntry entry) {
PwEntry newEntry = super.populateNewEntry(entry);
if (mSelectedIconID == -1) {
if (mIsNew) {
newEntry.icon = App.getDB().pm.iconFactory.getIcon(0);
}
else {
// Keep previous icon, if no new one was selected
newEntry.icon = mEntry.icon;
}
}
else {
newEntry.icon = App.getDB().pm.iconFactory.getIcon(mSelectedIconID);
}
return newEntry;
}
protected static void putParentId(Intent i, String parentKey, PwGroupV3 parent) {
i.putExtra(parentKey, parent.groupId);
}
@Override
protected PwGroupId getParentGroupId(Intent i, String key) {
int groupId = i.getIntExtra(key, -1);
return new PwGroupIdV3(groupId);
}
}

View File

@@ -1,206 +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 2 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.keepassdroid;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.database.PwDatabaseV4;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwGroupIdV4;
import com.keepassdroid.database.PwGroupV4;
import com.keepassdroid.database.security.ProtectedString;
import com.keepassdroid.utils.Types;
import com.keepassdroid.view.EntryEditSection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
public class EntryEditActivityV4 extends EntryEditActivity {
private ScrollView scroll;
private LayoutInflater inflater;
protected static void putParentId(Intent i, String parentKey, PwGroupV4 parent) {
PwGroupId id = parent.getId();
PwGroupIdV4 id4 = (PwGroupIdV4) id;
i.putExtra(parentKey, Types.UUIDtoBytes(id4.getId()));
}
@Override
protected PwGroupId getParentGroupId(Intent i, String key) {
byte[] buf = i.getByteArrayExtra(key);
UUID id = Types.bytestoUUID(buf);
return new PwGroupIdV4(id);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
super.onCreate(savedInstanceState);
scroll = (ScrollView) findViewById(R.id.entry_scroll);
View add = findViewById(R.id.add_advanced);
add.setVisibility(View.VISIBLE);
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
EntryEditSection ees = (EntryEditSection) inflater.inflate(R.layout.entry_edit_section, container, false);
ees.setData("", new ProtectedString(false, ""));
container.addView(ees);
// Scroll bottom
scroll.post(new Runnable() {
@Override
public void run() {
scroll.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
});
}
@Override
protected void fillData() {
super.fillData();
PwEntryV4 entry = (PwEntryV4) mEntry;
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
if (entry.strings.size() > 0) {
for (Entry<String, ProtectedString> pair : entry.strings.entrySet()) {
String key = pair.getKey();
if (!PwEntryV4.IsStandardString(key)) {
EntryEditSection ees = (EntryEditSection) inflater.inflate(R.layout.entry_edit_section, container, false);
ees.setData(key, pair.getValue());
container.addView(ees);
}
}
}
}
@SuppressWarnings("unchecked")
@Override
protected PwEntry populateNewEntry() {
PwEntryV4 newEntry = (PwEntryV4) mEntry.clone(true);
newEntry.history = (ArrayList<PwEntryV4>) newEntry.history.clone();
newEntry.createBackup((PwDatabaseV4)App.getDB().pm);
newEntry = (PwEntryV4) super.populateNewEntry(newEntry);
Map<String, ProtectedString> strings = newEntry.strings;
// Delete all new standard strings
Iterator<Entry<String, ProtectedString>> iter = strings.entrySet().iterator();
while (iter.hasNext()) {
Entry<String, ProtectedString> pair = iter.next();
if (!PwEntryV4.IsStandardString(pair.getKey())) {
iter.remove();
}
}
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
for (int i = 0; i < container.getChildCount(); i++) {
View view = container.getChildAt(i);
TextView keyView = (TextView)view.findViewById(R.id.title);
String key = keyView.getText().toString();
TextView valueView = (TextView)view.findViewById(R.id.value);
String value = valueView.getText().toString();
CheckBox cb = (CheckBox)view.findViewById(R.id.protection);
boolean protect = cb.isChecked();
strings.put(key, new ProtectedString(protect, value));
}
return newEntry;
}
public void deleteAdvancedString(View view) {
ViewGroup section = (ViewGroup) view.getParent();
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
for (int i = 0; i < container.getChildCount(); i++) {
ViewGroup ees = (ViewGroup) container.getChildAt(i);
if (ees == section) {
container.removeViewAt(i);
container.invalidate();
break;
}
}
}
@Override
protected boolean validateBeforeSaving() {
if(!super.validateBeforeSaving()) {
return false;
}
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
for (int i = 0; i < container.getChildCount(); i++) {
EntryEditSection ees = (EntryEditSection) container.getChildAt(i);
TextView keyView = (TextView) ees.findViewById(R.id.title);
CharSequence key = keyView.getText();
if (key == null || key.length() == 0) {
Toast.makeText(this, R.string.error_string_key, Toast.LENGTH_LONG).show();
return false;
}
}
return true;
}
}

View File

@@ -1,256 +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 2 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.keepassdroid;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import com.kunzisoft.keepass.KeePass;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwDatabaseV4;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwGroupV3;
import com.keepassdroid.database.PwGroupV4;
import com.keepassdroid.database.edit.AddGroup;
import com.keepassdroid.dialog.ReadOnlyDialog;
import com.keepassdroid.view.ClickView;
import com.keepassdroid.view.GroupAddEntryView;
import com.keepassdroid.view.GroupRootView;
import com.keepassdroid.view.GroupViewOnlyView;
public abstract class GroupActivity extends GroupBaseActivity
implements GroupEditFragment.CreateGroupListener, IconPickerFragment.IconPickerListener {
private static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
protected boolean addGroupEnabled = false;
protected boolean addEntryEnabled = false;
protected boolean isRoot = false;
protected boolean readOnly = false;
private static final String TAG = "Group Activity:";
public static void Launch(Activity act) {
Launch(act, null);
}
public static void Launch(Activity act, PwGroup group) {
Intent i;
// Need to use PwDatabase since tree may be null
PwDatabase db = App.getDB().pm;
if ( db instanceof PwDatabaseV3 ) {
i = new Intent(act, GroupActivityV3.class);
if ( group != null ) {
PwGroupV3 g = (PwGroupV3) group;
i.putExtra(KEY_ENTRY, g.groupId);
}
} else if ( db instanceof PwDatabaseV4 ) {
i = new Intent(act, GroupActivityV4.class);
if ( group != null ) {
PwGroupV4 g = (PwGroupV4) group;
i.putExtra(KEY_ENTRY, g.uuid.toString());
}
} else {
// Reached if db is null
Log.d(TAG, "Tried to launch with null db");
return;
}
act.startActivityForResult(i,0);
}
protected abstract PwGroupId retrieveGroupId(Intent i);
protected void setupButtons() {
addGroupEnabled = !readOnly;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( isFinishing() ) {
return;
}
setResult(KeePass.EXIT_NORMAL);
Log.w(TAG, "Creating tree view");
Intent intent = getIntent();
PwGroupId id = retrieveGroupId(intent);
Database db = App.getDB();
readOnly = db.readOnly;
PwGroup root = db.pm.rootGroup;
if ( id == null ) {
mGroup = root;
} else {
mGroup = db.pm.groups.get(id);
}
Log.w(TAG, "Retrieved tree");
if ( mGroup == null ) {
Log.w(TAG, "Group was null");
return;
}
isRoot = mGroup == root;
setupButtons();
if ( addGroupEnabled && addEntryEnabled ) {
setContentView(new GroupAddEntryView(this));
} else if ( addGroupEnabled ) {
setContentView(new GroupRootView(this));
} else if ( addEntryEnabled ) {
setContentView(new GroupAddEntryView(this));
View addGroup = findViewById(R.id.add_group);
addGroup.setVisibility(View.GONE);
} else {
setContentView(new GroupViewOnlyView(this));
}
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle("");
setSupportActionBar(toolbar);
if ( mGroup.getParent() != null )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
Log.w(TAG, "Set view");
if ( addGroupEnabled ) {
// Add Group button
View addGroup = findViewById(R.id.add_group);
addGroup.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GroupEditFragment groupEditFragment = new GroupEditFragment();
groupEditFragment.show(getSupportFragmentManager(), TAG_CREATE_GROUP);
}
});
}
if ( addEntryEnabled ) {
// Add Entry button
View addEntry = findViewById(R.id.add_entry);
addEntry.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
EntryEditActivity.Launch(GroupActivity.this, mGroup);
}
});
}
setGroupTitle();
setGroupIcon();
setListAdapter(new PwGroupListAdapter(this, mGroup));
registerForContextMenu(getListView());
Log.w(TAG, "Finished creating tree");
if (isRoot) {
showWarnings();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
ClickView cv = (ClickView) acmi.targetView;
cv.onCreateMenu(menu, menuInfo);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) item.getMenuInfo();
ClickView cv = (ClickView) acmi.targetView;
return cv.onContextItemSelected(item);
}
@Override
public void approveCreateGroup(Bundle bundle) {
String GroupName = bundle.getString(GroupEditFragment.KEY_NAME);
int GroupIconID = bundle.getInt(GroupEditFragment.KEY_ICON_ID);
GroupActivity act = GroupActivity.this;
Handler handler = new Handler();
AddGroup task = AddGroup.getInstance(this, App.getDB(), GroupName, GroupIconID, mGroup, act.new RefreshTask(handler), false);
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
pt.run();
}
@Override
public void cancelCreateGroup(Bundle bundle) {
// Do nothing here
}
@Override
// For icon in create tree dialog
public void iconPicked(Bundle bundle) {
GroupEditFragment groupEditFragment = (GroupEditFragment) getSupportFragmentManager().findFragmentByTag(TAG_CREATE_GROUP);
if (groupEditFragment != null) {
groupEditFragment.iconPicked(bundle);
}
}
protected void showWarnings() {
if (App.getDB().readOnly) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs.getBoolean(getString(R.string.show_read_only_warning), true)) {
Dialog dialog = new ReadOnlyDialog(this);
dialog.show();
}
}
}
}

View File

@@ -1,44 +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 2 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.keepassdroid;
import android.content.Intent;
import com.keepassdroid.database.PwGroupIdV3;
public class GroupActivityV3 extends GroupActivity {
@Override
protected PwGroupIdV3 retrieveGroupId(Intent i) {
int id = i.getIntExtra(KEY_ENTRY, -1);
if ( id == -1 ) {
return null;
}
return new PwGroupIdV3(id);
}
@Override
protected void setupButtons() {
super.setupButtons();
addEntryEnabled = !isRoot && !readOnly;
}
}

View File

@@ -1,47 +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 2 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.keepassdroid;
import java.util.UUID;
import android.content.Intent;
import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwGroupIdV4;
public class GroupActivityV4 extends GroupActivity {
@Override
protected PwGroupId retrieveGroupId(Intent i) {
String uuid = i.getStringExtra(KEY_ENTRY);
if ( uuid == null || uuid.length() == 0 ) {
return null;
}
return new PwGroupIdV4(UUID.fromString(uuid));
}
@Override
protected void setupButtons() {
super.setupButtons();
addEntryEnabled = !readOnly;
}
}

View File

@@ -1,304 +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 2 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.keepassdroid;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.kunzisoft.keepass.KeePass;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.compat.ActivityCompat;
import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.edit.OnFinish;
import com.keepassdroid.search.SearchResultsActivity;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.view.ClickView;
import com.keepassdroid.view.GroupViewOnlyView;
public abstract class GroupBaseActivity extends LockCloseListActivity {
protected ListView mList;
protected ListAdapter mAdapter;
public static final String KEY_ENTRY = "entry";
private SharedPreferences prefs;
protected PwGroup mGroup;
@Override
protected void onResume() {
super.onResume();
refreshIfDirty();
}
public void refreshIfDirty() {
Database db = App.getDB();
if ( db.dirty.contains(mGroup) ) {
db.dirty.remove(mGroup);
((BaseAdapter) mAdapter).notifyDataSetChanged();
}
}
protected void onListItemClick(ListView l, View v, int position, long id) {
ClickView cv = (ClickView) mAdapter.getView(position, null, null);
cv.onClick();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Likely the app has been killed exit the activity
if ( ! App.getDB().Loaded() ) {
finish();
return;
}
prefs = PreferenceManager.getDefaultSharedPreferences(this);
ActivityCompat.invalidateOptionsMenu(this);
setContentView(new GroupViewOnlyView(this));
setResult(KeePass.EXIT_NORMAL);
styleScrollBars();
}
protected void styleScrollBars() {
ensureCorrectListView();
mList.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
mList.setTextFilterEnabled(true);
}
protected void setGroupTitle() {
if ( mGroup != null ) {
String name = mGroup.getName();
TextView tv = (TextView) findViewById(R.id.group_name);
if ( name != null && name.length() > 0 ) {
if ( tv != null ) {
tv.setText(name);
}
} else {
if ( tv != null ) {
tv.setText(getText(R.string.root));
}
}
}
}
protected void setGroupIcon() {
if (mGroup != null) {
ImageView iv = (ImageView) findViewById(R.id.icon);
App.getDB().drawFactory.assignDrawableTo(iv, getResources(), mGroup.getIcon());
}
}
protected void setListAdapter(ListAdapter adapter) {
ensureCorrectListView();
mAdapter = adapter;
mList.setAdapter(adapter);
}
protected ListView getListView() {
ensureCorrectListView();
return mList;
}
private void ensureCorrectListView(){
mList = (ListView)findViewById(R.id.group_list);
mList.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id)
{
onListItemClick((ListView)parent, v, position, id);
}
}
);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search, menu);
MenuUtil.donationMenuInflater(inflater, menu);
inflater.inflate(R.menu.tree, menu);
inflater.inflate(R.menu.database, menu);
inflater.inflate(R.menu.default_menu, menu);
// Get the SearchView and set the searchable configuration
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
assert searchManager != null;
MenuItem searchItem = menu.findItem(R.id.menu_search);
SearchView searchView = null;
if (searchItem != null) {
searchView = (SearchView) searchItem.getActionView();
}
if (searchView != null) {
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
}
return true;
}
private void setSortMenuText(Menu menu) {
boolean sortByName = false;
// Will be null if onPrepareOptionsMenu is called before onCreate
if (prefs != null) {
sortByName = prefs.getBoolean(getString(R.string.sort_key), getResources().getBoolean(R.bool.sort_default));
}
int resId;
if ( sortByName ) {
resId = R.string.sort_db;
} else {
resId = R.string.sort_name;
}
menu.findItem(R.id.menu_sort).setTitle(resId);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if ( ! super.onPrepareOptionsMenu(menu) ) {
return false;
}
setSortMenuText(menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_search:
onSearchRequested();
return true;
case R.id.menu_sort:
toggleSort();
return true;
case R.id.menu_lock:
App.setShutdown();
setResult(KeePass.EXIT_LOCK);
finish();
return true;
case R.id.menu_change_master_key:
setPassword();
return true;
default:
MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
return super.onOptionsItemSelected(item);
}
}
private void toggleSort() {
// Toggle setting
String sortKey = getString(R.string.sort_key);
boolean sortByName = prefs.getBoolean(sortKey, getResources().getBoolean(R.bool.sort_default));
Editor editor = prefs.edit();
editor.putBoolean(sortKey, ! sortByName);
EditorCompat.apply(editor);
// Refresh menu titles
ActivityCompat.invalidateOptionsMenu(this);
// Mark all groups as dirty now to refresh them on load
Database db = App.getDB();
db.markAllGroupsAsDirty();
// We'll manually refresh this tree so we can remove it
db.dirty.remove(mGroup);
// Tell the adapter to refresh it's list
((BaseAdapter) mAdapter).notifyDataSetChanged();
}
private void setPassword() {
SetPasswordDialog dialog = new SetPasswordDialog();
dialog.show(getSupportFragmentManager(), "passwordDialog");
}
public class RefreshTask extends OnFinish {
public RefreshTask(Handler handler) {
super(handler);
}
@Override
public void run() {
if ( mSuccess) {
refreshIfDirty();
} else {
displayMessage(GroupBaseActivity.this);
}
}
}
public class AfterDeleteGroup extends OnFinish {
public AfterDeleteGroup(Handler handler) {
super(handler);
}
@Override
public void run() {
if ( mSuccess) {
refreshIfDirty();
} else {
mHandler.post(new UIToastTask(GroupBaseActivity.this, "Unrecoverable error: " + mMessage));
App.setShutdown();
finish();
}
}
}
}

View File

@@ -1,47 +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 2 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.keepassdroid;
import com.keepassdroid.compat.BuildCompat;
import android.os.Build;
import android.os.Bundle;
import android.view.WindowManager.LayoutParams;
/**
* Locking Close Activity that sets FLAG_SECURE to prevent screenshots, and from
* appearing in the recent app preview
* @author Brian Pellin
*
*/
public abstract class LockCloseHideActivity extends LockCloseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Several gingerbread devices have problems with FLAG_SECURE
int ver = BuildCompat.getSdkVersion();
if (ver >= BuildCompat.VERSION_CODE_ICE_CREAM_SANDWICH || ver < BuildCompat.VERSION_CODE_GINGERBREAD) {
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
}
}
}

View File

@@ -1,32 +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 2 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.keepassdroid;
import com.keepassdroid.timeout.TimeoutHelper;
public abstract class LockCloseListActivity extends LockingActivity {
@Override
protected void onResume() {
super.onResume();
TimeoutHelper.checkShutdown(this);
}
}

View File

@@ -1,42 +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 2 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.keepassdroid;
import com.keepassdroid.stylish.StylishActivity;
import com.keepassdroid.timeout.TimeoutHelper;
public abstract class LockingActivity extends StylishActivity {
@Override
protected void onPause() {
super.onPause();
TimeoutHelper.pause(this);
}
@Override
protected void onResume() {
super.onResume();
TimeoutHelper.resume(this);
}
}

View File

@@ -1,728 +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 2 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.keepassdroid;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.RequiresApi;
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.kunzisoft.keepass.KeePass;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.compat.BackupManagerCompat;
import com.keepassdroid.compat.ClipDataCompat;
import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.compat.StorageAF;
import com.keepassdroid.database.edit.LoadDB;
import com.keepassdroid.database.edit.OnFinish;
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
import com.keepassdroid.fileselect.BrowserDialog;
import com.keepassdroid.fingerprint.FingerPrintHelper;
import com.keepassdroid.intents.Intents;
import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.Interaction;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.UriUtil;
import com.keepassdroid.utils.Util;
import java.io.File;
import java.io.FileNotFoundException;
import javax.crypto.Cipher;
public class PasswordActivity extends LockingActivity implements FingerPrintHelper.FingerPrintCallback {
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
private static final String KEY_FILENAME = "fileName";
private static final String KEY_KEYFILE = "keyFile";
private static final String KEY_PASSWORD = "password";
private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately";
private static final String VIEW_INTENT = "android.intent.action.VIEW";
private static final int FILE_BROWSE = 256;
public static final int GET_CONTENT = 257;
private static final int OPEN_DOC = 258;
private Uri mDbUri = null;
private Uri mKeyUri = null;
private boolean mRememberKeyfile;
SharedPreferences prefs;
SharedPreferences prefsNoBackup;
private FingerPrintHelper fingerPrintHelper;
private boolean fingerprintMustBeConfigured = true;
private int mode;
private static final String PREF_KEY_VALUE_PREFIX = "valueFor_"; // key is a combination of db file name and this prefix
private static final String PREF_KEY_IV_PREFIX = "ivFor_"; // key is a combination of db file name and this prefix
private View fingerprintView;
private TextView confirmationView;
private EditText passwordView;
private Button confirmButton;
public static void Launch(
Activity act,
String fileName) throws FileNotFoundException {
Launch(act, fileName, "");
}
public static void Launch(
Activity act,
String fileName,
String keyFile) throws FileNotFoundException {
if (EmptyUtils.isNullOrEmpty(fileName)) {
throw new FileNotFoundException();
}
Uri uri = UriUtil.parseDefaultFile(fileName);
String scheme = uri.getScheme();
if (!EmptyUtils.isNullOrEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
File dbFile = new File(uri.getPath());
if (!dbFile.exists()) {
throw new FileNotFoundException();
}
}
Intent i = new Intent(act, PasswordActivity.class);
i.putExtra(KEY_FILENAME, fileName);
i.putExtra(KEY_KEYFILE, keyFile);
act.startActivityForResult(i, 0);
}
@Override
protected void onActivityResult(
int requestCode,
int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case KeePass.EXIT_NORMAL:
setEditText(R.id.password, "");
App.getDB().clear();
break;
case KeePass.EXIT_LOCK:
setResult(KeePass.EXIT_LOCK);
setEditText(R.id.password, "");
finish();
App.getDB().clear();
break;
case FILE_BROWSE:
if (resultCode == RESULT_OK) {
String filename = data.getDataString();
if (filename != null) {
EditText fn = (EditText) findViewById(R.id.pass_keyfile);
fn.setText(filename);
mKeyUri = UriUtil.parseDefaultFile(filename);
}
}
break;
case GET_CONTENT:
case OPEN_DOC:
if (resultCode == RESULT_OK) {
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
if (requestCode == GET_CONTENT) {
uri = UriUtil.translate(this, uri);
}
String path = uri.toString();
if (path != null) {
EditText fn = (EditText) findViewById(R.id.pass_keyfile);
fn.setText(path);
}
mKeyUri = uri;
}
}
}
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent i = getIntent();
prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefsNoBackup = getSharedPreferences("nobackup", Context.MODE_PRIVATE);
mRememberKeyfile = prefs.getBoolean(getString(R.string.keyfile_key), getResources().getBoolean(R.bool.keyfile_default));
setContentView(R.layout.password);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
confirmButton = (Button) findViewById(R.id.pass_ok);
fingerprintView = findViewById(R.id.fingerprint);
confirmationView = (TextView) findViewById(R.id.fingerprint_label);
passwordView = (EditText) findViewById(R.id.password);
new InitTask().execute(i);
}
@Override
protected void onResume() {
super.onResume();
// If the application was shutdown make sure to clear the password field, if it
// was saved in the instance state
if (App.isShutdown()) {
TextView password = (TextView) findViewById(R.id.password);
password.setText("");
}
// Clear the shutdown flag
App.clearShutdown();
// checks if fingerprint is available, will also start listening for fingerprints when available
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
initForFingerprint();
checkAvailability();
}
}
private void retrieveSettings() {
String defaultFilename = prefs.getString(KEY_DEFAULT_FILENAME, "");
if (!EmptyUtils.isNullOrEmpty(mDbUri.getPath()) && UriUtil.equalsDefaultfile(mDbUri, defaultFilename)) {
CheckBox checkbox = (CheckBox) findViewById(R.id.default_database);
checkbox.setChecked(true);
}
}
private Uri getKeyFile(Uri dbUri) {
if (mRememberKeyfile) {
return App.getFileHistory().getFileByName(dbUri);
} else {
return null;
}
}
private void populateView() {
String db = (mDbUri == null) ? "" : mDbUri.toString();
setEditText(R.id.filename, db);
String key = (mKeyUri == null) ? "" : mKeyUri.toString();
setEditText(R.id.pass_keyfile, key);
}
private void errorMessage(int resId) {
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
}
// fingerprint related code here
@RequiresApi(api = Build.VERSION_CODES.M)
private void initForFingerprint() {
fingerPrintHelper = new FingerPrintHelper(this, this);
// when text entered we can enable the logon/purchase button and if required update encryption/decryption mode
passwordView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(
final CharSequence s,
final int start,
final int count,
final int after) {}
@Override
public void onTextChanged(
final CharSequence s,
final int start,
final int before,
final int count) {}
@Override
public void afterTextChanged(final Editable s) {
if ( !fingerprintMustBeConfigured ) {
final boolean validInput = s.length() > 0;
// encrypt or decrypt mode based on how much input or not
confirmationView.setText(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
mode = validInput ? toggleMode(Cipher.ENCRYPT_MODE) : toggleMode(Cipher.DECRYPT_MODE);
}
}
});
// callback for fingerprint findings
fingerPrintHelper.setAuthenticationCallback(new FingerprintManagerCompat.AuthenticationCallback() {
@Override
public void onAuthenticationError(
final int errorCode,
final CharSequence errString) {
// this is triggered on stop/start listening done by helper to switch between modes so don't restart here
// errorCode = 5
// errString = "Fingerprint operation canceled."
//onFingerprintException();
//confirmationView.setText(errString);
// true false fingerprint readings are handled otherwise with the toast messages, see below in code
}
@Override
public void onAuthenticationHelp(
final int helpCode,
final CharSequence helpString) {
onFingerprintException(new Exception("onAuthenticationHelp"));
confirmationView.setText(helpString);
}
@Override
public void onAuthenticationSucceeded(final FingerprintManagerCompat.AuthenticationResult result) {
if (mode == Cipher.ENCRYPT_MODE) {
// newly store the entered password in encrypted way
final String password = passwordView.getText().toString();
fingerPrintHelper.encryptData(password);
} else if (mode == Cipher.DECRYPT_MODE) {
// retrieve the encrypted value from preferences
final String encryptedValue = prefsNoBackup.getString(getPreferenceKeyValue(), null);
if (encryptedValue != null) {
fingerPrintHelper.decryptData(encryptedValue);
}
}
}
@Override
public void onAuthenticationFailed() {
onFingerprintException(new Exception("onAuthenticationFailed"));
}
});
}
private String getPreferenceKeyValue() {
// makes it possible to store passwords uniqly per database
return PREF_KEY_VALUE_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
}
private String getPreferenceKeyIvSpec() {
return PREF_KEY_IV_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
}
@RequiresApi(api = Build.VERSION_CODES.M)
private int toggleMode(final int newMode) {
mode = newMode;
switch (mode) {
case Cipher.ENCRYPT_MODE:
fingerPrintHelper.initEncryptData();
break;
case Cipher.DECRYPT_MODE:
final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null);
fingerPrintHelper.initDecryptData(ivSpecValue);
break;
}
return newMode;
}
@Override
protected void onPause() {
super.onPause();
// stop listening when we go in background
if (fingerPrintHelper != null) {
fingerPrintHelper.stopListening();
}
}
private void setFingerPrintVisibility(int vis) {
fingerprintView.setVisibility(vis);
confirmationView.setVisibility(vis);
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void checkAvailability() {
// fingerprint not supported (by API level or hardware) so keep option hidden
if (!fingerPrintHelper.isFingerprintSupported(FingerprintManagerCompat.from(this))) {
setFingerPrintVisibility(View.GONE);
}
// fingerprint is available but not configured show icon but in disabled state with some information
else if (!fingerPrintHelper.hasEnrolledFingerprints()) {
setFingerPrintVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
fingerprintView.setAlpha(0.3f);
}
// This happens when no fingerprints are registered. Listening won't start
confirmationView.setText(R.string.configure_fingerprint);
}
// finally fingerprint available and configured so we can use it
else {
fingerprintMustBeConfigured = false;
setFingerPrintVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
fingerprintView.setAlpha(1f);
}
// fingerprint available but no stored password found yet for this DB so show info don't listen
if (prefsNoBackup.getString(getPreferenceKeyValue(), null) == null) {
confirmationView.setText(R.string.no_password_stored);
}
// all is set here so we can confirm to user and start listening for fingerprints
else {
confirmationView.setText(R.string.scanning_fingerprint);
// listen for decryption by default
toggleMode(Cipher.DECRYPT_MODE);
}
}
}
@Override
public void handleEncryptedResult(
final String value,
final String ivSpec) {
prefsNoBackup.edit()
.putString(getPreferenceKeyValue(), value)
.putString(getPreferenceKeyIvSpec(), ivSpec)
.apply();
// and remove visual input to reset UI
confirmButton.performClick();
confirmationView.setText(R.string.encrypted_value_stored);
}
@Override
public void handleDecryptedResult(final String value) {
// on decrypt enter it for the purchase/login action
passwordView.setText(value);
confirmButton.performClick();
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onInvalidKeyException() {
Toast.makeText(this, R.string.fingerprint_invalid_key, Toast.LENGTH_SHORT).show();
checkAvailability(); // restarts listening
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onFingerprintException(Exception e) {
//Toast.makeText(this, R.string.fingerprint_error, Toast.LENGTH_SHORT).show();
checkAvailability();
e.printStackTrace();
}
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
@Override
public void onCheckedChanged(
CompoundButton buttonView,
boolean isChecked) {
String newDefaultFileName;
if (isChecked) {
newDefaultFileName = mDbUri.toString();
} else {
newDefaultFileName = "";
}
SharedPreferences.Editor editor = prefs.edit();
editor.putString(KEY_DEFAULT_FILENAME, newDefaultFileName);
EditorCompat.apply(editor);
BackupManagerCompat backupManager = new BackupManagerCompat(PasswordActivity.this);
backupManager.dataChanged();
}
}
private class OkClickHandler implements View.OnClickListener {
public void onClick(View view) {
String pass = getEditText(R.id.password);
String key = getEditText(R.id.pass_keyfile);
loadDatabase(pass, key);
}
}
private void loadDatabase(
String pass,
String keyfile) {
loadDatabase(pass, UriUtil.parseDefaultFile(keyfile));
}
private void loadDatabase(
String pass,
Uri keyfile) {
if (pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) {
errorMessage(R.string.error_nopass);
return;
}
// Clear before we load
Database db = App.getDB();
db.clear();
// Clear the shutdown flag
App.clearShutdown();
Handler handler = new Handler();
LoadDB task = new LoadDB(db, PasswordActivity.this, mDbUri, pass, keyfile, new AfterLoad(handler, db));
ProgressTask pt = new ProgressTask(PasswordActivity.this, task, R.string.loading_database);
pt.run();
}
private String getEditText(int resId) {
return Util.getEditText(this, resId);
}
private void setEditText(
int resId,
String str) {
TextView te = (TextView) findViewById(resId);
if (te != null) {
te.setText(str);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuUtil.defaultMenuInflater(getMenuInflater(), menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
break;
default:
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
}
return super.onOptionsItemSelected(item);
}
private final class AfterLoad extends OnFinish {
private Database db;
public AfterLoad(
Handler handler,
Database db) {
super(handler);
this.db = db;
}
@Override
public void run() {
if (db.passwordEncodingError) {
PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper();
dialog.show(PasswordActivity.this, new OnClickListener() {
@Override
public void onClick(
DialogInterface dialog,
int which) {
GroupActivity.Launch(PasswordActivity.this);
}
});
} else if (mSuccess) {
GroupActivity.Launch(PasswordActivity.this);
} else {
displayMessage(PasswordActivity.this);
}
}
}
private class InitTask extends AsyncTask<Intent, Void, Integer> {
String password = "";
boolean launch_immediately = false;
@Override
protected Integer doInBackground(Intent... args) {
Intent i = args[0];
String action = i.getAction();
if (action != null && action.equals(VIEW_INTENT)) {
Uri incoming = i.getData();
mDbUri = incoming;
mKeyUri = ClipDataCompat.getUriFromIntent(i, KEY_KEYFILE);
if (incoming == null) {
return R.string.error_can_not_handle_uri;
} else if (incoming.getScheme().equals("file")) {
String fileName = incoming.getPath();
if (fileName.length() == 0) {
// No file name
return R.string.FileNotFound;
}
File dbFile = new File(fileName);
if (!dbFile.exists()) {
// File does not exist
return R.string.FileNotFound;
}
if (mKeyUri == null) {
mKeyUri = getKeyFile(mDbUri);
}
} else if (incoming.getScheme().equals("content")) {
if (mKeyUri == null) {
mKeyUri = getKeyFile(mDbUri);
}
} else {
return R.string.error_can_not_handle_uri;
}
password = i.getStringExtra(KEY_PASSWORD);
launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false);
} else {
mDbUri = UriUtil.parseDefaultFile(i.getStringExtra(KEY_FILENAME));
mKeyUri = UriUtil.parseDefaultFile(i.getStringExtra(KEY_KEYFILE));
password = i.getStringExtra(KEY_PASSWORD);
launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false);
if (mKeyUri == null || mKeyUri.toString().length() == 0) {
mKeyUri = getKeyFile(mDbUri);
}
}
return null;
}
public void onPostExecute(Integer result) {
if (result != null) {
Toast.makeText(PasswordActivity.this, result, Toast.LENGTH_LONG).show();
finish();
return;
}
populateView();
Button confirmButton = (Button) findViewById(R.id.pass_ok);
confirmButton.setOnClickListener(new OkClickHandler());
if (password != null) {
TextView tv_password = (TextView) findViewById(R.id.password);
tv_password.setText(password);
}
CheckBox defaultCheck = (CheckBox) findViewById(R.id.default_database);
defaultCheck.setOnCheckedChangeListener(new DefaultCheckChange());
View browse = findViewById(R.id.browse_button);
browse.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (StorageAF.useStorageFramework(PasswordActivity.this)) {
Intent i = new Intent(StorageAF.ACTION_OPEN_DOCUMENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
startActivityForResult(i, OPEN_DOC);
} else {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
try {
startActivityForResult(i, GET_CONTENT);
} catch (ActivityNotFoundException e) {
lookForOpenIntentsFilePicker();
}
}
}
private void lookForOpenIntentsFilePicker() {
if (Interaction.isIntentAvailable(PasswordActivity.this, Intents.OPEN_INTENTS_FILE_BROWSE)) {
Intent i = new Intent(Intents.OPEN_INTENTS_FILE_BROWSE);
// Get file path parent if possible
try {
if (mDbUri != null && mDbUri.toString().length() > 0) {
if (mDbUri.getScheme().equals("file")) {
File keyfile = new File(mDbUri.getPath());
File parent = keyfile.getParentFile();
if (parent != null) {
i.setData(Uri.parse("file://" + parent.getAbsolutePath()));
}
}
}
} catch (Exception e) {
// Ignore
}
try {
startActivityForResult(i, FILE_BROWSE);
} catch (ActivityNotFoundException e) {
showBrowserDialog();
}
} else {
showBrowserDialog();
}
}
private void showBrowserDialog() {
BrowserDialog browserDialog = new BrowserDialog(PasswordActivity.this);
browserDialog.show();
}
});
retrieveSettings();
if (launch_immediately) {
loadDatabase(password, mKeyUri);
}
}
}
}

View File

@@ -1,148 +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 2 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.keepassdroid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.kunzisoft.keepass.R;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.view.PwEntryView;
import com.keepassdroid.view.PwGroupView;
public class PwGroupListAdapter extends BaseAdapter {
private GroupBaseActivity mAct;
private PwGroup mGroup;
private List<PwGroup> groupsForViewing;
private List<PwEntry> entriesForViewing;
private Comparator<PwEntry> entryComp = new PwEntry.EntryNameComparator();
private Comparator<PwGroup> groupComp = new PwGroup.GroupNameComparator();
private SharedPreferences prefs;
public PwGroupListAdapter(GroupBaseActivity act, PwGroup group) {
mAct = act;
mGroup = group;
prefs = PreferenceManager.getDefaultSharedPreferences(act);
filterAndSort();
}
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
filterAndSort();
}
@Override
public void notifyDataSetInvalidated() {
super.notifyDataSetInvalidated();
filterAndSort();
}
private void filterAndSort() {
entriesForViewing = new ArrayList<PwEntry>();
for (int i = 0; i < mGroup.childEntries.size(); i++) {
PwEntry entry = mGroup.childEntries.get(i);
if ( ! entry.isMetaStream() ) {
entriesForViewing.add(entry);
}
}
boolean sortLists = prefs.getBoolean(mAct.getString(R.string.sort_key), mAct.getResources().getBoolean(R.bool.sort_default));
if ( sortLists ) {
groupsForViewing = new ArrayList<PwGroup>(mGroup.childGroups);
Collections.sort(entriesForViewing, entryComp);
Collections.sort(groupsForViewing, groupComp);
} else {
groupsForViewing = mGroup.childGroups;
}
}
public int getCount() {
return groupsForViewing.size() + entriesForViewing.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
int size = groupsForViewing.size();
if ( position < size ) {
return createGroupView(position, convertView);
} else {
return createEntryView(position - size, convertView);
}
}
private View createGroupView(int position, View convertView) {
PwGroup group = groupsForViewing.get(position);
PwGroupView gv;
if (convertView == null || !(convertView instanceof PwGroupView)) {
gv = PwGroupView.getInstance(mAct, group);
}
else {
gv = (PwGroupView) convertView;
gv.convertView(group);
}
return gv;
}
private PwEntryView createEntryView(int position, View convertView) {
PwEntry entry = entriesForViewing.get(position);
PwEntryView ev;
if (convertView == null || !(convertView instanceof PwEntryView)) {
ev = PwEntryView.getInstance(mAct, entry, position);
}
else {
ev = (PwEntryView) convertView;
ev.convertView(entry, position);
}
return ev;
}
}

View File

@@ -1,156 +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 2 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.keepassdroid;
import android.app.Dialog;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.database.edit.FileOnFinish;
import com.keepassdroid.database.edit.OnFinish;
import com.keepassdroid.database.edit.SetPassword;
import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.UriUtil;
public class SetPasswordDialog extends DialogFragment {
private final static String FINISH_TAG = "FINISH_TAG";
private byte[] masterKey;
private Uri mKeyfile;
private FileOnFinish mFinish;
private View rootView;
public byte[] getKey() {
return masterKey;
}
public Uri keyfile() {
return mKeyfile;
}
public static SetPasswordDialog newInstance(FileOnFinish finish) {
SetPasswordDialog setPasswordDialog = new SetPasswordDialog();
Bundle args = new Bundle();
args.putSerializable(FINISH_TAG, finish);
setPasswordDialog.setArguments(args);
return setPasswordDialog;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
if(getArguments() != null && getArguments().containsKey(FINISH_TAG)) {
mFinish = (FileOnFinish) getArguments().getSerializable(FINISH_TAG);
}
rootView = inflater.inflate(R.layout.set_password, null);
builder.setView(rootView)
// Add action buttons
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
TextView passView = (TextView) rootView.findViewById(R.id.pass_password);
String pass = passView.getText().toString();
TextView passConfView = (TextView) rootView.findViewById(R.id.pass_conf_password);
String confpass = passConfView.getText().toString();
// Verify that passwords match
if ( ! pass.equals(confpass) ) {
// Passwords do not match
Toast.makeText(getContext(), R.string.error_pass_match, Toast.LENGTH_LONG).show();
return;
}
TextView keyfileView = (TextView) rootView.findViewById(R.id.pass_keyfile);
Uri keyfile = UriUtil.parseDefaultFile(keyfileView.getText().toString());
mKeyfile = keyfile;
// Verify that a password or keyfile is set
if ( pass.length() == 0 && EmptyUtils.isNullOrEmpty(keyfile)) {
Toast.makeText(getContext(), R.string.error_nopass, Toast.LENGTH_LONG).show();
return;
}
SetPassword sp = new SetPassword(getContext(), App.getDB(), pass, keyfile, new AfterSave(mFinish, new Handler()));
final ProgressTask pt = new ProgressTask(getContext(), sp, R.string.saving_database);
boolean valid = sp.validatePassword(getContext(), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
pt.run();
}
});
if (valid) {
pt.run();
}
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
SetPasswordDialog.this.getDialog().cancel();
if ( mFinish != null ) {
mFinish.run();
}
}
});
return builder.create();
}
private class AfterSave extends OnFinish {
private FileOnFinish mFinish;
public AfterSave(FileOnFinish finish, Handler handler) {
super(finish, handler);
mFinish = finish;
}
@Override
public void run() {
if ( mSuccess ) {
if ( mFinish != null ) {
mFinish.setFilename(mKeyfile);
}
dismiss();
} else {
displayMessage(getContext());
}
super.run();
}
}
}

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid;
package com.keepassdroid.activities;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -27,8 +27,10 @@ import android.util.Log;
import android.view.MenuItem;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.keepassdroid.stylish.StylishActivity;
import com.kunzisoft.keepass.R;
import org.joda.time.DateTime;
public class AboutActivity extends StylishActivity {
@@ -56,6 +58,9 @@ public class AboutActivity extends StylishActivity {
version = getString(R.string.version_label) + " " + version;
TextView versionText = (TextView) findViewById(R.id.activity_about_version);
versionText.setText(version);
TextView disclaimerText = (TextView) findViewById(R.id.disclaimer);
disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear()));
}
@Override

View File

@@ -18,7 +18,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid;
package com.keepassdroid.activities;
import android.app.Activity;
import android.app.AlertDialog;
@@ -32,14 +32,14 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.widget.Toolbar;
import android.support.v4.app.NotificationCompat;
import android.support.v7.widget.Toolbar;
import android.text.SpannableString;
import android.text.method.LinkMovementMethod;
import android.text.method.PasswordTransformationMethod;
import android.text.util.Linkify;
import android.view.Menu;
import android.view.MenuInflater;
@@ -49,85 +49,59 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.kunzisoft.keepass.KeePass;
import com.kunzisoft.keepass.R;
import com.keepassdroid.app.App;
import com.keepassdroid.compat.ActivityCompat;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.exception.SamsungClipboardException;
import com.keepassdroid.intents.Intents;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.password.PasswordActivity;
import com.keepassdroid.tasks.UIToastTask;
import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.Types;
import com.keepassdroid.utils.Util;
import com.keepassdroid.view.EntryContentsView;
import com.kunzisoft.keepass.R;
import java.text.DateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
public class EntryActivity extends LockCloseHideActivity {
import static com.keepassdroid.settings.PreferencesUtil.isClipboardNotificationsEnable;
public class EntryActivity extends LockingHideActivity {
public static final String KEY_ENTRY = "entry";
public static final String KEY_REFRESH_POS = "refresh_pos";
public static final int NOTIFY_USERNAME = 1;
public static final int NOTIFY_PASSWORD = 2;
public static void Launch(Activity act, PwEntry pw, int pos) {
Intent i;
if ( pw instanceof PwEntryV4 ) {
i = new Intent(act, EntryActivityV4.class);
} else {
i = new Intent(act, EntryActivity.class);
}
i.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
i.putExtra(KEY_REFRESH_POS, pos);
act.startActivityForResult(i,0);
}
private ImageView titleIconView;
private TextView titleView;
private EntryContentsView entryContentsView;
protected PwEntry mEntry;
private Timer mTimer = new Timer();
private boolean mShowPassword;
private int mPos;
private NotificationManager mNM;
private BroadcastReceiver mIntentReceiver;
protected boolean readOnly = false;
private DateFormat dateFormat;
private DateFormat timeFormat;
protected void setEntryView() {
setContentView(R.layout.entry_view);
}
protected void setupEditButtons() {
View edit = findViewById(R.id.entry_edit);
edit.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
EntryEditActivity.Launch(EntryActivity.this, mEntry);
}
});
if (readOnly) {
edit.setVisibility(View.GONE);
}
}
public static void launch(Activity act, PwEntry pw) {
Intent intent = new Intent(act, EntryActivity.class);
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mShowPassword = ! prefs.getBoolean(getString(R.string.maskpass_key), getResources().getBoolean(R.bool.maskpass_default));
super.onCreate(savedInstanceState);
setEntryView();
setContentView(R.layout.entry_view);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
@@ -135,10 +109,6 @@ public class EntryActivity extends LockCloseHideActivity {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
Context appCtx = getApplicationContext();
dateFormat = android.text.format.DateFormat.getDateFormat(appCtx);
timeFormat = android.text.format.DateFormat.getTimeFormat(appCtx);
Database db = App.getDB();
// Likely the app has been killed exit the activity
@@ -148,13 +118,11 @@ public class EntryActivity extends LockCloseHideActivity {
}
readOnly = db.readOnly;
setResult(KeePass.EXIT_NORMAL);
mShowPassword = !PreferencesUtil.isPasswordMask(this);
// Get Entry from UUID
Intent i = getIntent();
UUID uuid = Types.bytestoUUID(i.getByteArrayExtra(KEY_ENTRY));
mPos = i.getIntExtra(KEY_REFRESH_POS, -1);
assert(uuid != null);
mEntry = db.pm.entries.get(uuid);
if (mEntry == null) {
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show();
@@ -167,41 +135,62 @@ public class EntryActivity extends LockCloseHideActivity {
// Update last access time.
mEntry.touch(false, false);
fillData(false);
setupEditButtons();
// Notification Manager
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if ( mEntry.getPassword().length() > 0 ) {
// only show notification if password is available
Notification password = getNotification(Intents.COPY_PASSWORD, R.string.copy_password);
mNM.notify(NOTIFY_PASSWORD, password);
}
if ( mEntry.getUsername().length() > 0 ) {
// only show notification if username is available
Notification username = getNotification(Intents.COPY_USERNAME, R.string.copy_username);
mNM.notify(NOTIFY_USERNAME, username);
}
// Get views
titleIconView = (ImageView) findViewById(R.id.entry_icon);
titleView = (TextView) findViewById(R.id.entry_title);
entryContentsView = (EntryContentsView) findViewById(R.id.entry_contents);
entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this));
fillData();
// Setup Edit Buttons
View edit = findViewById(R.id.entry_edit);
edit.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
EntryEditActivity.Launch(EntryActivity.this, mEntry);
}
});
if (readOnly) {
edit.setVisibility(View.GONE);
}
// If notifications enabled in settings
if (isClipboardNotificationsEnable(getApplicationContext())) {
// Notification Manager
mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (mEntry.getPassword().length() > 0) {
// only show notification if password is available
Notification password = getNotification(Intents.COPY_PASSWORD, R.string.copy_password);
mNM.notify(NOTIFY_PASSWORD, password);
}
if (mEntry.getUsername().length() > 0) {
// only show notification if username is available
Notification username = getNotification(Intents.COPY_USERNAME, R.string.copy_username);
mNM.notify(NOTIFY_USERNAME, username);
}
}
mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if ( action.equals(Intents.COPY_USERNAME) ) {
String username = mEntry.getUsername();
if ( username.length() > 0 ) {
timeoutCopyToClipboard(username);
}
} else if ( action.equals(Intents.COPY_PASSWORD) ) {
String password = new String(mEntry.getPassword());
if ( password.length() > 0 ) {
timeoutCopyToClipboard(new String(mEntry.getPassword()));
if ( action != null) {
if (action.equals(Intents.COPY_USERNAME)) {
String username = mEntry.getUsername();
if (username.length() > 0) {
timeoutCopyToClipboard(username);
}
} else if (action.equals(Intents.COPY_PASSWORD)) {
String password = mEntry.getPassword();
if (password.length() > 0) {
timeoutCopyToClipboard(password);
}
}
}
}
@@ -248,60 +237,88 @@ public class EntryActivity extends LockCloseHideActivity {
return notify;
}
private String getDateTime(Date dt) {
return dateFormat.format(dt) + " " + timeFormat.format(dt);
}
protected void fillData(boolean trimList) {
ImageView iv = (ImageView) findViewById(R.id.entry_icon);
private void populateTitle(Drawable drawIcon, String text) {
titleIconView.setImageDrawable(drawIcon);
titleView.setText(text);
}
protected void fillData() {
Database db = App.getDB();
db.drawFactory.assignDrawableTo(iv, getResources(), mEntry.getIcon());
PwDatabase pm = db.pm;
populateText(R.id.entry_title, mEntry.getTitle(true, pm));
populateText(R.id.entry_user_name, mEntry.getUsername(true, pm));
populateText(R.id.entry_url, mEntry.getUrl(true, pm));
populateText(R.id.entry_password, mEntry.getPassword(true, pm));
setPasswordStyle();
populateText(R.id.entry_created, getDateTime(mEntry.getCreationTime()));
populateText(R.id.entry_modified, getDateTime(mEntry.getLastModificationTime()));
populateText(R.id.entry_accessed, getDateTime(mEntry.getLastAccessTime()));
// Assign title
populateTitle(db.drawFactory.getIconDrawable(getResources(), mEntry.getIcon()),
mEntry.getTitle(true, pm));
// Assign basic fields
entryContentsView.assignUserName(mEntry.getUsername(true, pm));
entryContentsView.assignUserNameCopyListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm),
getString(R.string.copy_field, getString(R.string.entry_user_name)));
}
});
entryContentsView.assignPassword(mEntry.getPassword(true, pm));
entryContentsView.assignPasswordCopyListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
timeoutCopyToClipboard(mEntry.getPassword(true, App.getDB().pm),
getString(R.string.copy_field, getString(R.string.entry_password)));
}
});
entryContentsView.assignURL(mEntry.getUrl(true, pm));
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
entryContentsView.assignComment(mEntry.getNotes(true, pm));
// Assign custom fields
if (mEntry.allowExtraFields()) {
entryContentsView.clearExtraFields();
for (Map.Entry<String, String> field : mEntry.getExtraFields(pm).entrySet()) {
final String label = field.getKey();
final String value = field.getValue();
entryContentsView.addExtraField(label, value, new View.OnClickListener() {
@Override
public void onClick(View view) {
timeoutCopyToClipboard(value, getString(R.string.copy_field, label));
}
});
}
}
// Assign dates
entryContentsView.assignCreationDate(mEntry.getCreationTime());
entryContentsView.assignModificationDate(mEntry.getLastModificationTime());
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime());
Date expires = mEntry.getExpiryTime();
if ( mEntry.expires() ) {
populateText(R.id.entry_expires, getDateTime(expires));
entryContentsView.assignExpiresDate(expires);
} else {
populateText(R.id.entry_expires, R.string.never);
entryContentsView.assignExpiresDate(getString(R.string.never));
}
populateText(R.id.entry_comment, mEntry.getNotes(true, pm));
}
private void populateText(int viewId, int resId) {
TextView tv = (TextView) findViewById(viewId);
tv.setText(resId);
}
private void populateText(int viewId, String text) {
TextView tv = (TextView) findViewById(viewId);
tv.setText(text);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if ( resultCode == KeePass.EXIT_REFRESH || resultCode == KeePass.EXIT_REFRESH_TITLE ) {
fillData(true);
if ( resultCode == KeePass.EXIT_REFRESH_TITLE ) {
Intent ret = new Intent();
ret.putExtra(KEY_REFRESH_POS, mPos);
setResult(KeePass.EXIT_REFRESH, ret);
}
switch (requestCode) {
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
fillData();
break;
}
}
private void changeShowPasswordIcon(MenuItem togglePassword) {
if ( mShowPassword ) {
togglePassword.setTitle(R.string.menu_hide_password);
togglePassword.setIcon(R.drawable.ic_visibility_off_white_24dp);
} else {
togglePassword.setTitle(R.string.menu_showpass);
togglePassword.setIcon(R.drawable.ic_visibility_white_24dp);
}
}
@@ -312,27 +329,21 @@ public class EntryActivity extends LockCloseHideActivity {
MenuInflater inflater = getMenuInflater();
MenuUtil.donationMenuInflater(inflater, menu);
inflater.inflate(R.menu.entry, menu);
inflater.inflate(R.menu.lock_database, menu);
inflater.inflate(R.menu.database_lock, menu);
MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass);
if ( mShowPassword ) {
togglePassword.setTitle(R.string.menu_hide_password);
togglePassword.setIcon(R.drawable.ic_visibility_off_white_24dp);
} else {
togglePassword.setTitle(R.string.menu_showpass);
togglePassword.setIcon(R.drawable.ic_visibility_white_24dp);
}
if (!entryContentsView.isPasswordPresent()) {
togglePassword.setVisible(false);
} else {
changeShowPasswordIcon(togglePassword);
}
MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url);
MenuItem copyUser = menu.findItem(R.id.menu_copy_user);
MenuItem copyPass = menu.findItem(R.id.menu_copy_pass);
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so mEntry may not be set
if (mEntry == null) {
gotoUrl.setVisible(false);
copyUser.setVisible(false);
copyPass.setVisible(false);
}
else {
String url = mEntry.getUrl();
@@ -340,28 +351,10 @@ public class EntryActivity extends LockCloseHideActivity {
// disable button if url is not available
gotoUrl.setVisible(false);
}
if ( mEntry.getUsername().length() == 0 ) {
// disable button if username is not available
copyUser.setVisible(false);
}
if ( mEntry.getPassword().length() == 0 ) {
// disable button if password is not available
copyPass.setVisible(false);
}
}
return true;
}
private void setPasswordStyle() {
TextView password = (TextView) findViewById(R.id.entry_password);
if ( mShowPassword ) {
password.setTransformationMethod(null);
} else {
password.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@@ -370,16 +363,9 @@ public class EntryActivity extends LockCloseHideActivity {
return MenuUtil.onDonationItemSelected(this);
case R.id.menu_toggle_pass:
if ( mShowPassword ) {
item.setTitle(R.string.menu_showpass);
item.setIcon(R.drawable.ic_visibility_white_24dp);
mShowPassword = false;
} else {
item.setTitle(R.string.menu_hide_password);
item.setIcon(R.drawable.ic_visibility_off_white_24dp);
mShowPassword = true;
}
setPasswordStyle();
mShowPassword = !mShowPassword;
changeShowPasswordIcon(item);
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
return true;
case R.id.menu_goto_url:
@@ -398,17 +384,9 @@ public class EntryActivity extends LockCloseHideActivity {
}
return true;
case R.id.menu_copy_user:
timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm));
return true;
case R.id.menu_copy_pass:
timeoutCopyToClipboard(mEntry.getPassword(true, App.getDB().pm));
return true;
case R.id.menu_lock:
App.setShutdown();
setResult(KeePass.EXIT_LOCK);
setResult(PasswordActivity.RESULT_EXIT_LOCK);
finish();
return true;
@@ -418,8 +396,15 @@ public class EntryActivity extends LockCloseHideActivity {
return super.onOptionsItemSelected(item);
}
private void timeoutCopyToClipboard(String text) {
timeoutCopyToClipboard(text, "");
}
private void timeoutCopyToClipboard(String text, String toastString) {
if (!toastString.isEmpty())
Toast.makeText(EntryActivity.this, toastString, Toast.LENGTH_LONG).show();
try {
Util.copyToClipboard(this, text);
} catch (SamsungClipboardException e) {
@@ -436,9 +421,20 @@ public class EntryActivity extends LockCloseHideActivity {
mTimer.schedule(new ClearClipboardTask(this, text), clipClearTime);
}
}
// Setup to allow the toast to happen in the foreground
@Override
public void finish() {
// Transit data in previous Activity after an update
/*
TODO Slowdown when add entry as result
Intent intent = new Intent();
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish();
}
// Setup to allow the toast to happen in the foreground
final Handler uiThreadCallback = new Handler();
// Task which clears the clipboard, and sends a toast to the foreground.

View File

@@ -0,0 +1,413 @@
/*
* 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 2 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.keepassdroid.activities;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.keepassdroid.app.App;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV4;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwIconStandard;
import com.keepassdroid.database.edit.AddEntry;
import com.keepassdroid.database.edit.OnFinish;
import com.keepassdroid.database.edit.RunnableOnFinish;
import com.keepassdroid.database.edit.UpdateEntry;
import com.keepassdroid.database.security.ProtectedString;
import com.keepassdroid.fragments.GeneratePasswordDialogFragment;
import com.keepassdroid.fragments.IconPickerDialogFragment;
import com.keepassdroid.icons.Icons;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.tasks.ProgressTask;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.Types;
import com.keepassdroid.utils.Util;
import com.keepassdroid.view.EntryEditNewField;
import com.kunzisoft.keepass.R;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
public class EntryEditActivity extends LockingHideActivity
implements IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener {
// Keys for current Activity
public static final String KEY_ENTRY = "entry";
public static final String KEY_PARENT = "parent";
// Keys for callback
public static final int ADD_ENTRY_RESULT_CODE = 31;
public static final int UPDATE_ENTRY_RESULT_CODE = 32;
public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
protected PwEntry mEntry;
protected PwEntry mCallbackNewEntry;
protected boolean mIsNew;
protected int mSelectedIconID = -1;
// Views
private ScrollView scrollView;
private TextView entryTitleView;
private TextView entryUserNameView;
private TextView entryUrlView;
private TextView entryPasswordView;
private TextView entryConfirmationPasswordView;
private TextView entryCommentView;
private ViewGroup entryExtraFieldsContainer;
/**
* launch EntryEditActivity to update an existing entry
* @param act from activity
* @param pw Entry to update
*/
public static void Launch(Activity act, PwEntry pw) {
Intent intent = new Intent(act, EntryEditActivity.class);
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
/**
* launch EntryEditActivity to add a new entry
* @param act from activity
* @param pwGroup Group who will contains new entry
*/
public static void Launch(Activity act, PwGroup pwGroup) {
Intent intent = new Intent(act, EntryEditActivity.class);
intent.putExtra(KEY_PARENT, pwGroup.getId());
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_edit);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
scrollView = (ScrollView) findViewById(R.id.entry_scroll);
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
entryTitleView = (TextView) findViewById(R.id.entry_title);
entryUserNameView = (TextView) findViewById(R.id.entry_user_name);
entryUrlView = (TextView) findViewById(R.id.entry_url);
entryPasswordView = (TextView) findViewById(R.id.entry_password);
entryConfirmationPasswordView = (TextView) findViewById(R.id.entry_confpassword);
entryCommentView = (TextView) findViewById(R.id.entry_comment);
entryExtraFieldsContainer = (ViewGroup) findViewById(R.id.advanced_container);
// Likely the app has been killed exit the activity
Database db = App.getDB();
if ( ! db.Loaded() ) {
finish();
return;
}
Intent intent = getIntent();
byte[] uuidBytes = intent.getByteArrayExtra(KEY_ENTRY);
PwDatabase pm = db.pm;
if ( uuidBytes == null ) {
PwGroupId parentId = (PwGroupId) intent.getSerializableExtra(KEY_PARENT);
PwGroup parent = pm.groups.get(parentId);
mEntry = PwEntry.getInstance(parent);
mIsNew = true;
} else {
UUID uuid = Types.bytestoUUID(uuidBytes);
mEntry = pm.entries.get(uuid);
mIsNew = false;
fillData();
}
View iconButton = findViewById(R.id.icon_button);
iconButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
IconPickerDialogFragment.launch(EntryEditActivity.this);
}
});
// Generate password button
View generatePassword = findViewById(R.id.generate_button);
generatePassword.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
}
});
// Save button
View save = findViewById(R.id.entry_save);
save.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (!validateBeforeSaving()) {
return;
}
mCallbackNewEntry = populateNewEntry();
OnFinish onFinish = new AfterSave();
EntryEditActivity act = EntryEditActivity.this;
RunnableOnFinish task;
if ( mIsNew ) {
task = new AddEntry(act, App.getDB(), mCallbackNewEntry, onFinish);
} else {
task = new UpdateEntry(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
}
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
pt.run();
}
});
if (mEntry.allowExtraFields()) {
View add = findViewById(R.id.add_new_field);
add.setVisibility(View.VISIBLE);
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EntryEditNewField ees = new EntryEditNewField(EntryEditActivity.this);
ees.setData("", new ProtectedString(false, ""));
entryExtraFieldsContainer.addView(ees);
// Scroll bottom
scrollView.post(new Runnable() {
@Override
public void run() {
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
});
}
}
protected boolean validateBeforeSaving() {
// Require title
String title = entryTitleView.getText().toString();
if ( title.length() == 0 ) {
Toast.makeText(this, R.string.error_title_required, Toast.LENGTH_LONG).show();
return false;
}
// Validate password
String pass = entryPasswordView.getText().toString();
String conf = entryConfirmationPasswordView.getText().toString();
if ( ! pass.equals(conf) ) {
Toast.makeText(this, R.string.error_pass_match, Toast.LENGTH_LONG).show();
return false;
}
// Validate extra fields
if (mEntry.allowExtraFields()) {
for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
EntryEditNewField entryEditNewField = (EntryEditNewField) entryExtraFieldsContainer.getChildAt(i);
String key = entryEditNewField.getLabel();
if (key == null || key.length() == 0) {
Toast.makeText(this, R.string.error_string_key, Toast.LENGTH_LONG).show();
return false;
}
}
}
return true;
}
protected PwEntry populateNewEntry() {
if (mEntry instanceof PwEntryV4) {
// TODO backup
PwEntryV4 newEntry = (PwEntryV4) mEntry.clone(true);
newEntry.history = (ArrayList<PwEntryV4>) newEntry.history.clone();
newEntry.createBackup((PwDatabaseV4) App.getDB().pm);
}
PwEntry newEntry = mEntry.clone(true);
Date now = Calendar.getInstance().getTime();
newEntry.setLastAccessTime(now);
newEntry.setLastModificationTime(now);
PwDatabase db = App.getDB().pm;
newEntry.setTitle(entryTitleView.getText().toString(), db);
if(mSelectedIconID != -1)
// or TODO icon factory newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(mSelectedIconID));
newEntry.setIcon(new PwIconStandard(mSelectedIconID));
else {
if (mIsNew) {
newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(0));
}
else {
// Keep previous icon, if no new one was selected
newEntry.setIcon(mEntry.icon);
}
}
newEntry.setUrl(entryUrlView.getText().toString(), db);
newEntry.setUsername(entryUserNameView.getText().toString(), db);
newEntry.setNotes(entryCommentView.getText().toString(), db);
newEntry.setPassword(entryPasswordView.getText().toString(), db);
if (newEntry.allowExtraFields()) {
// Delete all new standard strings
newEntry.removeExtraFields();
// Add extra fields from views
for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
EntryEditNewField view = (EntryEditNewField) entryExtraFieldsContainer.getChildAt(i);
String key = view.getLabel();
String value = view.getValue();
boolean protect = view.isProtected();
newEntry.addField(key, new ProtectedString(protect, value));
}
}
return newEntry;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.donationMenuInflater(inflater, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_donate:
return MenuUtil.onDonationItemSelected(this);
case android.R.id.home:
finish();
}
return super.onOptionsItemSelected(item);
}
protected void fillData() {
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
App.getDB().drawFactory.assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
boolean visibilityFont = PreferencesUtil.fieldFontIsInVisibility(this);
entryTitleView.setText(mEntry.getTitle());
entryUserNameView.setText(mEntry.getUsername());
entryUrlView.setText(mEntry.getUrl());
String password = mEntry.getPassword();
entryPasswordView.setText(password);
entryConfirmationPasswordView.setText(password);
entryCommentView.setText(mEntry.getNotes());
Util.applyFontVisibilityToTextView(visibilityFont, entryCommentView);
if (mEntry.allowExtraFields()) {
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
for (Map.Entry<String, ProtectedString> pair : mEntry.getExtraProtectedFields().entrySet()) {
EntryEditNewField entryEditNewField = new EntryEditNewField(EntryEditActivity.this);
entryEditNewField.setData(pair.getKey(), pair.getValue());
entryEditNewField.setFontVisibility(visibilityFont);
container.addView(entryEditNewField);
}
}
}
@Override
public void iconPicked(Bundle bundle) {
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
}
@Override
public void acceptPassword(Bundle bundle) {
String generatedPassword = bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID);
entryPasswordView.setText(generatedPassword);
entryConfirmationPasswordView.setText(generatedPassword);
}
@Override
public void cancelPassword(Bundle bundle) {
// Do nothing here
}
@Override
public void finish() {
// Assign entry callback as a result in all case
if (mCallbackNewEntry != null) {
Intent intentEntry = new Intent();
if (mIsNew) {
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
} else {
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry);
}
}
super.finish();
}
private final class AfterSave extends OnFinish {
AfterSave() {
super(new Handler());
}
@Override
public void run() {
if ( mSuccess ) {
finish();
} else {
displayMessage(EntryEditActivity.this);
}
}
}
}

View File

@@ -0,0 +1,426 @@
/*
* 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 2 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.keepassdroid.activities;
import android.app.Activity;
import android.app.Dialog;
import android.app.SearchManager;
import android.app.assist.AssistStructure;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.RequiresApi;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import com.keepassdroid.adapters.NodeAdapter;
import com.keepassdroid.app.App;
import com.keepassdroid.autofill.AutofillHelper;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwGroupId;
import com.keepassdroid.database.PwNode;
import com.keepassdroid.database.SortNodeEnum;
import com.keepassdroid.database.edit.AddGroup;
import com.keepassdroid.database.edit.DeleteEntry;
import com.keepassdroid.database.edit.DeleteGroup;
import com.keepassdroid.dialog.ReadOnlyDialog;
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
import com.keepassdroid.fragments.GroupEditDialogFragment;
import com.keepassdroid.fragments.IconPickerDialogFragment;
import com.keepassdroid.password.PasswordActivity;
import com.keepassdroid.search.SearchResultsActivity;
import com.keepassdroid.tasks.ProgressTask;
import com.keepassdroid.view.ListNodesWithAddButtonView;
import com.kunzisoft.keepass.R;
public class GroupActivity extends ListNodesActivity
implements GroupEditDialogFragment.EditGroupListener, IconPickerDialogFragment.IconPickerListener {
protected boolean addGroupEnabled = false;
protected boolean addEntryEnabled = false;
protected boolean isRoot = false;
protected boolean readOnly = false;
protected EditGroupDialogAction editGroupDialogAction = EditGroupDialogAction.NONE;
private ListNodesWithAddButtonView rootView;
private AutofillHelper autofillHelper;
private enum EditGroupDialogAction {
CREATION, UPDATE, NONE
}
private static final String TAG = "Group Activity:";
public static void launch(Activity act) {
launch(act, (PwGroup) null);
}
public static void launch(Activity act, PwGroup group) {
Intent intent = new Intent(act, GroupActivity.class);
if ( group != null ) {
intent.putExtra(KEY_ENTRY, group.getId());
}
act.startActivityForResult(intent, 0);
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity act, AssistStructure assistStructure) {
launch(act, null, assistStructure);
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity act, PwGroup group, AssistStructure assistStructure) {
if ( assistStructure != null ) {
Intent intent = new Intent(act, GroupActivity.class);
if ( group != null ) {
intent.putExtra(KEY_ENTRY, group.getId());
}
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
} else {
launch(act, group);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w(TAG, "Retrieved tree");
if ( mCurrentGroup == null ) {
Log.w(TAG, "Group was null");
return;
}
// Construct main view
rootView = new ListNodesWithAddButtonView(this);
rootView.enableAddGroup(addGroupEnabled);
rootView.enableAddEntry(addEntryEnabled);
setContentView(rootView);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle("");
setSupportActionBar(toolbar);
if ( mCurrentGroup.getParent() != null )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
rootView.setAddGroupClickListener(new View.OnClickListener() {
public void onClick(View v) {
editGroupDialogAction = EditGroupDialogAction.CREATION;
GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment();
groupEditDialogFragment.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
}
});
rootView.setAddEntryClickListener(new View.OnClickListener() {
public void onClick(View v) {
EntryEditActivity.Launch(GroupActivity.this, mCurrentGroup);
}
});
setGroupTitle();
setGroupIcon();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
Log.w(TAG, "Finished creating tree");
if (isRoot) {
showWarnings();
}
}
protected PwGroup initCurrentGroup() {
PwGroup currentGroup;
Database db = App.getDB();
readOnly = db.readOnly;
PwGroup root = db.pm.rootGroup;
Log.w(TAG, "Creating tree view");
PwGroupId pwGroupId = (PwGroupId) getIntent().getSerializableExtra(KEY_ENTRY);
if ( pwGroupId == null ) {
currentGroup = root;
} else {
currentGroup = db.pm.groups.get(pwGroupId);
}
addGroupEnabled = !readOnly;
addEntryEnabled = !readOnly;
isRoot = (currentGroup == root);
if ( !currentGroup.allowAddEntryIfIsRoot() )
addEntryEnabled = !isRoot && addEntryEnabled;
return currentGroup;
}
@Override
protected RecyclerView defineNodeList() {
return (RecyclerView) findViewById(R.id.nodes_list);
}
@Override
public void onNodeClick(PwNode node) {
// Add event when we have Autofill
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
mAdapter.registerANodeToUpdate(node);
switch (node.getType()) {
case GROUP:
GroupActivity.launch(this, (PwGroup) node, assistStructure);
break;
case ENTRY:
// Build response with the entry selected
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
}
}
if ( assistStructure == null ){
super.onNodeClick(node);
}
}
@Override
protected void addOptionsToAdapter(NodeAdapter nodeAdapter) {
super.addOptionsToAdapter(nodeAdapter);
nodeAdapter.setActivateContextMenu(true);
nodeAdapter.setNodeMenuListener(new NodeAdapter.NodeMenuListener() {
@Override
public boolean onOpenMenuClick(PwNode node) {
mAdapter.registerANodeToUpdate(node);
switch (node.getType()) {
case GROUP:
GroupActivity.launch(GroupActivity.this, (PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(GroupActivity.this, (PwEntry) node);
break;
}
return true;
}
@Override
public boolean onEditMenuClick(PwNode node) {
mAdapter.registerANodeToUpdate(node);
switch (node.getType()) {
case GROUP:
editGroupDialogAction = EditGroupDialogAction.UPDATE;
GroupEditDialogFragment groupEditDialogFragment =
GroupEditDialogFragment.build(node);
groupEditDialogFragment.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
break;
case ENTRY:
EntryEditActivity.Launch(GroupActivity.this, (PwEntry) node);
break;
}
return true;
}
@Override
public boolean onDeleteMenuClick(PwNode node) {
switch (node.getType()) {
case GROUP:
deleteGroup((PwGroup) node);
break;
case ENTRY:
deleteEntry((PwEntry) node);
break;
}
return true;
}
});
}
@Override
protected void onResume() {
super.onResume();
// Show button on resume
rootView.showButton();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
super.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
// Show button if hide after sort
rootView.showButton();
}
protected void setGroupIcon() {
if (mCurrentGroup != null) {
ImageView iv = (ImageView) findViewById(R.id.icon);
App.getDB().drawFactory.assignDrawableTo(iv, getResources(), mCurrentGroup.getIcon());
}
}
private void deleteEntry(PwEntry entry) {
Handler handler = new Handler();
DeleteEntry task = new DeleteEntry(this, App.getDB(), entry,
new AfterDeleteNode(handler, entry));
ProgressTask pt = new ProgressTask(this, task, R.string.saving_database);
pt.run();
}
private void deleteGroup(PwGroup group) {
//TODO Verify trash recycle bin
Handler handler = new Handler();
DeleteGroup task = new DeleteGroup(this, App.getDB(), group,
new AfterDeleteNode(handler, group));
ProgressTask pt = new ProgressTask(this, task, R.string.saving_database);
pt.run();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search, menu);
inflater.inflate(R.menu.database_master_key, menu);
inflater.inflate(R.menu.database_lock, menu);
// Get the SearchView and set the searchable configuration
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
assert searchManager != null;
MenuItem searchItem = menu.findItem(R.id.menu_search);
SearchView searchView = null;
if (searchItem != null) {
searchView = (SearchView) searchItem.getActionView();
}
if (searchView != null) {
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
case R.id.menu_search:
onSearchRequested();
return true;
case R.id.menu_lock:
App.setShutdown();
setResult(PasswordActivity.RESULT_EXIT_LOCK);
finish();
return true;
case R.id.menu_change_master_key:
setPassword();
return true;
}
return super.onOptionsItemSelected(item);
}
private void setPassword() {
AssignMasterKeyDialogFragment dialog = new AssignMasterKeyDialogFragment();
dialog.show(getSupportFragmentManager(), "passwordDialog");
}
@Override
public void approveEditGroup(Bundle bundle) {
String GroupName = bundle.getString(GroupEditDialogFragment.KEY_NAME);
int GroupIconID = bundle.getInt(GroupEditDialogFragment.KEY_ICON_ID);
switch (editGroupDialogAction) {
case CREATION:
// If edit group creation
Handler handler = new Handler();
AddGroup task = new AddGroup(this, App.getDB(), GroupName, GroupIconID, mCurrentGroup,
new AfterAddNode(handler), false);
ProgressTask pt = new ProgressTask(this, task, R.string.saving_database);
pt.run();
break;
case UPDATE:
// If edit group update
// TODO UpdateGroup
break;
}
editGroupDialogAction = EditGroupDialogAction.NONE;
}
@Override
public void cancelEditGroup(Bundle bundle) {
// Do nothing here
}
@Override
// For icon in create tree dialog
public void iconPicked(Bundle bundle) {
GroupEditDialogFragment groupEditDialogFragment =
(GroupEditDialogFragment) getSupportFragmentManager()
.findFragmentByTag(GroupEditDialogFragment.TAG_CREATE_GROUP);
if (groupEditDialogFragment != null) {
groupEditDialogFragment.iconPicked(bundle);
}
}
protected void showWarnings() {
if (App.getDB().readOnly) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
if (prefs.getBoolean(getString(R.string.show_read_only_warning), true)) {
Dialog dialog = new ReadOnlyDialog(this);
dialog.show();
}
}
}
}

View File

@@ -0,0 +1,314 @@
/*
* 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 2 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.keepassdroid.activities;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.keepassdroid.adapters.NodeAdapter;
import com.keepassdroid.app.App;
import com.keepassdroid.compat.ActivityCompat;
import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwNode;
import com.keepassdroid.database.edit.AfterAddNodeOnFinish;
import com.keepassdroid.database.edit.OnFinish;
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
import com.keepassdroid.fragments.SortDialogFragment;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.tasks.UIToastTask;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.database.SortNodeEnum;
import com.keepassdroid.view.AssignPasswordHelper;
import com.kunzisoft.keepass.R;
public abstract class ListNodesActivity extends LockingActivity
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.OnNodeClickCallback,
SortDialogFragment.SortSelectionListener {
public static final String KEY_ENTRY = "entry";
protected PwGroup mCurrentGroup;
protected NodeAdapter mAdapter;
private SharedPreferences prefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( isFinishing() ) {
return;
}
// Likely the app has been killed exit the activity
if ( ! App.getDB().Loaded() ) {
finish();
return;
}
prefs = PreferenceManager.getDefaultSharedPreferences(this);
ActivityCompat.invalidateOptionsMenu(this);
setContentView(R.layout.list_nodes);
mCurrentGroup = initCurrentGroup();
mAdapter = new NodeAdapter(this);
addOptionsToAdapter(mAdapter);
}
protected abstract PwGroup initCurrentGroup();
protected abstract RecyclerView defineNodeList();
protected void addOptionsToAdapter(NodeAdapter nodeAdapter) {
mAdapter.setOnNodeClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
// Add elements to the list
mAdapter.rebuildList(mCurrentGroup);
assignListToNodeAdapter(defineNodeList());
}
protected void setGroupTitle() {
if ( mCurrentGroup != null ) {
String name = mCurrentGroup.getName();
TextView tv = (TextView) findViewById(R.id.group_name);
if ( name != null && name.length() > 0 ) {
if ( tv != null ) {
tv.setText(name);
}
} else {
if ( tv != null ) {
tv.setText(getText(R.string.root));
}
}
}
}
protected void assignListToNodeAdapter(RecyclerView recyclerView) {
recyclerView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
// TODO mList.setTextFilterEnabled(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mAdapter);
}
@Override
public void onNodeClick(PwNode node) {
mAdapter.registerANodeToUpdate(node);
switch (node.getType()) {
case GROUP:
GroupActivity.launch(this, (PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node);
break;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.donationMenuInflater(inflater, menu);
inflater.inflate(R.menu.tree, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
// Toggle setting
Editor editor = prefs.edit();
editor.putString(getString(R.string.sort_node_key), sortNodeEnum.name());
editor.putBoolean(getString(R.string.sort_ascending_key), ascending);
editor.putBoolean(getString(R.string.sort_group_before_key), groupsBefore);
editor.putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom);
EditorCompat.apply(editor);
// Tell the adapter to refresh it's list
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
mAdapter.rebuildList(mCurrentGroup);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_sort:
SortDialogFragment sortDialogFragment;
PwDatabase database = App.getDB().pm;
/*
// TODO Recycle bin bottom
if (database.isRecycleBinAvailable() && database.isRecycleBinEnable()) {
sortDialogFragment =
SortDialogFragment.getInstance(
PrefsUtil.getListSort(this),
PrefsUtil.getAscendingSort(this),
PrefsUtil.getGroupsBeforeSort(this),
PrefsUtil.getRecycleBinBottomSort(this));
} else {
*/
sortDialogFragment =
SortDialogFragment.getInstance(
PreferencesUtil.getListSort(this),
PreferencesUtil.getAscendingSort(this),
PreferencesUtil.getGroupsBeforeSort(this));
//}
sortDialogFragment.show(getSupportFragmentManager(), "sortDialog");
return true;
default:
MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
return super.onOptionsItemSelected(item);
}
}
@Override
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
AssignPasswordHelper assignPasswordHelper =
new AssignPasswordHelper(this,
masterPassword, keyFile);
assignPasswordHelper.assignPasswordInDatabase(null);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
PwNode newNode = (PwNode) data.getSerializableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
if (newNode != null) {
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
mAdapter.addNode(newNode);
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode);
mAdapter.rebuildList(mCurrentGroup);
}
} else {
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
}
}
break;
}
}
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.startActivityForResult(intent, requestCode, options);
}
}
class AfterAddNode extends AfterAddNodeOnFinish {
AfterAddNode(Handler handler) {
super(handler);
}
public void run(PwNode pwNode) {
super.run();
if (mSuccess) {
mAdapter.addNode(pwNode);
} else {
displayMessage(ListNodesActivity.this);
}
}
}
class AfterDeleteNode extends OnFinish {
private PwNode pwNode;
AfterDeleteNode(Handler handler, PwNode pwNode) {
super(handler);
this.pwNode = pwNode;
}
@Override
public void run() {
if ( mSuccess) {
mAdapter.removeNode(pwNode);
PwGroup parent = pwNode.getParent();
PwDatabase database = App.getDB().pm;
if (database.isRecycleBinAvailable() && database.isRecycleBinEnable()) {
PwGroup recycleBin = database.getRecycleBin();
// Add trash if it doesn't exists
if (parent.equals(recycleBin)
&& mCurrentGroup.getParent() == null
&& !mCurrentGroup.equals(recycleBin)) {
mAdapter.addNode(parent);
}
}
} else {
mHandler.post(new UIToastTask(ListNodesActivity.this, "Unrecoverable error: " + mMessage));
App.setShutdown();
finish();
}
}
}
}

View File

@@ -0,0 +1,87 @@
/*
* 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 2 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.keepassdroid.activities;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.keepassdroid.app.App;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.stylish.StylishActivity;
import com.keepassdroid.timeout.TimeoutHelper;
public abstract class LockingActivity extends StylishActivity {
private ScreenReceiver screenReceiver;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this)) {
screenReceiver = new ScreenReceiver();
registerReceiver(screenReceiver, new IntentFilter((Intent.ACTION_SCREEN_OFF)));
} else
screenReceiver = null;
}
@Override
protected void onResume() {
super.onResume();
TimeoutHelper.checkShutdown(this);
TimeoutHelper.resume(this);
}
@Override
protected void onPause() {
super.onPause();
TimeoutHelper.pause(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if(screenReceiver != null)
unregisterReceiver(screenReceiver);
}
public class ScreenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction() != null) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(LockingActivity.this)) {
App.setShutdown();
TimeoutHelper.checkShutdown(LockingActivity.this);
}
}
}
}
}
}

View File

@@ -17,29 +17,30 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid;
package com.keepassdroid.activities;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
import com.kunzisoft.keepass.KeePass;
import com.keepassdroid.app.App;
import com.keepassdroid.compat.BuildCompat;
public abstract class LockCloseActivity extends LockingActivity {
/**
* Locking Hide Activity that sets FLAG_SECURE to prevent screenshots, and from
* appearing in the recent app preview
*/
public abstract class LockingHideActivity extends LockingActivity {
@Override
protected void onResume() {
super.onResume();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
checkShutdown();
}
private void checkShutdown() {
if ( App.isShutdown() && App.getDB().Loaded() ) {
setResult(KeePass.EXIT_LOCK);
finish();
// Several gingerbread devices have problems with FLAG_SECURE
int ver = BuildCompat.getSdkVersion();
if (ver >= BuildCompat.VERSION_CODE_ICE_CREAM_SANDWICH || ver < BuildCompat.VERSION_CODE_GINGERBREAD) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
}
/* (non-Javadoc) Workaround for HTC Linkify issues

View File

@@ -0,0 +1,17 @@
package com.keepassdroid.adapters;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
abstract class BasicViewHolder extends RecyclerView.ViewHolder {
View container;
ImageView icon;
TextView text;
BasicViewHolder(View itemView) {
super(itemView);
}
}

View File

@@ -0,0 +1,17 @@
package com.keepassdroid.adapters;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
class EntryViewHolder extends BasicViewHolder {
EntryViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.entry_container);
icon = (ImageView) itemView.findViewById(R.id.entry_icon);
text = (TextView) itemView.findViewById(R.id.entry_text);
}
}

View File

@@ -0,0 +1,17 @@
package com.keepassdroid.adapters;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
class GroupViewHolder extends BasicViewHolder {
GroupViewHolder(View itemView) {
super(itemView);
container = itemView.findViewById(R.id.group_container);
icon = (ImageView) itemView.findViewById(R.id.group_icon);
text = (TextView) itemView.findViewById(R.id.group_text);
}
}

View File

@@ -0,0 +1,293 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.adapters;
import android.content.Context;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.util.SortedListAdapterCallback;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.keepassdroid.app.App;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.PwNode;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.database.SortNodeEnum;
import com.kunzisoft.keepass.R;
public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private SortedList<PwNode> nodeSortedList;
private Context context;
private LayoutInflater inflater;
private float textSize;
private SortNodeEnum listSort;
private boolean groupsBeforeSort;
private boolean ascendingSort;
private OnNodeClickCallback onNodeClickCallback;
private int nodePositionToUpdate;
private NodeMenuListener nodeMenuListener;
private boolean activateContextMenu;
/**
* Create node list adapter with contextMenu or not
* @param context Context to use
*/
public NodeAdapter(final Context context) {
this.inflater = LayoutInflater.from(context);
this.context = context;
this.textSize = PreferencesUtil.getListTextSize(context);
this.listSort = PreferencesUtil.getListSort(context);
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
this.activateContextMenu = false;
this.nodePositionToUpdate = -1;
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
@Override public int compare(PwNode item1, PwNode item2) {
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort).compare(item1, item2);
}
@Override public boolean areContentsTheSame(PwNode oldItem, PwNode newItem) {
return oldItem.isContentVisuallyTheSame(newItem);
}
@Override public boolean areItemsTheSame(PwNode item1, PwNode item2) {
return item1.equals(item2);
}
});
}
public void setActivateContextMenu(boolean activate) {
this.activateContextMenu = activate;
}
/**
* Rebuild the list by clear and build children from the group
*/
public void rebuildList(PwGroup group) {
this.nodeSortedList.clear();
this.nodeSortedList.addAll(group.getDirectChildren());
}
/**
* Add a node to the list
* @param node Node to add
*/
public void addNode(PwNode node) {
nodeSortedList.add(node);
}
/**
* Register a node to update before an action
* Call updateLastNodeRegister() after the action to update the node
* @param node Node to register
*/
public void registerANodeToUpdate(PwNode node) {
nodePositionToUpdate = nodeSortedList.indexOf(node);
}
/**
* Update the last Node register in the list
* Work if only registerANodeToUpdate(PwNode node) is called before
*/
public void updateLastNodeRegister(PwNode node) {
// Don't really update here, sorted list knows each original ref, so we just notify a change
try {
if (nodePositionToUpdate != -1) {
// Don't know why but there is a bug to remove a node after this update
nodeSortedList.updateItemAt(nodePositionToUpdate, node);
nodeSortedList.recalculatePositionOfItemAt(nodePositionToUpdate);
nodePositionToUpdate = -1;
}
else {
Log.e(NodeAdapter.class.getName(), "registerANodeToUpdate must be called before updateLastNodeRegister");
}
} catch (IndexOutOfBoundsException e) {
Log.e(NodeAdapter.class.getName(), e.getMessage());
}
}
/**
* Remove node in the list
* @param node Node to delete
*/
public void removeNode(PwNode node) {
nodeSortedList.remove(node);
}
/**
* Notify a change sort of the list
*/
public void notifyChangeSort(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore) {
this.listSort = sortNodeEnum;
this.ascendingSort = ascending;
this.groupsBeforeSort = groupsBefore;
}
@Override
public int getItemViewType(int position) {
return nodeSortedList.get(position).getType().ordinal();
}
@Override
public BasicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
BasicViewHolder basicViewHolder;
View view;
if (viewType == PwNode.Type.GROUP.ordinal()) {
view = inflater.inflate(R.layout.list_nodes_group, parent, false);
basicViewHolder = new GroupViewHolder(view);
} else {
view = inflater.inflate(R.layout.list_nodes_entry, parent, false);
basicViewHolder = new EntryViewHolder(view);
}
return basicViewHolder;
}
@Override
public void onBindViewHolder(BasicViewHolder holder, int position) {
PwNode subNode = nodeSortedList.get(position);
// Assign image
App.getDB().drawFactory.assignDrawableTo(holder.icon,
context.getResources(), subNode.getIcon());
// Assign text
holder.text.setText(subNode.getDisplayTitle());
// Assign click
holder.container.setOnClickListener(
new OnNodeClickListener(subNode));
// Context menu
if (activateContextMenu) {
holder.container.setOnCreateContextMenuListener(
new ContextMenuBuilder(subNode, nodeMenuListener));
}
// Assign text size
holder.text.setTextSize(textSize);
}
@Override
public int getItemCount() {
return nodeSortedList.size();
}
/**
* Assign a listener when a node is clicked
*/
public void setOnNodeClickListener(OnNodeClickCallback onNodeClickCallback) {
this.onNodeClickCallback = onNodeClickCallback;
}
/**
* Assign a listener when an element of menu is clicked
*/
public void setNodeMenuListener(NodeMenuListener nodeMenuListener) {
this.nodeMenuListener = nodeMenuListener;
}
/**
* Callback listener to redefine to do an action when a node is click
*/
public interface OnNodeClickCallback {
void onNodeClick(PwNode node);
}
/**
* Menu listener to redefine to do an action in menu
*/
public interface NodeMenuListener {
boolean onOpenMenuClick(PwNode node);
boolean onEditMenuClick(PwNode node);
boolean onDeleteMenuClick(PwNode node);
}
/**
* Utility class for node listener
*/
private class OnNodeClickListener implements View.OnClickListener {
private PwNode node;
OnNodeClickListener(PwNode node) {
this.node = node;
}
@Override
public void onClick(View v) {
if (onNodeClickCallback != null)
onNodeClickCallback.onNodeClick(node);
}
}
/**
* Utility class for menu listener
*/
private class ContextMenuBuilder implements View.OnCreateContextMenuListener {
private static final int MENU_OPEN = Menu.FIRST;
private static final int MENU_EDIT = MENU_OPEN + 1;
private static final int MENU_DELETE = MENU_EDIT + 1;
private PwNode node;
private NodeMenuListener menuListener;
ContextMenuBuilder(PwNode node, NodeMenuListener menuListener) {
this.menuListener = menuListener;
this.node = node;
}
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
MenuItem clearMenu = contextMenu.add(Menu.NONE, MENU_OPEN, Menu.NONE, R.string.menu_open);
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
if (!App.getDB().readOnly && !node.equals(App.getDB().pm.getRecycleBin())) {
// TODO make edit for group
// clearMenu = contextMenu.add(Menu.NONE, MENU_EDIT, Menu.NONE, R.string.menu_edit);
// clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
clearMenu = contextMenu.add(Menu.NONE, MENU_DELETE, Menu.NONE, R.string.menu_delete);
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
}
}
private MenuItem.OnMenuItemClickListener mOnMyActionClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (menuListener == null)
return false;
switch ( item.getItemId() ) {
case MENU_OPEN:
return menuListener.onOpenMenuClick(node);
case MENU_EDIT:
return menuListener.onEditMenuClick(node);
case MENU_DELETE:
return menuListener.onDeleteMenuClick(node);
default:
return false;
}
}
};
}
}

View File

@@ -19,16 +19,16 @@
*/
package com.keepassdroid.app;
import android.app.Application;
import android.support.multidex.MultiDexApplication;
import com.keepassdroid.Database;
import com.keepassdroid.compat.PRNGFixes;
import com.keepassdroid.database.Database;
import com.keepassdroid.fileselect.RecentFileHistory;
import com.keepassdroid.stylish.Stylish;
import java.util.Calendar;
public class App extends Application {
public class App extends MultiDexApplication {
private static Database db = null;
private static boolean shutdown = false;
private static Calendar calendar = null;

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2017 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 2 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.keepassdroid.autofill;
import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import com.keepassdroid.fileselect.FileSelectActivity;
import com.kunzisoft.keepass.KeePass;
@RequiresApi(api = Build.VERSION_CODES.O)
public class AutoFillAuthActivity extends KeePass {
private AutofillHelper autofillHelper;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
autofillHelper = new AutofillHelper();
super.onCreate(savedInstanceState);
}
public static IntentSender getAuthIntentSenderForResponse(Context context) {
final Intent intent = new Intent(context, AutoFillAuthActivity.class);
return PendingIntent.getActivity(context, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
}
@Override
protected void startFileSelectActivity() {
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
AssistStructure assistStructure = autofillHelper.retrieveAssistStructure(getIntent());
if (assistStructure != null) {
FileSelectActivity.launch(this, assistStructure);
} else {
setResult(RESULT_CANCELED);
finish();
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}

View File

@@ -0,0 +1,174 @@
/*
* 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 2 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.keepassdroid.autofill;
import android.app.Activity;
import android.app.assist.AssistStructure;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.service.autofill.Dataset;
import android.service.autofill.FillResponse;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.widget.RemoteViews;
import com.keepassdroid.database.PwEntry;
import com.kunzisoft.keepass.R;
import java.util.ArrayList;
import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.O)
public class AutofillHelper {
public static final int AUTOFILL_RESPONSE_REQUEST_CODE = 8165;
private AssistStructure assistStructure = null;
public AssistStructure retrieveAssistStructure(Intent intent) {
if (intent != null && intent.getExtras() != null) {
assistStructure = intent.getParcelableExtra(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE);
}
return assistStructure;
}
/**
* Call retrieveAssistStructure before
*/
public AssistStructure getAssistStructure() {
return assistStructure;
}
public static void addAssistStructureExtraInIntent(Intent intent, AssistStructure assistStructure) {
if (assistStructure != null) {
intent.putExtra(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE, assistStructure);
}
}
/**
* Define if android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE is an extra bundle key present in the Intent
*/
public static boolean isIntentContainsExtraAssistStructureKey(Intent intent) {
return (intent != null
&& intent.getExtras() != null
&& intent.getExtras().containsKey(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE));
}
private @Nullable Dataset buildDataset(Context context, PwEntry entry,
StructureParser.Result struct) {
String title = makeEntryTitle(entry);
RemoteViews views = newRemoteViews(context.getPackageName(), title);
Dataset.Builder builder = new Dataset.Builder(views);
builder.setId(entry.getUUID().toString());
if (entry.getPassword() != null) {
AutofillValue value = AutofillValue.forText(entry.getPassword());
struct.password.forEach(id -> builder.setValue(id, value));
}
if (entry.getUsername() != null) {
AutofillValue value = AutofillValue.forText(entry.getUsername());
List<AutofillId> ids = new ArrayList<>(struct.username);
if (entry.getUsername().contains("@") || struct.username.isEmpty())
ids.addAll(struct.email);
ids.forEach(id -> builder.setValue(id, value));
}
try {
return builder.build();
} catch (IllegalArgumentException e) {
// if not value be set
return null;
}
}
static private String makeEntryTitle(PwEntry entry) {
if (!entry.getTitle().isEmpty() && !entry.getUsername().isEmpty())
return String.format("%s (%s)", entry.getTitle(), entry.getUsername());
if (!entry.getTitle().isEmpty())
return entry.getTitle();
if (!entry.getUsername().isEmpty())
return entry.getUsername();
if (!entry.getNotes().isEmpty())
return entry.getNotes().trim();
return ""; // TODO No title
}
/**
* Method to hit when right key is selected
*/
public void buildResponseWhenEntrySelected(Activity activity, PwEntry entry) {
Intent mReplyIntent;
Intent intent = activity.getIntent();
if (isIntentContainsExtraAssistStructureKey(intent)) {
AssistStructure structure = intent.getParcelableExtra(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE);
StructureParser.Result result = new StructureParser(structure).parse();
// New Response
FillResponse.Builder responseBuilder = new FillResponse.Builder();
Dataset dataset = buildDataset(activity, entry, result);
responseBuilder.addDataset(dataset);
mReplyIntent = new Intent();
Log.d(activity.getClass().getName(), "Successed Autofill auth.");
mReplyIntent.putExtra(
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
responseBuilder.build());
activity.setResult(Activity.RESULT_OK, mReplyIntent);
} else {
Log.w(activity.getClass().getName(), "Failed Autofill auth.");
activity.setResult(Activity.RESULT_CANCELED);
}
}
/**
* Utility method to loop and close each activity with return data
*/
public static void onActivityResultSetResult(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == AUTOFILL_RESPONSE_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
activity.setResult(resultCode, data);
}
if (resultCode == Activity.RESULT_CANCELED) {
activity.setResult(Activity.RESULT_CANCELED);
}
}
}
/**
* Utility method to loop and close each activity with return data
*/
public static void onActivityResultSetResultAndFinish(Activity activity, int requestCode, int resultCode, Intent data) {
if (requestCode == AUTOFILL_RESPONSE_REQUEST_CODE) {
onActivityResultSetResult(activity, requestCode, resultCode, data);
activity.finish();
}
}
private static RemoteViews newRemoteViews(String packageName, String remoteViewsText) {
RemoteViews presentation =
new RemoteViews(packageName, R.layout.autofill_service_list_item);
presentation.setTextViewText(R.id.text, remoteViewsText);
return presentation;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2017 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 2 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.keepassdroid.autofill;
import android.app.assist.AssistStructure;
import android.content.IntentSender;
import android.os.Build;
import android.os.CancellationSignal;
import android.service.autofill.AutofillService;
import android.service.autofill.FillCallback;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.SaveCallback;
import android.service.autofill.SaveRequest;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.util.Log;
import android.view.autofill.AutofillId;
import android.widget.RemoteViews;
import com.kunzisoft.keepass.R;
import java.util.Arrays;
import java.util.List;
@RequiresApi(api = Build.VERSION_CODES.O)
public class KeeAutofillService extends AutofillService {
private static final String TAG = "KeeAutofillService";
@Override
public void onFillRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal,
@NonNull FillCallback callback) {
List<FillContext> fillContexts = request.getFillContexts();
AssistStructure latestStructure = fillContexts.get(fillContexts.size() - 1).getStructure();
cancellationSignal.setOnCancelListener(() ->
Log.e(TAG, "Cancel autofill not implemented in this sample.")
);
FillResponse.Builder responseBuilder = new FillResponse.Builder();
// Check user's settings for authenticating Responses and Datasets.
StructureParser.Result parseResult = new StructureParser(latestStructure).parse();
AutofillId[] autofillIds = parseResult.allAutofillIds();
if (!Arrays.asList(autofillIds).isEmpty()) {
// If the entire Autofill Response is authenticated, AuthActivity is used
// to generate Response.
IntentSender sender = AutoFillAuthActivity.getAuthIntentSenderForResponse(this);
RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.autofill_service_unlock);
responseBuilder.setAuthentication(autofillIds, sender, presentation);
callback.onSuccess(responseBuilder.build());
}
}
@Override
public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) {
// TODO Save autofill
//callback.onFailure(getString(R.string.autofill_not_support_save));
}
@Override
public void onConnected() {
Log.d(TAG, "onConnected");
}
@Override
public void onDisconnected() {
Log.d(TAG, "onDisconnected");
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.autofill;
import android.app.assist.AssistStructure;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.text.InputType;
import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Parse AssistStructure and guess username and password fields.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
class StructureParser {
static private final String TAG = StructureParser.class.getName();
final private AssistStructure structure;
private Result result;
private AutofillId usernameCandidate;
StructureParser(AssistStructure structure) {
this.structure = structure;
}
Result parse() {
result = new Result();
usernameCandidate = null;
for (int i=0; i<structure.getWindowNodeCount(); ++i) {
AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
result.title.add(windowNode.getTitle());
result.webDomain.add(windowNode.getRootViewNode().getWebDomain());
parseViewNode(windowNode.getRootViewNode());
}
// If not explicit username field found, add the field just before password field.
if (result.username.isEmpty() && result.email.isEmpty()
&& !result.password.isEmpty() && usernameCandidate != null)
result.username.add(usernameCandidate);
return result;
}
private void parseViewNode(AssistStructure.ViewNode node) {
String[] hints = node.getAutofillHints();
if (hints != null && hints.length > 0) {
if (Arrays.stream(hints).anyMatch(View.AUTOFILL_HINT_USERNAME::equals))
result.username.add(node.getAutofillId());
else if (Arrays.stream(hints).anyMatch(View.AUTOFILL_HINT_EMAIL_ADDRESS::equals))
result.email.add(node.getAutofillId());
else if (Arrays.stream(hints).anyMatch(View.AUTOFILL_HINT_PASSWORD::equals))
result.password.add(node.getAutofillId());
else
Log.d(TAG, "unsupported hints");
} else if (node.getAutofillType() == View.AUTOFILL_TYPE_TEXT) {
int inputType = node.getInputType();
if ((inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) > 0)
result.email.add(node.getAutofillId());
else if ((inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD) > 0)
result.password.add(node.getAutofillId());
else if (result.password.isEmpty())
usernameCandidate = node.getAutofillId();
}
for (int i=0; i<node.getChildCount(); ++i)
parseViewNode(node.getChildAt(i));
}
@RequiresApi(api = Build.VERSION_CODES.O)
static class Result {
final List<CharSequence> title;
final List<String> webDomain;
final List<AutofillId> username;
final List<AutofillId> email;
final List<AutofillId> password;
private Result() {
title = new ArrayList<>();
webDomain = new ArrayList<>();
username = new ArrayList<>();
email = new ArrayList<>();
password = new ArrayList<>();
}
AutofillId[] allAutofillIds() {
ArrayList<AutofillId> all = new ArrayList<>();
all.addAll(username);
all.addAll(email);
all.addAll(password);
return all.toArray(new AutofillId[0]);
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2017 Brian Pellin.
*
* This file is part of KeePassDroid.
*
* KeePassDroid 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 2 of the License, or
* (at your option) any later version.
*
* KeePassDroid 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 KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid.compat;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.spec.AlgorithmParameterSpec;
public class KeyGenParameterSpecCompat {
private static Class builder;
private static Constructor buildConst;
private static Method builderBuild;
private static Method setBlockModes;
private static Method setUserAuthReq;
private static Method setEncPad;
private static boolean available;
static {
try {
builder = Class.forName("android.security.keystore.KeyGenParameterSpec$Builder");
buildConst = builder.getConstructor(String.class, int.class);
builderBuild = builder.getMethod("build", (Class [])null);
setBlockModes = builder.getMethod("setBlockModes", String[].class);
setUserAuthReq = builder.getMethod("setUserAuthenticationRequired", new Class []{boolean.class});
setEncPad = builder.getMethod("setEncryptionPaddings", String[].class);
available = true;
} catch (Exception e) {
available = false;
}
}
public static AlgorithmParameterSpec build(String keystoreAlias, int purpose, String blockMode,
boolean userAuthReq, String encPadding) {
if (!available) {
return null;
}
try {
Object inst = buildConst.newInstance(keystoreAlias, purpose);
inst = setBlockModes.invoke(inst, new Object[] {new String[] {blockMode}});
inst = setUserAuthReq.invoke(inst, userAuthReq);
inst = setEncPad.invoke(inst, new Object[] {new String[] {encPadding}});
return (AlgorithmParameterSpec) builderBuild.invoke(inst, null);
} catch (Exception e) {
return null;
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2017 Brian Pellin.
*
* This file is part of KeePassDroid.
*
* KeePassDroid 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 2 of the License, or
* (at your option) any later version.
*
* KeePassDroid 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 KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid.compat;
import android.app.KeyguardManager;
import java.lang.reflect.Method;
public class KeyguardManagerCompat {
private static Method isKeyguardSecure;
private static boolean available;
static {
try {
isKeyguardSecure = KeyguardManager.class.getMethod("isKeyguardSecure", (Class[]) null);
available = true;
} catch (Exception e) {
available = false;
}
}
public static boolean isKeyguardSecure(KeyguardManager inst) {
if (!available) {
return false;
}
try {
return (boolean) isKeyguardSecure.invoke(inst, null);
} catch (Exception e) {
return false;
}
}
}

View File

@@ -51,6 +51,6 @@ public class StorageAF {
if (!supportsStorageFramework()) { return false; }
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return prefs.getBoolean(ctx.getString(R.string.saf_key), ctx.getResources().getBoolean(R.bool.saf_default));
return prefs.getBoolean(ctx.getString(R.string.saf_key), ctx.getResources().getBoolean(R.bool.settings_saf_default));
}
}

View File

@@ -19,6 +19,7 @@
*/
package com.keepassdroid.database;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -49,6 +50,14 @@ public class BinaryPool {
public Set<Entry<Integer, ProtectedBinary>> entrySet() {
return pool.entrySet();
}
public void clear() {
pool.clear();
}
public Collection<ProtectedBinary> binaries() {
return pool.values();
}
private class AddBinaries extends EntryHandler<PwEntryV4> {
@@ -72,7 +81,7 @@ public class BinaryPool {
}
private void poolAdd(ProtectedBinary pb) {
public void poolAdd(ProtectedBinary pb) {
assert(pb != null);
if (poolFind(pb) != -1) return;

View File

@@ -17,43 +17,40 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid;
package com.keepassdroid.database;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SyncFailedException;
import java.util.HashSet;
import java.util.Set;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwGroup;
import com.keepassdroid.database.exception.ContentFileNotFoundException;
import com.keepassdroid.database.exception.InvalidDBException;
import com.keepassdroid.database.exception.InvalidPasswordException;
import com.keepassdroid.database.exception.PwDbOutputException;
import com.keepassdroid.database.load.Importer;
import com.keepassdroid.database.load.ImporterFactory;
import com.keepassdroid.database.save.PwDbOutput;
import com.keepassdroid.icons.DrawableFactory;
import com.keepassdroid.search.SearchDbHelper;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.utils.UriUtil;
import com.kunzisoft.keepass.R;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SyncFailedException;
/**
* @author bpellin
*/
public class Database {
public Set<PwGroup> dirty = new HashSet<PwGroup>();
public PwDatabase pm;
public Uri mUri;
public SearchDbHelper searchHelper;
@@ -92,6 +89,26 @@ public class Database {
readOnly = !file.canWrite();
}
try {
passUrisAsInputStreams(ctx, uri, password, keyfile, status, debug, 0);
} catch (InvalidPasswordException e) {
// Retry with rounds fix
try {
passUrisAsInputStreams(ctx, uri, password, keyfile, status, debug, getFixRounds(ctx));
} catch (Exception e2) {
// Rethrow original exception
throw e;
}
}
}
private long getFixRounds(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return prefs.getLong(ctx.getString(R.string.roundsFix_key), ctx.getResources().getInteger(R.integer.roundsFix_default));
}
private void passUrisAsInputStreams(Context ctx, Uri uri, String password, Uri keyfile, UpdateStatus status, boolean debug, long roundsFix) throws IOException, FileNotFoundException, InvalidDBException {
InputStream is, kfIs;
try {
is = UriUtil.getUriInputStream(ctx, uri);
@@ -106,7 +123,7 @@ public class Database {
Log.e("KPD", "Database::LoadData", e);
throw ContentFileNotFoundException.getInstance(keyfile);
}
LoadData(ctx, is, password, kfIs, status, debug);
LoadData(ctx, is, password, kfIs, status, debug, roundsFix);
}
public void LoadData(Context ctx, InputStream is, String password, InputStream kfIs, boolean debug) throws IOException, InvalidDBException {
@@ -114,6 +131,10 @@ public class Database {
}
public void LoadData(Context ctx, InputStream is, String password, InputStream kfIs, UpdateStatus status, boolean debug) throws IOException, InvalidDBException {
LoadData(ctx, is, password, kfIs, status, debug, 0);
}
public void LoadData(Context ctx, InputStream is, String password, InputStream kfIs, UpdateStatus status, boolean debug, long roundsFix) throws IOException, InvalidDBException {
BufferedInputStream bis = new BufferedInputStream(is);
if ( ! bis.markSupported() ) {
@@ -127,7 +148,7 @@ public class Database {
bis.reset(); // Return to the start
pm = imp.openDatabase(bis, password, kfIs, status);
pm = imp.openDatabase(bis, password, kfIs, status, roundsFix);
if ( pm != null ) {
PwGroup root = pm.rootGroup;
pm.populateGlobals(root);
@@ -196,7 +217,6 @@ public class Database {
}
public void clear() {
dirty.clear();
drawFactory.clear();
pm = null;
@@ -204,16 +224,4 @@ public class Database {
loaded = false;
passwordEncodingError = false;
}
public void markAllGroupsAsDirty() {
for ( PwGroup group : pm.getGroups() ) {
dirty.add(group);
}
// TODO: This should probably be abstracted out
// The root tree in v3 is not an 'official' tree
if ( pm instanceof PwDatabaseV3 ) {
dirty.add(pm.rootGroup);
}
}
}

View File

@@ -19,12 +19,8 @@
*/
package com.keepassdroid.database;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
@@ -36,8 +32,6 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import android.os.DropBoxManager.Entry;
import com.keepassdroid.crypto.finalkey.FinalKey;
import com.keepassdroid.crypto.finalkey.FinalKeyFactory;
import com.keepassdroid.database.exception.InvalidKeyFileException;
@@ -252,7 +246,7 @@ public abstract class PwDatabase {
public abstract void setNumRounds(long rounds) throws NumberFormatException;
public abstract boolean appSettingsEnabled();
public abstract boolean algorithmSettingsEnabled();
public abstract PwEncryptionAlgorithm getEncAlgorithm();
@@ -262,7 +256,7 @@ public abstract class PwDatabase {
parent = rootGroup;
}
parent.childGroups.add(newGroup);
parent.addChildGroup(newGroup);
newGroup.setParent(parent);
groups.put(newGroup.getId(), newGroup);
@@ -271,15 +265,16 @@ public abstract class PwDatabase {
public void removeGroupFrom(PwGroup remove, PwGroup parent) {
// Remove tree from parent tree
parent.childGroups.remove(remove);
if (parent != null) {
parent.removeChildGroup(remove);
}
groups.remove(remove.getId());
}
public void addEntryTo(PwEntry newEntry, PwGroup parent) {
// Add entry to parent
if (parent != null) {
parent.childEntries.add(newEntry);
parent.addChildEntry(newEntry);
}
newEntry.setParent(parent);
@@ -289,7 +284,7 @@ public abstract class PwDatabase {
public void removeEntryFrom(PwEntry remove, PwGroup parent) {
// Remove entry for parent
if (parent != null) {
parent.childEntries.remove(remove);
parent.removeChildEntry(remove);
}
entries.remove(remove.getUUID());
}
@@ -337,28 +332,73 @@ public abstract class PwDatabase {
}
}
/**
* Determine if RecycleBin is available or not for this version of database
* @return true if RecycleBin enable
*/
public boolean isRecycleBinAvailable() {
return false;
}
/**
* Determine if RecycleBin is enable or not
* @return true if RecycleBin enable, false if is not available or not enable
*/
public boolean isRecycleBinEnable() {
return false;
}
/**
* Define if a Group must be delete or recycle
* @param group Group to remove
* @return true if group can be recycle, false elsewhere
*/
public boolean canRecycle(PwGroup group) {
return false;
}
/**
* Define if an Entry must be delete or recycle
* @param entry Entry to remove
* @return true if entry can be recycle, false elsewhere
*/
public boolean canRecycle(PwEntry entry) {
return false;
}
public void recycle(PwGroup group) {
// Assume calls to this are protected by calling inRecyleBin
throw new RuntimeException("Call not valid for .kdb databases.");
}
public void recycle(PwEntry entry) {
// Assume calls to this are protected by calling inRecyleBin
throw new RuntimeException("Call not valid for .kdb databases.");
}
public void undoRecycle(PwGroup group, PwGroup origParent) {
throw new RuntimeException("Call not valid for .kdb databases.");
}
public void undoRecycle(PwEntry entry, PwGroup origParent) {
throw new RuntimeException("Call not valid for .kdb databases.");
}
public void deleteGroup(PwGroup group) {
PwGroup parent = group.getParent();
removeGroupFrom(group, parent);
parent.touch(false, true);
}
public void deleteEntry(PwEntry entry) {
PwGroup parent = entry.getParent();
removeEntryFrom(entry, parent);
parent.touch(false, true);
}
// TODO Delete group
public void undoDeleteGroup(PwGroup group, PwGroup origParent) {
addGroupTo(group, origParent);
}
public void undoDeleteEntry(PwEntry entry, PwGroup origParent) {

View File

@@ -51,7 +51,6 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import com.keepassdroid.database.exception.InvalidKeyFileException;
@@ -76,7 +75,7 @@ public class PwDatabaseV3 extends PwDatabase {
// Algorithm used to encrypt the database
public PwEncryptionAlgorithm algorithm;
public int numKeyEncRounds;
@Override
public PwEncryptionAlgorithm getEncAlgorithm() {
return algorithm;
@@ -164,7 +163,7 @@ public class PwDatabaseV3 extends PwDatabase {
List<PwGroup> rootChildGroups = getGrpRoots();
root.setGroups(rootChildGroups);
root.childEntries = new ArrayList<PwEntry>();
root.childEntries = new ArrayList<>();
root.level = -1;
for (int i = 0; i < rootChildGroups.size(); i++) {
PwGroupV3 grp = (PwGroupV3) rootChildGroups.get(i);
@@ -180,12 +179,12 @@ public class PwDatabaseV3 extends PwDatabase {
currentGroup.childEntries = getEntries(currentGroup);
// set parent in child entries
for (int i = 0; i < currentGroup.childEntries.size(); i++) {
for (int i = 0; i < currentGroup.numbersOfChildEntries(); i++) {
PwEntryV3 entry = (PwEntryV3) currentGroup.childEntries.get(i);
entry.parent = currentGroup;
}
// recursively construct child groups
for (int i = 0; i < currentGroup.childGroups.size(); i++) {
for (int i = 0; i < currentGroup.numbersOfChildGroups(); i++) {
PwGroupV3 grp = (PwGroupV3) currentGroup.childGroups.get(i);
grp.parent = currentGroup;
constructTree((PwGroupV3) currentGroup.childGroups.get(i));
@@ -263,7 +262,7 @@ public class PwDatabaseV3 extends PwDatabase {
}
@Override
public boolean appSettingsEnabled() {
public boolean algorithmSettingsEnabled() {
return true;
}

View File

@@ -19,12 +19,29 @@
*/
package com.keepassdroid.database;
import java.io.FileInputStream;
import android.webkit.URLUtil;
import com.keepassdroid.collections.VariantDictionary;
import com.keepassdroid.crypto.CryptoUtil;
import com.keepassdroid.crypto.engine.AesEngine;
import com.keepassdroid.crypto.engine.CipherEngine;
import com.keepassdroid.crypto.keyDerivation.AesKdf;
import com.keepassdroid.crypto.keyDerivation.KdfEngine;
import com.keepassdroid.crypto.keyDerivation.KdfFactory;
import com.keepassdroid.crypto.keyDerivation.KdfParameters;
import com.keepassdroid.database.exception.InvalidKeyFileException;
import com.keepassdroid.utils.EmptyUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@@ -36,29 +53,8 @@ import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.spongycastle.crypto.engines.AESEngine;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import android.webkit.URLUtil;
import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.collections.VariantDictionary;
import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.crypto.CryptoUtil;
import com.keepassdroid.crypto.PwStreamCipherFactory;
import com.keepassdroid.crypto.engine.AesEngine;
import com.keepassdroid.crypto.engine.CipherEngine;
import com.keepassdroid.crypto.keyDerivation.AesKdf;
import com.keepassdroid.crypto.keyDerivation.KdfEngine;
import com.keepassdroid.crypto.keyDerivation.KdfFactory;
import com.keepassdroid.crypto.keyDerivation.KdfParameters;
import com.keepassdroid.database.exception.InvalidKeyFileException;
import com.keepassdroid.utils.EmptyUtils;
public class PwDatabaseV4 extends PwDatabase {
@@ -73,6 +69,7 @@ public class PwDatabaseV4 extends PwDatabase {
public UUID dataCipher = AesEngine.CIPHER_UUID;
public CipherEngine dataEngine = new AesEngine();
public PwCompressionAlgorithm compressionAlgorithm = PwCompressionAlgorithm.Gzip;
// TODO: Refactor me away to get directly from kdfParameters
public long numKeyEncRounds = 6000;
public Date nameChanged = DEFAULT_NOW;
public Date settingsChanged = DEFAULT_NOW;
@@ -103,7 +100,8 @@ public class PwDatabaseV4 extends PwDatabase {
public Map<String, String> customData = new HashMap<String, String>();
public KdfParameters kdfParameters = KdfFactory.getDefaultParameters();
public VariantDictionary publicCustomData = new VariantDictionary();
public BinaryPool binPool = new BinaryPool();
public String localizedAppName = "KeePassDroid";
public class MemoryProtectionConfig {
@@ -131,7 +129,7 @@ public class PwDatabaseV4 extends PwDatabase {
throws InvalidKeyFileException, IOException {
assert(key != null);
byte[] fKey;
byte[] fKey = new byte[]{};
if ( key.length() > 0 && keyInputStream != null) {
return getCompositeKey(key, keyInputStream);
@@ -139,8 +137,6 @@ public class PwDatabaseV4 extends PwDatabase {
fKey = getPasswordKey(key);
} else if ( keyInputStream != null) {
fKey = getFileKey(keyInputStream);
} else {
throw new IllegalArgumentException( "Key cannot be empty." );
}
MessageDigest md;
@@ -175,13 +171,24 @@ public class PwDatabaseV4 extends PwDatabase {
Arrays.fill(cmpKey, (byte)0);
}
}
public void makeFinalKey(byte[] masterSeed, KdfParameters kdfP) throws IOException {
makeFinalKey(masterSeed, kdfP, 0);
}
public void makeFinalKey(byte[] masterSeed, KdfParameters kdfP, long roundsFix)
throws IOException {
KdfEngine kdfEngine = KdfFactory.get(kdfP.kdfUUID);
if (kdfEngine == null) {
throw new IOException("Unknown key derivation function");
}
// Set to 6000 rounds to open corrupted database
if (roundsFix > 0 && kdfP.kdfUUID.equals(AesKdf.CIPHER_UUID)) {
kdfP.setUInt32(AesKdf.ParamRounds, roundsFix);
numKeyEncRounds = roundsFix;
}
byte[] transformedMasterKey = kdfEngine.transform(masterKey, kdfP);
if (transformedMasterKey.length != 32) {
transformedMasterKey = CryptoUtil.hashSha256(transformedMasterKey);
@@ -293,7 +300,7 @@ public class PwDatabaseV4 extends PwDatabase {
}
@Override
public boolean appSettingsEnabled() {
public boolean algorithmSettingsEnabled() {
return false;
}
@@ -353,15 +360,23 @@ public class PwDatabaseV4 extends PwDatabase {
recycleBinUUID = recycleBin.uuid;
}
}
@Override
public boolean isRecycleBinAvailable() {
return true;
}
@Override
public boolean isRecycleBinEnable() {
return recycleBinEnabled;
}
@Override
public boolean canRecycle(PwGroup group) {
if (!recycleBinEnabled) {
return false;
}
PwGroup recycle = getRecycleBin();
return (recycle == null) || (!group.isContainedIn(recycle));
}
@@ -370,11 +385,25 @@ public class PwDatabaseV4 extends PwDatabase {
if (!recycleBinEnabled) {
return false;
}
PwGroup parent = entry.getParent();
return (parent != null) && canRecycle(parent);
}
@Override
public void recycle(PwGroup group) {
ensureRecycleBin();
PwGroup parent = group.getParent();
removeGroupFrom(group, parent);
parent.touch(false, true);
PwGroup recycleBin = getRecycleBin();
addGroupTo(group, recycleBin);
group.touch(false, true);
// TODO ? group.touchLocation();
}
@Override
public void recycle(PwEntry entry) {
ensureRecycleBin();
@@ -390,6 +419,15 @@ public class PwDatabaseV4 extends PwDatabase {
entry.touchLocation();
}
@Override
public void undoRecycle(PwGroup group, PwGroup origParent) {
PwGroup recycleBin = getRecycleBin();
removeGroupFrom(group, recycleBin);
addGroupTo(group, origParent);
}
@Override
public void undoRecycle(PwEntry entry, PwGroup origParent) {
@@ -402,15 +440,14 @@ public class PwDatabaseV4 extends PwDatabase {
@Override
public void deleteEntry(PwEntry entry) {
super.deleteEntry(entry);
deletedObjects.add(new PwDeletedObject(entry.getUUID()));
}
@Override
public void undoDeleteEntry(PwEntry entry, PwGroup origParent) {
super.undoDeleteEntry(entry, origParent);
deletedObjects.remove(entry);
// TODO undo delete entry
deletedObjects.remove(entry);
}
@Override

View File

@@ -23,16 +23,8 @@ import android.annotation.SuppressLint;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
@SuppressLint("SimpleDateFormat")
public class PwDatabaseV4XML {
public static final SimpleDateFormat dateFormat;
static {
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public static final String ElemDocNode = "KeePassFile";
public static final String ElemMeta = "Meta";
public static final String ElemRoot = "Root";
@@ -134,4 +126,15 @@ public class PwDatabaseV4XML {
public static final String ElemCustomData = "CustomData";
public static final String ElemStringDictExItem = "Item";
public static final ThreadLocal<SimpleDateFormat> dateFormatter =
new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat dateFormat;
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat;
}
};
}

View File

@@ -19,6 +19,7 @@
*/
package com.keepassdroid.database;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -32,7 +33,7 @@ import com.keepassdroid.utils.Types;
* @author bpellin
*
*/
public class PwDate implements Cloneable {
public class PwDate implements Cloneable, Serializable {
private static final int DATE_SIZE = 5;

View File

@@ -95,7 +95,6 @@ public class PwDbHeaderV4 extends PwDbHeader {
public byte[] streamStartBytes = new byte[32];
public CrsAlgorithm innerRandomStream;
public long version;
public List<ProtectedBinary> binaries = new ArrayList<ProtectedBinary>();
public PwDbHeaderV4(PwDatabaseV4 d) {
db = d;
@@ -194,7 +193,9 @@ public class PwDbHeaderV4 extends PwDbHeader {
if (!db.kdfParameters.kdfUUID.equals(kdfR.uuid)) {
db.kdfParameters = kdfR.getDefaultParameters();
}
db.kdfParameters.setUInt64(AesKdf.ParamRounds, LEDataInputStream.readLong(fieldData, 0));
long rounds = LEDataInputStream.readLong(fieldData, 0);
db.kdfParameters.setUInt64(AesKdf.ParamRounds, rounds);
db.numKeyEncRounds = rounds;
break;
case PwDbHeaderV4Fields.EncryptionIV:

View File

@@ -19,36 +19,22 @@
*/
package com.keepassdroid.database;
import com.keepassdroid.database.iterator.EntrySearchStringIterator;
import com.keepassdroid.database.security.ProtectedString;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import com.keepassdroid.database.iterator.EntrySearchStringIterator;
import com.keepassdroid.utils.SprEngine;
public abstract class PwEntry implements Cloneable {
public abstract class PwEntry extends PwNode implements Cloneable {
protected static final String PMS_TAN_ENTRY = "<TAN>";
public static class EntryNameComparator implements Comparator<PwEntry> {
public int compare(PwEntry object1, PwEntry object2) {
return object1.getTitle().compareToIgnoreCase(object2.getTitle());
}
}
public PwIconStandard icon = PwIconStandard.FIRST;
public PwEntry() {
}
public static PwEntry getInstance(PwGroup parent) {
return PwEntry.getInstance(parent, true, true);
}
public static PwEntry getInstance(PwGroup parent, boolean initId, boolean initDates) {
if (parent instanceof PwGroupV3) {
return new PwEntryV3((PwGroupV3)parent);
}
@@ -72,12 +58,17 @@ public abstract class PwEntry implements Cloneable {
return newEntry;
}
public PwEntry clone(boolean deepStrings) {
return (PwEntry) clone();
}
public void assign(PwEntry source) {
@Override
public Type getType() {
return Type.ENTRY;
}
public void assign(PwEntry source) {
icon = source.icon;
}
@@ -109,12 +100,10 @@ public abstract class PwEntry implements Cloneable {
public abstract String getPassword(boolean decodeRef, PwDatabase db);
public abstract String getUrl(boolean decodeRef, PwDatabase db);
public abstract String getNotes(boolean decodeRef, PwDatabase db);
public abstract Date getCreationTime();
public abstract Date getLastModificationTime();
public abstract Date getLastAccessTime();
public abstract Date getExpiryTime();
public abstract boolean expires();
public abstract PwGroup getParent();
public abstract void setTitle(String title, PwDatabase db);
public abstract void setUsername(String user, PwDatabase db);
@@ -147,11 +136,53 @@ public abstract class PwEntry implements Cloneable {
}
}
// TODO encapsulate extra fields
/**
* To redefine if version of entry allow extra field,
* @return true if entry allows extra field
*/
public boolean allowExtraFields() {
return false;
}
/**
* Retrieve extra fields to show, key is the label, value is the value of field
* @param pm Database
* @return Map of label/value
*/
public Map<String, String> getExtraFields(PwDatabase pm) {
return new HashMap<>();
}
/**
* Retrieve extra protected fields to show, key is the label, value is the value protected of field
* @return Map of label/value
*/
public Map<String, ProtectedString> getExtraProtectedFields() {
return new HashMap<>();
}
/**
* Add an extra field to the list
* @param label Label of field, must be unique
* @param value Value of field
*/
public void addField(String label, ProtectedString value) {}
/**
* Delete all extra fields
*/
public void removeExtraFields() {}
/**
* If it's a node with only meta information like Meta-info SYSTEM Database Color
* @return false by default, true if it's a meta stream
*/
public boolean isMetaStream() {
return false;
}
public EntrySearchStringIterator stringIterator() {
return EntrySearchStringIterator.getInstance(this);
}
@@ -173,10 +204,87 @@ public abstract class PwEntry implements Cloneable {
public void touchLocation() { }
public abstract void setParent(PwGroup parent);
public boolean isSearchingEnabled() {
return false;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PwEntry pwEntry = (PwEntry) o;
return isSameType(pwEntry)
&& (getUUID() != null ? getUUID().equals(pwEntry.getUUID()) : pwEntry.getUUID() == null);
}
@Override
public int hashCode() {
return getUUID() != null ? getUUID().hashCode() : 0;
}
/**
* Comparator of Entry by Name
*/
public static class EntryNameComparator implements Comparator<PwEntry> {
private boolean ascending;
public EntryNameComparator() {
this(true);
}
public EntryNameComparator(boolean ascending) {
this.ascending = ascending;
}
public int compare(PwEntry object1, PwEntry object2) {
if (object1.equals(object2))
return 0;
int entryTitleComp = object1.getTitle().compareToIgnoreCase(object2.getTitle());
// If same title, can be different
if (entryTitleComp == 0) {
return object1.hashCode() - object2.hashCode();
}
// If descending
if (!ascending)
entryTitleComp = -entryTitleComp;
return entryTitleComp;
}
}
/**
* Comparator of Entry by Creation
*/
public static class EntryCreationComparator implements Comparator<PwEntry> {
private boolean ascending;
public EntryCreationComparator() {
this(true);
}
public EntryCreationComparator(boolean ascending) {
this.ascending = ascending;
}
public int compare(PwEntry object1, PwEntry object2) {
if (object1.equals(object2))
return 0;
int entryCreationComp = object1.getCreationTime().compareTo(object2.getCreationTime());
// If same creation, can be different
if (entryCreationComp == 0) {
return object1.hashCode() - object2.hashCode();
}
// If descending
if (!ascending)
entryCreationComp = -entryCreationComp;
return entryCreationComp;
}
}
}

View File

@@ -43,14 +43,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
package com.keepassdroid.database;
// PhoneID
import com.keepassdroid.utils.Types;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.UUID;
import com.keepassdroid.utils.Types;
/**
* Structure containing information about one entry.
@@ -191,9 +191,8 @@ public class PwEntryV3 extends PwEntry {
tLastMod = new PwDate(now);
tExpire = new PwDate(NEVER_EXPIRE);
}
}
/**
* @return the actual password byte array.
*/
@@ -281,7 +280,6 @@ public class PwEntryV3 extends PwEntry {
return true;
}
@Override
public void assign(PwEntry source) {

View File

@@ -19,19 +19,21 @@
*/
package com.keepassdroid.database;
import com.keepassdroid.database.security.ProtectedBinary;
import com.keepassdroid.database.security.ProtectedString;
import com.keepassdroid.utils.SprEngine;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
import com.keepassdroid.database.security.ProtectedBinary;
import com.keepassdroid.database.security.ProtectedString;
import com.keepassdroid.utils.SprEngine;
public class PwEntryV4 extends PwEntry implements ITimeLogger {
public static final String STR_TITLE = "Title";
public static final String STR_USERNAME = "UserName";
@@ -41,14 +43,14 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
public PwGroupV4 parent;
public UUID uuid = PwDatabaseV4.UUID_ZERO;
public HashMap<String, ProtectedString> strings = new HashMap<String, ProtectedString>();
public HashMap<String, ProtectedBinary> binaries = new HashMap<String, ProtectedBinary>();
private HashMap<String, ProtectedString> fields = new HashMap<>();
public HashMap<String, ProtectedBinary> binaries = new HashMap<>();
public PwIconCustom customIcon = PwIconCustom.ZERO;
public String foregroundColor = "";
public String backgroupColor = "";
public String overrideURL = "";
public AutoType autoType = new AutoType();
public ArrayList<PwEntryV4> history = new ArrayList<PwEntryV4>();
public ArrayList<PwEntryV4> history = new ArrayList<>();
private Date parentGroupLastMod = PwDatabaseV4.DEFAULT_NOW;
private Date creation = PwDatabaseV4.DEFAULT_NOW;
@@ -62,7 +64,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
public String tags = "";
public Map<String, String> customData = new HashMap<String, String>();
public class AutoType implements Cloneable {
public class AutoType implements Cloneable, Serializable {
private static final long OBF_OPT_NONE = 0;
public boolean enabled = true;
@@ -123,28 +125,18 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
}
}
@SuppressWarnings("unchecked")
@Override
public PwEntry clone(boolean deepStrings) {
PwEntryV4 entry = (PwEntryV4) super.clone(deepStrings);
if (deepStrings) {
entry.strings = (HashMap<String, ProtectedString>) strings.clone();
entry.fields = (HashMap<String, ProtectedString>) fields.clone();
}
return entry;
}
@SuppressWarnings("unchecked")
public PwEntryV4 cloneDeep() {
PwEntryV4 entry = (PwEntryV4) clone(true);
entry.binaries = (HashMap<String, ProtectedBinary>) binaries.clone();
entry.history = (ArrayList<PwEntryV4>) history.clone();
entry.autoType = (AutoType) autoType.clone();
return entry;
}
@Override
public void assign(PwEntry source) {
@@ -162,7 +154,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
private void assign(PwEntryV4 source) {
parent = source.parent;
uuid = source.uuid;
strings = source.strings;
fields = source.fields;
binaries = source.binaries;
customIcon = source.customIcon;
foregroundColor = source.foregroundColor;
@@ -313,7 +305,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
}
public String getString(String key) {
ProtectedString value = strings.get(key);
ProtectedString value = fields.get(key);
if ( value == null ) return new String("");
@@ -322,7 +314,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
public void setString(String key, String value, boolean protect) {
ProtectedString ps = new ProtectedString(protect, value);
strings.put(key, ps);
fields.put(key, ps);
}
public Date getLocationChanged() {
@@ -367,22 +359,26 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
} else {
return customIcon;
}
}
public static boolean IsStandardString(String key) {
return key.equals(STR_TITLE) || key.equals(STR_USERNAME)
|| key.equals(STR_PASSWORD) || key.equals(STR_URL)
|| key.equals(STR_NOTES);
}
public void createBackup(PwDatabaseV4 db) {
PwEntryV4 copy = cloneDeep();
copy.history = new ArrayList<PwEntryV4>();
copy.history = new ArrayList<>();
history.add(copy);
if (db != null) maintainBackups(db);
}
@SuppressWarnings("unchecked")
public PwEntryV4 cloneDeep() {
PwEntryV4 entry = (PwEntryV4) clone(true);
entry.binaries = (HashMap<String, ProtectedBinary>) binaries.clone();
entry.history = (ArrayList<PwEntryV4>) history.clone();
entry.autoType = (AutoType) autoType.clone();
return entry;
}
private boolean maintainBackups(PwDatabaseV4 db) {
boolean deleted = false;
@@ -432,13 +428,73 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
history.remove(index);
}
}
@Override
public boolean allowExtraFields() {
return true;
}
public Map<String, ProtectedString> getFields() {
return fields;
}
@Override
public Map<String, ProtectedString> getExtraProtectedFields() {
Map<String, ProtectedString> protectedFields = super.getExtraProtectedFields();
if (fields.size() > 0) {
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
String key = pair.getKey();
if (!PwEntryV4.IsStandardField(key)) {
protectedFields.put(key, pair.getValue());
}
}
}
return protectedFields;
}
@Override
public Map<String, String> getExtraFields(PwDatabase pm) {
Map<String, String> extraFields = super.getExtraFields(pm);
SprEngine spr = SprEngine.getInstance(pm);
// Display custom fields
if (fields.size() > 0) {
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
String key = pair.getKey();
// TODO Add hidden style for protection field
if (!PwEntryV4.IsStandardField(key)) {
extraFields.put(key, spr.compile(pair.getValue().toString(), this, pm));
}
}
}
return extraFields;
}
public static boolean IsStandardField(String key) {
return key.equals(STR_TITLE) || key.equals(STR_USERNAME)
|| key.equals(STR_PASSWORD) || key.equals(STR_URL)
|| key.equals(STR_NOTES);
}
public void addField(String label, ProtectedString value) {
fields.put(label, value);
}
@Override
public void removeExtraFields() {
Iterator<Entry<String, ProtectedString>> iter = fields.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, ProtectedString> pair = iter.next();
if (!PwEntryV4.IsStandardField(pair.getKey())) {
iter.remove();
}
}
}
private static final long FIXED_LENGTH_SIZE = 128; // Approximate fixed length size
public long getSize() {
long size = FIXED_LENGTH_SIZE;
for (Entry<String, ProtectedString> pair : strings.entrySet()) {
for (Entry<String, ProtectedString> pair : fields.entrySet()) {
size += pair.getKey().length();
size += pair.getValue().length();
}

View File

@@ -19,75 +19,117 @@
*/
package com.keepassdroid.database;
import com.keepassdroid.utils.StrUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import com.keepassdroid.utils.StrUtil;
public abstract class PwGroup extends PwNode {
public abstract class PwGroup {
public List<PwGroup> childGroups = new ArrayList<PwGroup>();
public List<PwEntry> childEntries = new ArrayList<PwEntry>();
// TODO Change dependency and make private
public List<PwGroup> childGroups = new ArrayList<>();
public List<PwEntry> childEntries = new ArrayList<>();
public String name = "";
public PwIconStandard icon;
public abstract PwGroup getParent();
public abstract void setParent(PwGroup parent);
private List<PwNode> children = new ArrayList<>();
public void initNewGroup(String nm, PwGroupId newId) {
setId(newId);
name = nm;
}
public void addChildGroup(PwGroup group) {
this.childGroups.add(group);
}
public void addChildEntry(PwEntry entry) {
this.childEntries.add(entry);
}
public void removeChildGroup(PwGroup group) {
this.childGroups.remove(group);
}
public void removeChildEntry(PwEntry entry) {
this.childEntries.remove(entry);
}
public int numbersOfChildGroups() {
return childGroups.size();
}
public int numbersOfChildEntries() {
return childEntries.size();
}
@Override
public Type getType() {
return Type.GROUP;
}
/**
* Filter MetaStream entries and return children
* @return List of direct children (one level below) as PwNode
*/
public List<PwNode> getDirectChildren() {
children.clear();
children.addAll(childGroups);
for(PwEntry child : childEntries) {
if (!child.isMetaStream())
children.add(child);
}
return children;
}
/**
* Number of direct elements in Node (one level below)
* @return Size of child elements, default is 0
*/
public int numberOfDirectChildren() {
return childGroups.size() + childEntries.size();
}
public boolean isContainedIn(PwGroup container) {
PwGroup cur = this;
while (cur != null) {
if (cur == container) {
return true;
}
cur = cur.getParent();
}
return false;
}
public abstract PwGroupId getId();
public abstract void setId(PwGroupId id);
public abstract String getName();
@Override
public String getDisplayTitle() {
return getName();
}
public abstract String getName();
public abstract Date getLastMod();
public PwIcon getIcon() {
return icon;
}
public void sortGroupsByName() {
Collections.sort(childGroups, new GroupNameComparator());
}
public static class GroupNameComparator implements Comparator<PwGroup> {
public int compare(PwGroup object1, PwGroup object2) {
return object1.getName().compareToIgnoreCase(object2.getName());
}
}
public abstract void setLastAccessTime(Date date);
public abstract void setLastModificationTime(Date date);
public void sortEntriesByName() {
Collections.sort(childEntries, new PwEntry.EntryNameComparator());
}
public void initNewGroup(String nm, PwGroupId newId) {
setId(newId);
name = nm;
}
public boolean isContainedIn(PwGroup container) {
PwGroup cur = this;
while (cur != null) {
if (cur == container) {
return true;
}
cur = cur.getParent();
}
public boolean allowAddEntryIfIsRoot() {
return false;
}
public void touch(boolean modified, boolean touchParents) {
Date now = new Date();
setLastAccessTime(now);
if (modified) {
@@ -100,7 +142,6 @@ public abstract class PwGroup {
}
}
public void searchEntries(SearchParameters sp, List<PwEntry> listStorage) {
if (sp == null) { return; }
if (listStorage == null) { return; }
@@ -147,7 +188,6 @@ public abstract class PwGroup {
complement.add(entry);
}
}
pg = complement;
}
else {
@@ -181,19 +221,94 @@ public abstract class PwGroup {
if (entryHandler != null) {
for (PwEntry entry : childEntries) {
if (!entryHandler.operate(entry)) return false;
}
}
for (PwGroup group : childGroups) {
if ((groupHandler != null) && !groupHandler.operate(group)) return false;
group.preOrderTraverseTree(groupHandler, entryHandler);
}
return true;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PwGroup pwGroup = (PwGroup) o;
return isSameType(pwGroup)
&& (getId() != null ? getId().equals(pwGroup.getId()) : pwGroup.getId() == null);
}
@Override
public int hashCode() {
PwGroupId groupId = getId();
return groupId != null ? groupId.hashCode() : 0;
}
/**
* Group comparator by name
*/
public static class GroupNameComparator implements Comparator<PwGroup> {
private boolean ascending;
public GroupNameComparator() {
this(true);
}
public GroupNameComparator(boolean ascending) {
this.ascending = ascending;
}
public int compare(PwGroup object1, PwGroup object2) {
if (object1.equals(object2))
return 0;
int groupNameComp = object1.getName().compareToIgnoreCase(object2.getName());
// If same name, can be different
if (groupNameComp == 0) {
return object1.hashCode() - object2.hashCode();
}
// If descending
if (!ascending)
groupNameComp = -groupNameComp;
return groupNameComp;
}
}
/**
* Group comparator by name
*/
public static class GroupCreationComparator implements Comparator<PwGroup> {
private boolean ascending;
public GroupCreationComparator() {
this(true);
}
public GroupCreationComparator(boolean ascending) {
this.ascending = ascending;
}
public int compare(PwGroup object1, PwGroup object2) {
if (object1.equals(object2))
return 0;
int groupCreationComp = object1.getCreationTime().compareTo(object2.getCreationTime());
// If same creation, can be different
if (groupCreationComp == 0) {
return object1.hashCode() - object2.hashCode();
}
// If descending
if (!ascending)
groupCreationComp = -groupCreationComp;
return groupCreationComp;
}
}
}

View File

@@ -1,5 +1,7 @@
package com.keepassdroid.database;
public abstract class PwGroupId {
import java.io.Serializable;
public abstract class PwGroupId implements Serializable {
}

View File

@@ -39,7 +39,7 @@ public class PwGroupIdV3 extends PwGroupId {
@Override
public int hashCode() {
Integer i = Integer.valueOf(id);
Integer i = id;
return i.hashCode();
}

View File

@@ -30,12 +30,15 @@ Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
package com.keepassdroid.database;
import android.content.Intent;
import android.os.Bundle;
import com.keepassdroid.utils.Types;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* @author Brian Pellin <bpellin@gmail.com>
* @author Naomaru Itoi <nao@phoneid.org>
@@ -43,8 +46,6 @@ import java.util.List;
* @author Dominik Reichl <dominik.reichl@t-online.de>
*/
public class PwGroupV3 extends PwGroup {
public PwGroupV3() {
}
public String toString() {
return name;
@@ -155,4 +156,11 @@ public class PwGroupV3 extends PwGroup {
tLastMod = new PwDate(date);
}
@Override
public Date getCreationTime() {
if(tCreation != null)
return tCreation.getJDate();
else
return new Date();
}
}

View File

@@ -48,9 +48,7 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
private long usageCount = 0;
public Map<String, String> customData = new HashMap<String, String>();
public PwGroupV4() {
}
public PwGroupV4() {}
public PwGroupV4(boolean createUUID, boolean setTimes, String name, PwIconStandard icon) {
if (createUUID) {
@@ -64,7 +62,14 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
this.name = name;
this.icon = icon;
}
@Override
public void initNewGroup(String nm, PwGroupId newId) {
super.initNewGroup(nm, newId);
lastAccess = lastMod = creation = parentGroupLastMod = new Date();
}
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership) {
AddGroup(subGroup, takeOwnership, false);
}
@@ -86,8 +91,8 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
public void AddEntry(PwEntryV4 pe, boolean takeOwnership, boolean updateLocationChanged) {
assert(pe != null);
childEntries.add(pe);
addChildEntry(pe);
if ( takeOwnership ) pe.parent = this;
@@ -102,7 +107,7 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
public void buildChildGroupsRecursive(List<PwGroup> list) {
list.add(this);
for ( int i = 0; i < childGroups.size(); i++) {
for ( int i = 0; i < numbersOfChildGroups(); i++) {
PwGroupV4 child = (PwGroupV4) childGroups.get(i);
child.buildChildGroupsRecursive(list);
@@ -110,11 +115,11 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
}
public void buildChildEntriesRecursive(List<PwEntry> list) {
for ( int i = 0; i < childEntries.size(); i++ ) {
for ( int i = 0; i < numbersOfChildEntries(); i++ ) {
list.add(childEntries.get(i));
}
for ( int i = 0; i < childGroups.size(); i++ ) {
for ( int i = 0; i < numbersOfChildGroups(); i++ ) {
PwGroupV4 child = (PwGroupV4) childGroups.get(i);
child.buildChildEntriesRecursive(list);
}
@@ -168,7 +173,6 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
public void setCreationTime(Date date) {
creation = date;
}
public void setExpiryTime(Date date) {
@@ -204,7 +208,11 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
@Override
public void setParent(PwGroup prt) {
parent = (PwGroupV4) prt;
}
@Override
public boolean allowAddEntryIfIsRoot() {
return true;
}
@Override
@@ -216,13 +224,6 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
}
}
@Override
public void initNewGroup(String nm, PwGroupId newId) {
super.initNewGroup(nm, newId);
lastAccess = lastMod = creation = parentGroupLastMod = new Date();
}
public boolean isSearchEnabled() {
PwGroupV4 group = this;
while (group != null) {

View File

@@ -1,6 +1,8 @@
package com.keepassdroid.database;
public abstract class PwIcon {
import java.io.Serializable;
public abstract class PwIcon implements Serializable {
public boolean isMetaStreamIcon() {
return false;

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.database;
import java.io.Serializable;
import java.util.Date;
/**
* Abstract class who manage Groups and Entries
*/
public abstract class PwNode implements Serializable {
/**
* Type of available Nodes
*/
public enum Type {
GROUP, ENTRY
}
/**
* @return Type of Node
*/
public abstract Type getType();
/**
* @return Title to display as view
*/
public abstract String getDisplayTitle();
/**
* @return Visual icon
*/
public abstract PwIcon getIcon();
/**
* @return Creation date and time of the node
*/
public abstract Date getCreationTime();
/**
* Retrieve the parent node
* @return PwGroup parent as group
*/
public abstract PwGroup getParent();
/**
* Assign a parent to this node
*/
public abstract void setParent(PwGroup parent);
/**
* If the content (type, title, icon) is visually the same
* @param o Node to compare
* @return True if visually as o
*/
public boolean isContentVisuallyTheSame(PwNode o) {
return getType().equals(o.getType())
&& getDisplayTitle().equals(o.getDisplayTitle())
&& getIcon().equals(o.getIcon());
}
/**
* Define if it's the same type of another node
* @param otherNode The other node to test
* @return true if both have the same type
*/
boolean isSameType(PwNode otherNode) {
return getType() != null ? getType().equals(otherNode.getType()) : otherNode.getType() == null;
}
}

View File

@@ -0,0 +1,5 @@
package com.keepassdroid.database;
public enum PwVersion {
V3, V4
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.database;
import java.util.Comparator;
public enum SortNodeEnum {
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
public Comparator<PwNode> getNodeComparator(boolean ascending, boolean groupsBefore) {
switch (this) {
case DB:
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
default:
case TITLE:
return new NodeTitleComparator(ascending, groupsBefore);
case USERNAME:
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
case CREATION_TIME:
return new NodeCreationComparator(ascending, groupsBefore);
case LAST_MODIFY_TIME:
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
case LAST_ACCESS_TIME:
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
}
}
private static abstract class NodeComparator implements Comparator<PwNode> {
boolean ascending;
boolean groupsBefore;
NodeComparator() {
this(true, true);
}
NodeComparator(boolean groupsBefore) {
this(true, groupsBefore);
}
NodeComparator(boolean ascending, boolean groupsBefore) {
this.ascending = ascending;
this.groupsBefore = groupsBefore;
}
}
/**
* Comparator of Node by Title, Groups first, Entries second
*/
public static class NodeTitleComparator extends NodeComparator {
public NodeTitleComparator() {
super();
}
public NodeTitleComparator(boolean groupsBefore) {
super(groupsBefore);
}
public NodeTitleComparator(boolean ascending, boolean groupsBefore) {
super(ascending, groupsBefore);
}
public int compare(PwNode object1, PwNode object2) {
if (object1.equals(object2))
return 0;
if (object1 instanceof PwGroup) {
if (object2 instanceof PwGroup) {
return new PwGroup.GroupNameComparator(ascending)
.compare((PwGroup) object1, (PwGroup) object2);
} else if (object2 instanceof PwEntry) {
if(groupsBefore)
return -1;
else
return 1;
} else {
return -1;
}
} else if (object1 instanceof PwEntry) {
if(object2 instanceof PwEntry) {
return new PwEntry.EntryNameComparator(ascending)
.compare((PwEntry) object1, (PwEntry) object2);
} else if (object2 instanceof PwGroup) {
if(groupsBefore)
return 1;
else
return -1;
} else {
return -1;
}
}
int nodeNameComp = object1.getDisplayTitle()
.compareToIgnoreCase(object2.getDisplayTitle());
// If same name, can be different
if (nodeNameComp == 0)
return object1.hashCode() - object2.hashCode();
return nodeNameComp;
}
}
/**
* Comparator of node by creation, Groups first, Entries second
*/
public static class NodeCreationComparator extends NodeComparator {
public NodeCreationComparator() {
super();
}
public NodeCreationComparator(boolean groupsBefore) {
super(groupsBefore);
}
public NodeCreationComparator(boolean ascending, boolean groupsBefore) {
super(ascending, groupsBefore);
}
@Override
public int compare(PwNode object1, PwNode object2) {
if (object1.equals(object2))
return 0;
if (object1 instanceof PwGroup) {
if (object2 instanceof PwGroup) {
return new PwGroup.GroupCreationComparator(ascending)
.compare((PwGroup) object1, (PwGroup) object2);
} else if (object2 instanceof PwEntry) {
if(groupsBefore)
return -1;
else
return 1;
} else {
return -1;
}
} else if (object1 instanceof PwEntry) {
if(object2 instanceof PwEntry) {
return new PwEntry.EntryCreationComparator(ascending)
.compare((PwEntry) object1, (PwEntry) object2);
} else if (object2 instanceof PwGroup) {
if(groupsBefore)
return 1;
else
return -1;
} else {
return -1;
}
}
int nodeCreationComp = object1.getCreationTime()
.compareTo(object2.getCreationTime());
// If same creation, can be different
if (nodeCreationComp == 0) {
return object1.hashCode() - object2.hashCode();
}
return nodeCreationComp;
}
}
}

View File

@@ -21,21 +21,16 @@ package com.keepassdroid.database.edit;
import android.content.Context;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
public class AddEntry extends RunnableOnFinish {
protected Database mDb;
private PwEntry mEntry;
private Context ctx;
public static AddEntry getInstance(Context ctx, Database db, PwEntry entry, OnFinish finish) {
return new AddEntry(ctx, db, entry, finish);
}
protected AddEntry(Context ctx, Database db, PwEntry entry, OnFinish finish) {
public AddEntry(Context ctx, Database db, PwEntry entry, OnFinish finish) {
super(finish);
mDb = db;
@@ -56,27 +51,18 @@ public class AddEntry extends RunnableOnFinish {
private class AfterAdd extends OnFinish {
public AfterAdd(OnFinish finish) {
AfterAdd(OnFinish finish) {
super(finish);
}
@Override
public void run() {
PwDatabase pm = mDb.pm;
if ( mSuccess ) {
PwGroup parent = mEntry.getParent();
// Mark parent tree dirty
mDb.dirty.add(parent);
} else {
if ( !mSuccess ) {
pm.removeEntryFrom(mEntry, mEntry.getParent());
}
// TODO if add entry callback
super.run();
}
}
}

View File

@@ -21,7 +21,7 @@ package com.keepassdroid.database.edit;
import android.content.Context;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwGroup;
@@ -32,24 +32,20 @@ public class AddGroup extends RunnableOnFinish {
private PwGroup mGroup;
private PwGroup mParent;
private Context ctx;
protected boolean mDontSave;
private boolean mDontSave;
public static AddGroup getInstance(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, boolean dontSave) {
return new AddGroup(ctx, db, name, iconid, parent, finish, dontSave);
}
private AddGroup(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, boolean dontSave) {
super(finish);
public AddGroup(Context ctx, Database db, String name, int iconid,
PwGroup parent, AfterAddNodeOnFinish afterAddNode,
boolean dontSave) {
super(afterAddNode);
mDb = db;
mName = name;
mIconID = iconid;
mParent = parent;
mDontSave = dontSave;
this.ctx = ctx;
mFinish = new AfterAdd(mFinish);
}
@@ -62,9 +58,7 @@ public class AddGroup extends RunnableOnFinish {
mGroup.initNewGroup(mName, pm.newGroupId());
mGroup.icon = mDb.pm.iconFactory.getIcon(mIconID);
pm.addGroupTo(mGroup, mParent);
//mParent.sortGroupsByName();
// Commit to disk
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
save.run();
@@ -72,24 +66,23 @@ public class AddGroup extends RunnableOnFinish {
private class AfterAdd extends OnFinish {
public AfterAdd(OnFinish finish) {
AfterAdd(OnFinish finish) {
super(finish);
}
@Override
public void run() {
PwDatabase pm = mDb.pm;
if ( mSuccess ) {
// Mark parent group dirty
mDb.dirty.add(mParent);
} else {
pm.removeGroupFrom(mGroup, mParent);
if ( !mSuccess ) {
pm.removeGroupFrom(mGroup, mParent);
}
super.run();
// TODO Better callback
AfterAddNodeOnFinish afterAddNode =
(AfterAddNodeOnFinish) super.mOnFinish;
afterAddNode.mSuccess = mSuccess;
afterAddNode.mMessage = mMessage;
afterAddNode.run(mGroup);
}
}
}

View File

@@ -0,0 +1,13 @@
package com.keepassdroid.database.edit;
import android.os.Handler;
import com.keepassdroid.database.PwNode;
public abstract class AfterAddNodeOnFinish extends OnFinish {
public AfterAddNodeOnFinish(Handler handler) {
super(handler);
}
public abstract void run(PwNode pwNode);
}

View File

@@ -19,20 +19,14 @@
*/
package com.keepassdroid.database.edit;
import android.content.Context;
import android.net.Uri;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.app.App;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwEncryptionAlgorithm;
import com.keepassdroid.utils.UriUtil;
public class CreateDB extends RunnableOnFinish {
private final int DEFAULT_ENCRYPTION_ROUNDS = 300;
private String mFilename;
private boolean mDontSave;
@@ -57,7 +51,6 @@ public class CreateDB extends RunnableOnFinish {
// Set Database state
db.pm = pm;
Uri.Builder b = new Uri.Builder();
db.mUri = UriUtil.parseDefaultFile(mFilename);
db.setLoaded();
App.clearShutdown();

View File

@@ -21,7 +21,7 @@ package com.keepassdroid.database.edit;
import android.content.Context;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
@@ -71,8 +71,6 @@ public class DeleteEntry extends RunnableOnFinish {
// Commit database
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
save.run();
}
private class AfterDelete extends OnFinish {
@@ -81,7 +79,7 @@ public class DeleteEntry extends RunnableOnFinish {
private PwEntry mEntry;
private boolean recycled;
public AfterDelete(OnFinish finish, PwGroup parent, PwEntry entry, boolean r) {
AfterDelete(OnFinish finish, PwGroup parent, PwEntry entry, boolean r) {
super(finish);
mParent = parent;
@@ -92,18 +90,7 @@ public class DeleteEntry extends RunnableOnFinish {
@Override
public void run() {
PwDatabase pm = mDb.pm;
if ( mSuccess ) {
// Mark parent dirty
if ( mParent != null ) {
mDb.dirty.add(mParent);
}
if (recycled) {
PwGroup recycleBin = pm.getRecycleBin();
mDb.dirty.add(recycleBin);
mDb.dirty.add(mDb.pm.rootGroup);
}
} else {
if ( !mSuccess ) {
if (recycled) {
pm.undoRecycle(mEntry, mParent);
}
@@ -111,11 +98,9 @@ public class DeleteEntry extends RunnableOnFinish {
pm.undoDeleteEntry(mEntry, mParent);
}
}
// TODO Callback after delete entry
super.run();
}
}
}

View File

@@ -19,109 +19,115 @@
*/
package com.keepassdroid.database.edit;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import com.keepassdroid.Database;
import com.keepassdroid.GroupBaseActivity;
import com.keepassdroid.app.App;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
import java.util.ArrayList;
import java.util.List;
public class DeleteGroup extends RunnableOnFinish {
private Context mContext;
private Database mDb;
private PwGroup mGroup;
private GroupBaseActivity mAct;
private boolean mDontSave;
public DeleteGroup(Database db, PwGroup group, GroupBaseActivity act, OnFinish finish) {
public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish) {
super(finish);
setMembers(db, group, act, false);
}
public DeleteGroup(Database db, PwGroup group, GroupBaseActivity act, OnFinish finish, boolean dontSave) {
super(finish);
setMembers(db, group, act, dontSave);
setMembers(ctx, db, group, false);
}
public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish, boolean dontSave) {
super(finish);
setMembers(ctx, db, group, dontSave);
}
public DeleteGroup(Database db, PwGroup group, OnFinish finish, boolean dontSave) {
super(finish);
setMembers(db, group, null, dontSave);
setMembers(null, db, group, dontSave);
}
private void setMembers(Database db, PwGroup group, GroupBaseActivity act, boolean dontSave) {
private void setMembers(Context ctx, Database db, PwGroup group, boolean dontSave) {
mDb = db;
mGroup = group;
mAct = act;
mContext = ctx;
mDontSave = dontSave;
mFinish = new AfterDelete(mFinish);
}
@Override
public void run() {
// Remove child entries
List<PwEntry> childEnt = new ArrayList<PwEntry>(mGroup.childEntries);
for ( int i = 0; i < childEnt.size(); i++ ) {
DeleteEntry task = new DeleteEntry(mAct, mDb, childEnt.get(i), null, true);
task.run();
}
// Remove child groups
List<PwGroup> childGrp = new ArrayList<PwGroup>(mGroup.childGroups);
for ( int i = 0; i < childGrp.size(); i++ ) {
DeleteGroup task = new DeleteGroup(mDb, childGrp.get(i), mAct, null, true);
task.run();
}
// Remove from parent
PwGroup parent = mGroup.getParent();
if ( parent != null ) {
parent.childGroups.remove(mGroup);
}
// Remove from PwDatabaseV3
mDb.pm.getGroups().remove(mGroup);
// Save
SaveDB save = new SaveDB(mAct, mDb, mFinish, mDontSave);
save.run();
PwDatabase pm = mDb.pm;
PwGroup parent = mGroup.getParent();
// Remove Group from parent
boolean recycle = pm.canRecycle(mGroup);
if (recycle) {
pm.recycle(mGroup);
}
else {
// TODO tests
// Remove child entries
List<PwEntry> childEnt = new ArrayList<>(mGroup.childEntries);
for ( int i = 0; i < childEnt.size(); i++ ) {
DeleteEntry task = new DeleteEntry(mContext, mDb, childEnt.get(i), null, true);
task.run();
}
// Remove child groups
List<PwGroup> childGrp = new ArrayList<>(mGroup.childGroups);
for ( int i = 0; i < childGrp.size(); i++ ) {
DeleteGroup task = new DeleteGroup(mContext, mDb, childGrp.get(i), null, true);
task.run();
}
pm.deleteGroup(mGroup);
// Remove from PwDatabaseV3
// TODO ENcapsulate
mDb.pm.getGroups().remove(mGroup);
}
// Save
mFinish = new AfterDelete(mFinish, parent, mGroup, recycle);
// Commit Database
SaveDB save = new SaveDB(mContext, mDb, mFinish, mDontSave);
save.run();
}
private class AfterDelete extends OnFinish {
public AfterDelete(OnFinish finish) {
private PwGroup mParent;
private PwGroup mGroup;
private boolean recycled;
AfterDelete(OnFinish finish, PwGroup parent, PwGroup mGroup, boolean recycle) {
super(finish);
this.mParent = parent;
this.mGroup = mGroup;
this.recycled = recycle;
}
public void run() {
if ( mSuccess ) {
// Remove from tree global
mDb.pm.groups.remove(mGroup.getId());
// Remove tree from the dirty global (if it is present), not a big deal if this fails
mDb.dirty.remove(mGroup);
// Mark parent dirty
PwGroup parent = mGroup.getParent();
if ( parent != null ) {
mDb.dirty.add(parent);
}
mDb.dirty.add(mDb.pm.rootGroup);
} else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
App.setShutdown();
}
PwDatabase pm = mDb.pm;
if ( !mSuccess ) {
if (recycled) {
pm.undoRecycle(mGroup, mParent);
}
else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
App.setShutdown();
// TODO TEST pm.undoDeleteGroup(mGroup, mParent);
}
}
// TODO Callback after delete group
super.run();
}
}
}

View File

@@ -25,7 +25,7 @@ import android.net.Uri;
import android.preference.PreferenceManager;
import com.kunzisoft.keepass.R;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.app.App;
import com.keepassdroid.database.exception.ArcFourException;
import com.keepassdroid.database.exception.ContentFileNotFoundException;
@@ -78,7 +78,7 @@ public class LoadDB extends RunnableOnFinish {
finish(false, mCtx.getString(R.string.file_not_found_content));
return;
} catch (FileNotFoundException e) {
finish(false, mCtx.getString(R.string.FileNotFound));
finish(false, mCtx.getString(R.string.file_not_found));
return;
} catch (IOException e) {
finish(false, e.getMessage());
@@ -104,6 +104,9 @@ public class LoadDB extends RunnableOnFinish {
} catch (OutOfMemoryError e) {
finish(false, mCtx.getString(R.string.error_out_of_memory));
return;
} catch (Exception e) {
finish(false, e.getMessage());
return;
}
finish(true);

View File

@@ -19,7 +19,7 @@
*/
package com.keepassdroid.database.edit;
import com.keepassdroid.UpdateStatus;
import com.keepassdroid.tasks.UpdateStatus;
public abstract class RunnableOnFinish implements Runnable {

View File

@@ -23,7 +23,7 @@ import android.content.Context;
import java.io.IOException;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.exception.PwDbOutputException;
public class SaveDB extends RunnableOnFinish {

View File

@@ -19,19 +19,19 @@
*/
package com.keepassdroid.database.edit;
import java.io.IOException;
import java.io.InputStream;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.exception.InvalidKeyFileException;
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
import com.keepassdroid.utils.UriUtil;
import java.io.IOException;
import java.io.InputStream;
public class SetPassword extends RunnableOnFinish {
private String mPassword;

View File

@@ -21,9 +21,8 @@ package com.keepassdroid.database.edit;
import android.content.Context;
import com.keepassdroid.Database;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
public class UpdateEntry extends RunnableOnFinish {
private Database mDb;
@@ -61,36 +60,19 @@ public class UpdateEntry extends RunnableOnFinish {
private class AfterUpdate extends OnFinish {
private PwEntry mBackup;
public AfterUpdate(PwEntry backup, OnFinish finish) {
AfterUpdate(PwEntry backup, OnFinish finish) {
super(finish);
mBackup = backup;
}
@Override
public void run() {
if ( mSuccess ) {
// Mark group dirty if title or icon changes
if ( ! mBackup.getTitle().equals(mNewE.getTitle()) || ! mBackup.getIcon().equals(mNewE.getIcon()) ) {
PwGroup parent = mBackup.getParent();
if ( parent != null ) {
// Resort entries
parent.sortEntriesByName();
// Mark parent group dirty
mDb.dirty.add(parent);
}
}
} else {
if ( !mSuccess ) {
// If we fail to save, back out changes to global structure
mOldE.assign(mBackup);
}
// TODO Callback for update entry
super.run();
}
}
}

View File

@@ -35,14 +35,14 @@ public class EntrySearchStringIteratorV4 extends EntrySearchStringIterator {
public EntrySearchStringIteratorV4(PwEntryV4 entry) {
this.sp = SearchParametersV4.DEFAULT;
setIterator = entry.strings.entrySet().iterator();
setIterator = entry.getFields().entrySet().iterator();
advance();
}
public EntrySearchStringIteratorV4(PwEntryV4 entry, SearchParametersV4 sp) {
this.sp = sp;
setIterator = entry.strings.entrySet().iterator();
setIterator = entry.getFields().entrySet().iterator();
advance();
}

View File

@@ -22,7 +22,7 @@ package com.keepassdroid.database.load;
import java.io.IOException;
import java.io.InputStream;
import com.keepassdroid.UpdateStatus;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.exception.InvalidDBException;
@@ -33,7 +33,7 @@ public abstract class Importer {
public abstract PwDatabase openDatabase( InputStream inStream, String password, InputStream keyInputStream)
throws IOException, InvalidDBException;
public abstract PwDatabase openDatabase( InputStream inStream, String password, InputStream keyInputStream, UpdateStatus status )
public abstract PwDatabase openDatabase( InputStream inStream, String password, InputStream keyInputStream, UpdateStatus status, long roundsFix)
throws IOException, InvalidDBException;

View File

@@ -66,7 +66,7 @@ import javax.crypto.spec.SecretKeySpec;
import android.util.Log;
import com.kunzisoft.keepass.R;
import com.keepassdroid.UpdateStatus;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwDate;
@@ -125,10 +125,10 @@ public class ImporterV3 extends Importer {
public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream kfIs)
throws IOException, InvalidDBException
{
return openDatabase(inStream, password, kfIs, new UpdateStatus());
return openDatabase(inStream, password, kfIs, new UpdateStatus(), 0);
}
public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream kfIs, UpdateStatus status )
public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream kfIs, UpdateStatus status, long roundsFix)
throws IOException, InvalidDBException
{
PwDatabaseV3 newManager;

View File

@@ -22,7 +22,7 @@ package com.keepassdroid.database.load;
import java.io.IOException;
import java.io.InputStream;
import com.keepassdroid.UpdateStatus;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.database.PwDatabaseV3Debug;
import com.keepassdroid.database.exception.InvalidDBException;
@@ -35,9 +35,10 @@ public class ImporterV3Debug extends ImporterV3 {
@Override
public PwDatabaseV3Debug openDatabase(InputStream inStream, String password,
InputStream keyInputStream, UpdateStatus status) throws IOException,
InputStream keyInputStream, UpdateStatus status, long roundsFix) throws IOException,
InvalidDBException {
return (PwDatabaseV3Debug) super.openDatabase(inStream, password, keyInputStream, status);
return (PwDatabaseV3Debug) super.openDatabase(inStream, password, keyInputStream, status,
roundsFix);
}

View File

@@ -46,11 +46,10 @@ import org.xmlpull.v1.XmlPullParserFactory;
import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.UpdateStatus;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.crypto.PwStreamCipherFactory;
import com.keepassdroid.crypto.engine.CipherEngine;
import com.keepassdroid.database.BinaryPool;
import com.keepassdroid.database.ITimeLogger;
import com.keepassdroid.database.PwCompressionAlgorithm;
import com.keepassdroid.database.PwDatabaseV4;
@@ -78,11 +77,11 @@ public class ImporterV4 extends Importer {
private StreamCipher randomStream;
private PwDatabaseV4 db;
private BinaryPool binPool = new BinaryPool();
private byte[] hashOfHeader = null;
private byte[] pbHeader = null;
private long version;
private int binNum = 0;
Calendar utcCal;
public ImporterV4() {
@@ -98,18 +97,17 @@ public class ImporterV4 extends Importer {
public PwDatabaseV4 openDatabase(InputStream inStream, String password,
InputStream keyInputStream) throws IOException, InvalidDBException {
return openDatabase(inStream, password, keyInputStream, new UpdateStatus());
return openDatabase(inStream, password, keyInputStream, new UpdateStatus(), 0);
}
@Override
public PwDatabaseV4 openDatabase(InputStream inStream, String password,
InputStream keyInputStream, UpdateStatus status) throws IOException,
InvalidDBException {
public PwDatabaseV4 openDatabase(InputStream inStream, String password,
InputStream keyInputStream, UpdateStatus status, long roundsFix) throws IOException,
InvalidDBException {
db = createDB();
PwDbHeaderV4 header = new PwDbHeaderV4(db);
header.binaries.clear();
db.binPool.clear();
PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream);
version = header.version;
@@ -118,7 +116,7 @@ public class ImporterV4 extends Importer {
pbHeader = hh.header;
db.setMasterKey(password, keyInputStream);
db.makeFinalKey(header.masterSeed, db.kdfParameters);
db.makeFinalKey(header.masterSeed, db.kdfParameters, roundsFix);
CipherEngine engine;
Cipher cipher;
@@ -253,6 +251,7 @@ public class ImporterV4 extends Importer {
byte[] bin = new byte[data.length - 1];
System.arraycopy(data, 1, bin, 0, data.length-1);
ProtectedBinary pb = new ProtectedBinary(prot, bin);
db.binPool.poolAdd(pb);
if (prot) {
Arrays.fill(data, (byte)0);
@@ -511,7 +510,7 @@ public class ImporterV4 extends Importer {
if ( key != null ) {
ProtectedBinary pbData = ReadProtectedBinary(xpp);
int id = Integer.parseInt(key);
binPool.put(id, pbData);
db.binPool.put(id, pbData);
} else {
ReadUnknown(xpp);
}
@@ -863,7 +862,7 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.EntryTimes && name.equalsIgnoreCase(ElemTimes) ) {
return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryString && name.equalsIgnoreCase(ElemString) ) {
ctxEntry.strings.put(ctxStringName, ctxStringValue);
ctxEntry.addField(ctxStringName, ctxStringValue);
ctxStringName = null;
ctxStringValue = null;
@@ -922,7 +921,7 @@ public class ImporterV4 extends Importer {
byte[] buf = Base64Coder.decode(sDate);
if (buf.length != 8) {
byte[] buf8 = new byte[8];
System.arraycopy(buf, 0, buf8, 0, buf.length);
System.arraycopy(buf, 0, buf8, 0, Math.min(buf.length, 8));
buf = buf8;
}
@@ -932,7 +931,7 @@ public class ImporterV4 extends Importer {
} else {
try {
utcDate = PwDatabaseV4XML.dateFormat.parse(sDate);
utcDate = PwDatabaseV4XML.dateFormatter.get().parse(sDate);
} catch (ParseException e) {
// Catch with null test below
}
@@ -1061,7 +1060,7 @@ public class ImporterV4 extends Importer {
xpp.next(); // Consume end tag
int id = Integer.parseInt(ref);
return binPool.get(id);
return db.binPool.get(id);
}
boolean compressed = false;

View File

@@ -19,7 +19,7 @@
*/
package com.keepassdroid.database.load;
import com.keepassdroid.UpdateStatus;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.database.PwDatabaseV4Debug;
import com.keepassdroid.database.exception.InvalidDBException;
@@ -35,9 +35,10 @@ public class ImporterV4Debug extends ImporterV4 {
@Override
public PwDatabaseV4Debug openDatabase(InputStream inStream, String password,
InputStream keyInputFile, UpdateStatus status) throws IOException,
InputStream keyInputFile, UpdateStatus status, long roundsFix) throws IOException,
InvalidDBException {
return (PwDatabaseV4Debug) super.openDatabase(inStream, password, keyInputFile, status);
return (PwDatabaseV4Debug) super.openDatabase(inStream, password, keyInputFile, status,
roundsFix);
}
}

View File

@@ -52,7 +52,7 @@ public class PwDbInnerHeaderOutputV4 {
los.writeInt(streamKeySize);
los.write(header.innerRandomStreamKey);
for (ProtectedBinary bin : header.binaries) {
for (ProtectedBinary bin : db.binPool.binaries()) {
byte flag = KdbxBinaryFlags.None;
if (bin.isProtected()) {
flag |= KdbxBinaryFlags.Protected;

View File

@@ -260,7 +260,7 @@ public class PwDbV3Output extends PwDbOutput {
groupList.add(group);
// Recurse over children
for ( int i = 0; i < group.childGroups.size(); i++ ) {
for ( int i = 0; i < group.numbersOfChildGroups(); i++ ) {
sortGroup((PwGroupV3) group.childGroups.get(i), groupList);
}
}

View File

@@ -19,36 +19,13 @@
*/
package com.keepassdroid.database.save;
import static com.keepassdroid.database.PwDatabaseV4XML.*;
import java.io.IOException;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.UUID;
import java.util.zip.GZIPOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import org.joda.time.DateTime;
import org.spongycastle.crypto.StreamCipher;
import org.xmlpull.v1.XmlSerializer;
import android.util.Xml;
import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.crypto.PwStreamCipherFactory;
import com.keepassdroid.crypto.engine.CipherEngine;
import com.keepassdroid.crypto.keyDerivation.KdfEngine;
import com.keepassdroid.crypto.keyDerivation.KdfFactory;
import com.keepassdroid.database.BinaryPool;
import com.keepassdroid.database.CrsAlgorithm;
import com.keepassdroid.database.EntryHandler;
import com.keepassdroid.database.GroupHandler;
@@ -78,11 +55,115 @@ import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.MemUtil;
import com.keepassdroid.utils.Types;
import org.joda.time.DateTime;
import org.spongycastle.crypto.StreamCipher;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Stack;
import java.util.UUID;
import java.util.zip.GZIPOutputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import biz.source_code.base64Coder.Base64Coder;
import static com.keepassdroid.database.PwDatabaseV4XML.AttrCompressed;
import static com.keepassdroid.database.PwDatabaseV4XML.AttrId;
import static com.keepassdroid.database.PwDatabaseV4XML.AttrProtected;
import static com.keepassdroid.database.PwDatabaseV4XML.AttrRef;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemAutoType;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemAutoTypeDefaultSeq;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemAutoTypeEnabled;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemAutoTypeItem;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemAutoTypeObfuscation;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemBgColor;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemBinaries;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemBinary;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemCreationTime;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemCustomData;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemCustomIconID;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemCustomIconItem;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemCustomIconItemData;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemCustomIconItemID;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemCustomIcons;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbColor;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbDefaultUser;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbDefaultUserChanged;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbDesc;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbDescChanged;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbKeyChangeForce;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbKeyChangeRec;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbKeyChanged;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbMntncHistoryDays;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbName;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDbNameChanged;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDeletedObject;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDeletedObjects;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDeletionTime;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemDocNode;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemEnableAutoType;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemEnableSearching;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemEntry;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemEntryTemplatesGroup;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemEntryTemplatesGroupChanged;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemExpires;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemExpiryTime;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemFgColor;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemGenerator;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemGroup;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemHeaderHash;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemHistory;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemHistoryMaxItems;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemHistoryMaxSize;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemIcon;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemIsExpanded;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemKey;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemKeystrokeSequence;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemLastAccessTime;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemLastModTime;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemLastSelectedGroup;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemLastTopVisibleEntry;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemLastTopVisibleGroup;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemLocationChanged;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemMemoryProt;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemMeta;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemName;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemNotes;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemOverrideUrl;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemProtNotes;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemProtPassword;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemProtTitle;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemProtURL;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemProtUserName;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemRecycleBinChanged;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemRecycleBinEnabled;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemRecycleBinUuid;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemRoot;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemString;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemStringDictExItem;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemTags;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemTimes;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemUsageCount;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemUuid;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemValue;
import static com.keepassdroid.database.PwDatabaseV4XML.ElemWindow;
import static com.keepassdroid.database.PwDatabaseV4XML.ValFalse;
import static com.keepassdroid.database.PwDatabaseV4XML.ValTrue;
public class PwDbV4Output extends PwDbOutput {
PwDatabaseV4 mPM;
private StreamCipher randomStream;
private BinaryPool binPool;
private XmlSerializer xml;
private PwDbHeaderV4 header;
private byte[] hashOfHeader;
@@ -201,8 +282,7 @@ public class PwDbV4Output extends PwDbOutput {
}
private void outputDatabase(OutputStream os) throws IllegalArgumentException, IllegalStateException, IOException {
binPool = new BinaryPool((PwGroupV4)mPM.rootGroup);
xml = Xml.newSerializer();
xml.setOutput(os, "UTF-8");
@@ -395,7 +475,7 @@ public class PwDbV4Output extends PwDbOutput {
writeList(ElemTimes, entry);
writeList(entry.strings, true);
writeList(entry.getFields(), true);
writeList(entry.binaries);
writeList(ElemAutoType, entry.autoType);
@@ -420,7 +500,7 @@ public class PwDbV4Output extends PwDbOutput {
xml.startTag(null, ElemValue);
String strRef = null;
if (allowRef) {
int ref = binPool.poolFind(value);
int ref = mPM.binPool.poolFind(value);
strRef = Integer.toString(ref);
}
@@ -480,7 +560,7 @@ public class PwDbV4Output extends PwDbOutput {
private void writeObject(String name, Date value) throws IllegalArgumentException, IllegalStateException, IOException {
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
writeObject(name, PwDatabaseV4XML.dateFormat.format(value));
writeObject(name, PwDatabaseV4XML.dateFormatter.get().format(value));
} else {
DateTime dt = new DateTime(value);
long seconds = DateUtil.convertDateToKDBX4Time(dt);
@@ -720,7 +800,7 @@ public class PwDbV4Output extends PwDbOutput {
private void writeBinPool() throws IllegalArgumentException, IllegalStateException, IOException {
xml.startTag(null, ElemBinaries);
for (Entry<Integer, ProtectedBinary> pair : binPool.entrySet()) {
for (Entry<Integer, ProtectedBinary> pair : mPM.binPool.entrySet()) {
xml.startTag(null, ElemBinary);
xml.attribute(null, AttrId, Integer.toString(pair.getKey()));

View File

@@ -19,7 +19,9 @@
*/
package com.keepassdroid.database.security;
public class ProtectedString {
import java.io.Serializable;
public class ProtectedString implements Serializable {
private String string;
private boolean protect;

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.fileselect;
import android.os.AsyncTask;
class DeleteFileHistoryAsyncTask extends AsyncTask<FileSelectBean, Void, Void> {
private AfterDeleteFileHistoryListener afterDeleteFileHistoryListener;
private RecentFileHistory fileHistory;
private FileSelectAdapter adapter;
DeleteFileHistoryAsyncTask(AfterDeleteFileHistoryListener afterDeleteFileHistoryListener, RecentFileHistory fileHistory, FileSelectAdapter adapter) {
this.afterDeleteFileHistoryListener = afterDeleteFileHistoryListener;
this.fileHistory = fileHistory;
this.adapter = adapter;
}
protected Void doInBackground(FileSelectBean... args) {
fileHistory.deleteFile(args[0].getFileUri());
return null;
}
protected void onPostExecute(Void v) {
adapter.notifyDataSetChanged();
if (adapter.getItemCount() == 0) {
if(afterDeleteFileHistoryListener != null)
afterDeleteFileHistoryListener.afterDeleteFile();
}
}
public interface AfterDeleteFileHistoryListener {
void afterDeleteFile();
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.fileselect;
import android.app.Dialog;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import java.text.DateFormat;
public class FileInformationDialogFragment extends DialogFragment {
private static final String FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG";
public static FileInformationDialogFragment newInstance(FileSelectBean fileSelectBean) {
FileInformationDialogFragment fileInformationDialogFragment =
new FileInformationDialogFragment();
Bundle args = new Bundle();
args.putSerializable(FILE_SELECT_BEEN_ARG, fileSelectBean);
fileInformationDialogFragment.setArguments(args);
return fileInformationDialogFragment;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View root = inflater.inflate(R.layout.file_selection_information, null);
if (getArguments() != null && getArguments().containsKey(FILE_SELECT_BEEN_ARG)) {
FileSelectBean fileSelectBean = (FileSelectBean) getArguments().getSerializable(FILE_SELECT_BEEN_ARG);
TextView fileWarningView = (TextView) root.findViewById(R.id.file_warning);
if(fileSelectBean != null) {
TextView fileNameView = (TextView) root.findViewById(R.id.file_filename);
TextView filePathView = (TextView) root.findViewById(R.id.file_path);
TextView fileSizeView = (TextView) root.findViewById(R.id.file_size);
TextView fileModificationView = (TextView) root.findViewById(R.id.file_modification);
fileWarningView.setVisibility(View.GONE);
fileNameView.setText(fileSelectBean.getFileName());
filePathView.setText(Uri.decode(fileSelectBean.getFileUri().toString()));
fileSizeView.setText(String.valueOf(fileSelectBean.getSize()));
fileModificationView.setText(DateFormat.getDateTimeInstance()
.format(fileSelectBean.getLastModification()));
if(fileSelectBean.notFound())
showFileNotFound(fileWarningView);
} else
showFileNotFound(fileWarningView);
}
builder.setView(root);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
return builder.create();
}
private void showFileNotFound(TextView fileWarningView) {
fileWarningView.setVisibility(View.VISIBLE);
fileWarningView.setText(R.string.file_not_found);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2017 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 2 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.keepassdroid.fileselect;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.util.Log;
import com.keepassdroid.stylish.Stylish;
import com.kunzisoft.keepass.R;
import com.nononsenseapps.filepicker.FilePickerActivity;
public class FilePickerStylishActivity extends FilePickerActivity {
private @StyleRes
int themeId;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
this.themeId = FilePickerStylish.getThemeId(this);
setTheme(themeId);
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
if(FilePickerStylish.getThemeId(this) != this.themeId) {
Log.d(this.getClass().getName(), "Theme change detected, restarting activity");
this.recreate();
}
}
public static class FilePickerStylish extends Stylish {
public static @StyleRes int getThemeId(Context context) {
if (themeString.equals(context.getString(R.string.list_style_name_night)))
return R.style.KeepassDXStyle_FilePickerStyle_Night;
return R.style.KeepassDXStyle_FilePickerStyle_Light;
}
}
}

View File

@@ -19,117 +19,163 @@
*/
package com.keepassdroid.fileselect;
import android.Manifest;
import android.content.ActivityNotFoundException;
import android.content.ContentResolver;
import android.app.Activity;
import android.app.assist.AssistStructure;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.kunzisoft.keepass.R;
import com.keepassdroid.GroupActivity;
import com.keepassdroid.PasswordActivity;
import com.keepassdroid.ProgressTask;
import com.keepassdroid.SetPasswordDialog;
import com.keepassdroid.activities.GroupActivity;
import com.keepassdroid.app.App;
import com.keepassdroid.compat.ContentResolverCompat;
import com.keepassdroid.compat.StorageAF;
import com.keepassdroid.autofill.AutofillHelper;
import com.keepassdroid.database.edit.CreateDB;
import com.keepassdroid.database.edit.FileOnFinish;
import com.keepassdroid.database.exception.ContentFileNotFoundException;
import com.keepassdroid.intents.Intents;
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
import com.keepassdroid.fragments.CreateFileDialogFragment;
import com.keepassdroid.password.PasswordActivity;
import com.keepassdroid.stylish.StylishActivity;
import com.keepassdroid.tasks.ProgressTask;
import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.Interaction;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.UriUtil;
import com.keepassdroid.utils.Util;
import com.keepassdroid.view.AssignPasswordHelper;
import com.keepassdroid.view.FileNameView;
import com.keepassdroid.view.KeyFileHelper;
import com.kunzisoft.keepass.R;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URLDecoder;
public class FileSelectActivity extends StylishActivity {
import permissions.dispatcher.NeedsPermission;
import permissions.dispatcher.OnNeverAskAgain;
import permissions.dispatcher.OnPermissionDenied;
import permissions.dispatcher.OnShowRationale;
import permissions.dispatcher.PermissionRequest;
import permissions.dispatcher.RuntimePermissions;
private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 111;
private ListView mList;
private ListAdapter mAdapter;
@RuntimePermissions
public class FileSelectActivity extends StylishActivity implements
CreateFileDialogFragment.DefinePathDialogListener ,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
FileSelectAdapter.FileItemOpenListener,
FileSelectAdapter.FileSelectClearListener,
FileSelectAdapter.FileInformationShowListener {
private static final int CMENU_CLEAR = Menu.FIRST;
public static final int FILE_BROWSE = 1;
public static final int GET_CONTENT = 2;
public static final int OPEN_DOC = 3;
private static final String TAG = "FileSelectActivity";
private static final String EXTRA_STAY = "EXTRA_STAY";
private FileSelectAdapter mAdapter;
private View fileListTitle;
private RecentFileHistory fileHistory;
private boolean recentMode = false;
// TODO Consultation Mode
private boolean consultationMode = false;
private AutofillHelper autofillHelper;
private EditText openFileNameView;
private FileNameView fileNameView;
private AssignPasswordHelper assignPasswordHelper;
private Uri databaseUri;
private KeyFileHelper keyFileHelper;
public static void launch(Activity activity) {
Intent intent = new Intent(activity, FileSelectActivity.class);
// only to avoid visible flickering when redirecting
activity.startActivityForResult(intent, 0);
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity activity, AssistStructure assistStructure) {
if ( assistStructure != null ) {
Intent intent = new Intent(activity, FileSelectActivity.class);
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
} else {
launch(activity);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (AutofillHelper.isIntentContainsExtraAssistStructureKey(getIntent()))
consultationMode = true;
}
fileHistory = App.getFileHistory();
if (fileHistory.hasRecentFiles()) {
recentMode = true;
setContentView(R.layout.file_selection);
} else {
setContentView(R.layout.file_selection_no_recent);
}
setContentView(R.layout.file_selection);
fileListTitle = findViewById(R.id.file_list_title);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar);
mList = (ListView)findViewById(R.id.file_list);
openFileNameView = (EditText) findViewById(R.id.file_filename);
fileNameView = (FileNameView) findViewById(R.id.file_select);
mList.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id)
{
onListItemClick((ListView)parent, v, position, id);
}
}
);
// Set the initial value of the filename
String defaultPath = Environment.getExternalStorageDirectory().getAbsolutePath()
+ getString(R.string.database_file_path_default)
+ getString(R.string.database_file_name_default)
+ getString(R.string.database_file_extension_default);
openFileNameView.setText(defaultPath);
RecyclerView mListFiles = (RecyclerView) findViewById(R.id.file_list);
mListFiles.setLayoutManager(new LinearLayoutManager(this));
// To retrieve info for AutoFill
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
// Open button
Button openButton = (Button) findViewById(R.id.open);
View openButton = findViewById(R.id.open_database);
openButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String fileName = Util.getEditText(FileSelectActivity.this,
R.id.file_filename);
String fileName = openFileNameView.getText().toString();
try {
PasswordActivity.Launch(FileSelectActivity.this, fileName);
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.retrieveAssistStructure(getIntent());
if (assistStructure != null) {
PasswordActivity.launch(FileSelectActivity.this,
fileName,
assistStructure);
}
}
if (assistStructure == null) {
PasswordActivity.launch(FileSelectActivity.this, fileName);
}
}
catch (ContentFileNotFoundException e) {
Toast.makeText(FileSelectActivity.this,
@@ -137,177 +183,252 @@ public class FileSelectActivity extends StylishActivity {
}
catch (FileNotFoundException e) {
Toast.makeText(FileSelectActivity.this,
R.string.FileNotFound, Toast.LENGTH_LONG).show();
R.string.file_not_found, Toast.LENGTH_LONG).show();
}
}
});
// Create button
Button createButton = (Button) findViewById(R.id.create);
View createButton = findViewById(R.id.create_database);
createButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String filename = Util.getEditText(FileSelectActivity.this,
R.id.file_filename);
// Make sure file name exists
if (filename.length() == 0) {
Toast
.makeText(FileSelectActivity.this,
R.string.error_filename_required,
Toast.LENGTH_LONG).show();
return;
}
// Try to create the file
File file = new File(filename);
try {
if (file.exists()) {
Toast.makeText(FileSelectActivity.this,
R.string.error_database_exists,
Toast.LENGTH_LONG).show();
return;
}
File parent = file.getParentFile();
if ( parent == null || (parent.exists() && ! parent.isDirectory()) ) {
Toast.makeText(FileSelectActivity.this,
R.string.error_invalid_path,
Toast.LENGTH_LONG).show();
return;
}
if ( ! parent.exists() ) {
// Create parent dircetory
if ( ! parent.mkdirs() ) {
Toast.makeText(FileSelectActivity.this,
R.string.error_could_not_create_parent,
Toast.LENGTH_LONG).show();
return;
}
}
file.createNewFile();
} catch (IOException e) {
Toast.makeText(
FileSelectActivity.this,
getText(R.string.error_file_not_create) + " "
+ e.getLocalizedMessage(),
Toast.LENGTH_LONG).show();
return;
}
// Prep an object to collect a password once the database has
// been created
CollectPassword password = new CollectPassword(
new LaunchGroupActivity(filename));
// Create the new database
CreateDB create = new CreateDB(FileSelectActivity.this, filename, password, true);
ProgressTask createTask = new ProgressTask(
FileSelectActivity.this, create,
R.string.progress_create);
createTask.run();
FileSelectActivityPermissionsDispatcher
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this);
}
});
keyFileHelper = new KeyFileHelper(this);
View browseButton = findViewById(R.id.browse_button);
browseButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (StorageAF.useStorageFramework(FileSelectActivity.this)) {
Intent i = new Intent(StorageAF.ACTION_OPEN_DOCUMENT);
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
startActivityForResult(i, OPEN_DOC);
}
else {
Intent i;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
i = new Intent(Intent.ACTION_OPEN_DOCUMENT);
} else {
i = new Intent(Intent.ACTION_GET_CONTENT);
}
i.addCategory(Intent.CATEGORY_OPENABLE);
i.setType("*/*");
browseButton.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
new KeyFileHelper.ClickDataUriCallback() {
@Override
public Uri onRequestIntentFilePicker() {
return Uri.parse("file://" + openFileNameView.getText().toString());
}
}));
try {
startActivityForResult(i, GET_CONTENT);
} catch (ActivityNotFoundException e) {
lookForOpenIntentsFilePicker();
} catch (SecurityException e) {
lookForOpenIntentsFilePicker();
}
// Construct adapter with listeners
mAdapter = new FileSelectAdapter(FileSelectActivity.this, fileHistory.getDbList());
mAdapter.setOnItemClickListener(this);
mAdapter.setFileSelectClearListener(this);
mAdapter.setFileInformationShowListener(this);
mListFiles.setAdapter(mAdapter);
// Load default database if not an orientation change
if (! (savedInstanceState != null
&& savedInstanceState.containsKey(EXTRA_STAY)
&& savedInstanceState.getBoolean(EXTRA_STAY, false)) ) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "");
if (fileName.length() > 0) {
Uri dbUri = UriUtil.parseDefaultFile(fileName);
String scheme = null;
if (dbUri != null)
scheme = dbUri.getScheme();
if (!EmptyUtils.isNullOrEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
String path = dbUri.getPath();
File db = new File(path);
if (db.exists()) {
launchPasswordActivityWithPath(path);
}
} else {
if (dbUri != null)
launchPasswordActivityWithPath(dbUri.toString());
}
}
}
}
private void launchPasswordActivityWithPath(String path) {
try {
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
PasswordActivity.launch(FileSelectActivity.this,
path,
assistStructure);
}
}
private void lookForOpenIntentsFilePicker() {
if (Interaction.isIntentAvailable(FileSelectActivity.this, Intents.OPEN_INTENTS_FILE_BROWSE)) {
Intent i = new Intent(Intents.OPEN_INTENTS_FILE_BROWSE);
i.setData(Uri.parse("file://" + Util.getEditText(FileSelectActivity.this, R.id.file_filename)));
try {
startActivityForResult(i, FILE_BROWSE);
} catch (ActivityNotFoundException e) {
showBrowserDialog();
}
} else {
showBrowserDialog();
if (assistStructure == null) {
PasswordActivity.launch(FileSelectActivity.this, path);
}
// Delete flickering for kitkat <=
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
overridePendingTransition(0, 0);
} catch (Exception e) {
// Ignore exception
}
}
@Override
protected void onResume() {
super.onResume();
fileNameView.updateExternalStorageWarning();
updateTitleFileListView();
mAdapter.notifyDataSetChanged();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// only to keep the current activity
outState.putBoolean(EXTRA_STAY, true);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// NOTE: delegate the permission handling to generated method
FileSelectActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
public void openCreateFileDialogFragment() {
CreateFileDialogFragment createFileDialogFragment = new CreateFileDialogFragment();
createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment");
}
private void updateTitleFileListView() {
if(mAdapter.getItemCount() == 0)
fileListTitle.setVisibility(View.INVISIBLE);
else
fileListTitle.setVisibility(View.VISIBLE);
}
/**
* Create file for database
* @return If not created, return false
*/
private boolean createDatabaseFile(Uri path) {
String pathString = URLDecoder.decode(path.getPath());
// Make sure file name exists
if (pathString.length() == 0) {
Log.e(TAG, getString(R.string.error_filename_required));
Toast.makeText(FileSelectActivity.this,
R.string.error_filename_required,
Toast.LENGTH_LONG).show();
return false;
}
// Try to create the file
File file = new File(pathString);
try {
if (file.exists()) {
Log.e(TAG, getString(R.string.error_database_exists) + " " + file);
Toast.makeText(FileSelectActivity.this,
R.string.error_database_exists,
Toast.LENGTH_LONG).show();
return false;
}
File parent = file.getParentFile();
if ( parent == null || (parent.exists() && ! parent.isDirectory()) ) {
Log.e(TAG, getString(R.string.error_invalid_path) + " " + file);
Toast.makeText(FileSelectActivity.this,
R.string.error_invalid_path,
Toast.LENGTH_LONG).show();
return false;
}
if ( ! parent.exists() ) {
// Create parent directory
if ( ! parent.mkdirs() ) {
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + parent);
Toast.makeText(FileSelectActivity.this,
R.string.error_could_not_create_parent,
Toast.LENGTH_LONG).show();
return false;
}
}
private void showBrowserDialog() {
BrowserDialog diag = new BrowserDialog(FileSelectActivity.this);
diag.show();
}
});
fillData();
registerForContextMenu(mList);
// Load default database
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "");
if (fileName.length() > 0) {
Uri dbUri = UriUtil.parseDefaultFile(fileName);
String scheme = dbUri.getScheme();
if (!EmptyUtils.isNullOrEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
String path = dbUri.getPath();
File db = new File(path);
if (db.exists()) {
try {
PasswordActivity.Launch(FileSelectActivity.this, path);
} catch (Exception e) {
// Ignore exception
}
}
}
else {
try {
PasswordActivity.Launch(FileSelectActivity.this, dbUri.toString());
} catch (Exception e) {
// Ignore exception
}
}
return file.createNewFile();
} catch (IOException e) {
Log.e(TAG, getString(R.string.error_could_not_create_parent) + " " + e.getLocalizedMessage());
e.printStackTrace();
Toast.makeText(
FileSelectActivity.this,
getText(R.string.error_file_not_create) + " "
+ e.getLocalizedMessage(),
Toast.LENGTH_LONG).show();
return false;
}
}
@Override
public boolean onDefinePathDialogPositiveClick(Uri pathFile) {
databaseUri = pathFile;
if(createDatabaseFile(pathFile)) {
AssignMasterKeyDialogFragment assignMasterKeyDialogFragment = new AssignMasterKeyDialogFragment();
assignMasterKeyDialogFragment.show(getSupportFragmentManager(), "passwordDialog");
return true;
} else
return false;
}
@Override
public boolean onDefinePathDialogNegativeClick(Uri pathFile) {
return true;
}
@Override
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
String databaseFilename = databaseUri.getPath();
// Prep an object to collect a password once the database has
// been created
FileOnFinish launchActivityOnFinish = new FileOnFinish(
new LaunchGroupActivity(databaseFilename));
AssignPasswordOnFinish assignPasswordOnFinish =
new AssignPasswordOnFinish(launchActivityOnFinish);
// Create the new database
CreateDB create = new CreateDB(FileSelectActivity.this,
databaseFilename, assignPasswordOnFinish, true);
ProgressTask createTask = new ProgressTask(
FileSelectActivity.this, create,
R.string.progress_create);
createTask.run();
assignPasswordHelper =
new AssignPasswordHelper(this,
masterPassword, keyFile);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
private class AssignPasswordOnFinish extends FileOnFinish {
AssignPasswordOnFinish(FileOnFinish fileOnFinish) {
super(fileOnFinish);
}
@Override
public void run() {
if (mSuccess) {
assignPasswordHelper.assignPasswordInDatabase(mOnFinish);
}
}
}
private class LaunchGroupActivity extends FileOnFinish {
private Uri mUri;
public LaunchGroupActivity(String filename) {
LaunchGroupActivity(String filename) {
super(null);
mUri = UriUtil.parseDefaultFile(filename);
}
@@ -316,181 +437,114 @@ public class FileSelectActivity extends StylishActivity {
if (mSuccess) {
// Add to recent files
fileHistory.createFile(mUri, getFilename());
GroupActivity.Launch(FileSelectActivity.this);
mAdapter.notifyDataSetChanged();
updateTitleFileListView();
GroupActivity.launch(FileSelectActivity.this);
}
}
}
private class CollectPassword extends FileOnFinish {
public CollectPassword(FileOnFinish finish) {
super(finish);
}
@Override
public void run() {
SetPasswordDialog dialog = SetPasswordDialog.newInstance(mOnFinish);
dialog.show(getSupportFragmentManager(), "passwordDialog");
}
}
private void fillData() {
// Set the initial value of the filename
EditText filename = (EditText) findViewById(R.id.file_filename);
filename.setText(Environment.getExternalStorageDirectory().getAbsolutePath() + getString(R.string.default_file_path));
mAdapter = new ArrayAdapter<String>(this, R.layout.file_row, R.id.file_filename, fileHistory.getDbList());
mList.setAdapter(mAdapter);
}
protected void onListItemClick(ListView l, View v, int position, long id) {
new AsyncTask<Integer, Void, Void>() {
String fileName;
String keyFile;
protected Void doInBackground(Integer... args) {
int position = args[0];
fileName = fileHistory.getDatabaseAt(position);
keyFile = fileHistory.getKeyfileAt(position);
return null;
}
protected void onPostExecute(Void v) {
@Override
public void onFileItemOpenListener(int itemPosition) {
new OpenFileHistoryAsyncTask(new OpenFileHistoryAsyncTask.AfterOpenFileHistoryListener() {
@Override
public void afterOpenFile(String fileName, String keyFile) {
try {
PasswordActivity.Launch(FileSelectActivity.this, fileName, keyFile);
}
catch (ContentFileNotFoundException e) {
Toast.makeText(FileSelectActivity.this, R.string.file_not_found_content, Toast.LENGTH_LONG)
.show();
}
catch (FileNotFoundException e) {
Toast.makeText(FileSelectActivity.this, R.string.FileNotFound, Toast.LENGTH_LONG)
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
PasswordActivity.launch(FileSelectActivity.this,
fileName, keyFile, assistStructure);
}
}
if (assistStructure == null) {
PasswordActivity.launch(FileSelectActivity.this, fileName, keyFile);
}
} catch (ContentFileNotFoundException e) {
Toast.makeText(FileSelectActivity.this,
R.string.file_not_found_content, Toast.LENGTH_LONG)
.show();
} catch (FileNotFoundException e) {
Toast.makeText(FileSelectActivity.this,
R.string.file_not_found, Toast.LENGTH_LONG)
.show();
}
updateTitleFileListView();
}
}.execute(position);
}, fileHistory).execute(itemPosition);
}
@Override
public void onClickFileInformation(FileSelectBean fileSelectBean) {
if (fileSelectBean != null) {
FileInformationDialogFragment fileInformationDialogFragment =
FileInformationDialogFragment.newInstance(fileSelectBean);
fileInformationDialogFragment.show(getSupportFragmentManager(), "fileInformation");
}
}
@Override
public boolean onFileSelectClearListener(final FileSelectBean fileSelectBean) {
new DeleteFileHistoryAsyncTask(new DeleteFileHistoryAsyncTask.AfterDeleteFileHistoryListener() {
@Override
public void afterDeleteFile() {
fileHistory.deleteFile(fileSelectBean.getFileUri());
mAdapter.notifyDataSetChanged();
updateTitleFileListView();
}
}, fileHistory, mAdapter).execute(fileSelectBean);
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
fillData();
String filename = null;
if (requestCode == FILE_BROWSE && resultCode == RESULT_OK) {
filename = data.getDataString();
if (filename != null) {
if (filename.startsWith("file://")) {
filename = filename.substring(7);
}
filename = URLDecoder.decode(filename);
}
}
else if ((requestCode == GET_CONTENT || requestCode == OPEN_DOC) && resultCode == RESULT_OK) {
if (data != null) {
Uri uri = data.getData();
if (uri != null) {
if (StorageAF.useStorageFramework(this)) {
try {
// try to persist read and write permissions
ContentResolver resolver = getContentResolver();
ContentResolverCompat.takePersistableUriPermission(resolver, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
ContentResolverCompat.takePersistableUriPermission(resolver, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
} catch (Exception e) {
// nop
}
}
if (requestCode == GET_CONTENT) {
uri = UriUtil.translate(this, uri);
}
filename = uri.toString();
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
if (filename != null) {
EditText fn = (EditText) findViewById(R.id.file_filename);
fn.setText(filename);
}
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
new KeyFileHelper.KeyFileCallback() {
@Override
public void onKeyFileResultCallback(Uri uri) {
if (uri != null) {
String filename = uri.toString();
openFileNameView.setText(filename);
}
}
});
}
@Override
protected void onResume() {
super.onResume();
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void showRationaleForExternalStorage(final PermissionRequest request) {
new AlertDialog.Builder(this)
.setMessage(R.string.permission_external_storage_rationale_write_database)
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
request.cancel();
}
})
.show();
}
// check for storage permission
checkStoragePermission();
// Check to see if we need to change modes
if ( fileHistory.hasRecentFiles() != recentMode ) {
// Restart the activity
Intent intent = getIntent();
startActivity(intent);
finish();
}
FileNameView fnv = (FileNameView) findViewById(R.id.file_select);
fnv.updateExternalStorageWarning();
}
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void showDeniedForExternalStorage() {
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show();
}
private void checkStoragePermission() {
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(FileSelectActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
//if (ActivityCompat.shouldShowRequestPermissionRationale(FileSelectActivity.this,
// Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
//} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(FileSelectActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
//}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
void showNeverAskForExternalStorage() {
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
@@ -504,43 +558,4 @@ public class FileSelectActivity extends StylishActivity {
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
&& super.onOptionsItemSelected(item);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, CMENU_CLEAR, 0, R.string.remove_from_filelist);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
super.onContextItemSelected(item);
if ( item.getItemId() == CMENU_CLEAR ) {
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) item.getMenuInfo();
TextView tv = (TextView) acmi.targetView;
String filename = tv.getText().toString();
new AsyncTask<String, Void, Void>() {
protected java.lang.Void doInBackground(String... args) {
String filename = args[0];
fileHistory.deleteFile(Uri.parse(args[0]));
return null;
}
protected void onPostExecute(Void v) {
refreshList();
}
}.execute(filename);
return true;
}
return false;
}
private void refreshList() {
((BaseAdapter) mAdapter).notifyDataSetChanged();
}
}

View File

@@ -0,0 +1,181 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.fileselect;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.v7.widget.RecyclerView;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import com.keepassdroid.settings.PreferencesUtil;
import com.kunzisoft.keepass.R;
import java.util.List;
public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder> {
private static final int MENU_CLEAR = 1;
private Context context;
private LayoutInflater inflater;
private List<String> listFiles;
private FileItemOpenListener fileItemOpenListener;
private FileSelectClearListener fileSelectClearListener;
private FileInformationShowListener fileInformationShowListener;
private @ColorInt
int warningColor;
FileSelectAdapter(Context context, List<String> listFiles) {
this.inflater = LayoutInflater.from(context);
this.context = context;
this.listFiles = listFiles;
TypedValue typedValue = new TypedValue();
Resources.Theme theme = context.getTheme();
theme.resolveAttribute(R.attr.colorAccentCompat, typedValue, true);
warningColor = typedValue.data;
}
@Override
public FileSelectViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.file_row, parent, false);
return new FileSelectViewHolder(view);
}
@Override
public void onBindViewHolder(FileSelectViewHolder holder, int position) {
FileSelectBean fileSelectBean = new FileSelectBean(context, listFiles.get(position));
// Context menu creation
holder.fileContainer.setOnCreateContextMenuListener(new ContextMenuBuilder(fileSelectBean));
// Click item to open file
if (fileItemOpenListener != null)
holder.fileContainer.setOnClickListener(new FileItemClickListener(position));
// Assign file name
if (PreferencesUtil.isFullFilePathEnable(context))
holder.fileName.setText(Uri.decode(fileSelectBean.getFileUri().toString()));
else
holder.fileName.setText(fileSelectBean.getFileName());
holder.fileName.setTextSize(PreferencesUtil.getListTextSize(context));
// Set warning
if (fileSelectBean.notFound()) {
holder.fileInformation.setColorFilter(
warningColor,
android.graphics.PorterDuff.Mode.MULTIPLY);
}
// Click on information
if (fileInformationShowListener != null)
holder.fileInformation.setOnClickListener(new FileInformationClickListener(fileSelectBean));
}
@Override
public int getItemCount() {
return listFiles.size();
}
void setOnItemClickListener(FileItemOpenListener fileItemOpenListener) {
this.fileItemOpenListener = fileItemOpenListener;
}
void setFileSelectClearListener(FileSelectClearListener fileSelectClearListener) {
this.fileSelectClearListener = fileSelectClearListener;
}
void setFileInformationShowListener(FileInformationShowListener fileInformationShowListener) {
this.fileInformationShowListener = fileInformationShowListener;
}
public interface FileItemOpenListener {
void onFileItemOpenListener(int itemPosition);
}
public interface FileSelectClearListener {
boolean onFileSelectClearListener(FileSelectBean fileSelectBean);
}
public interface FileInformationShowListener {
void onClickFileInformation(FileSelectBean fileSelectBean);
}
private class FileItemClickListener implements View.OnClickListener {
private int position;
FileItemClickListener(int position) {
this.position = position;
}
@Override
public void onClick(View v) {
fileItemOpenListener.onFileItemOpenListener(position);
}
}
private class FileInformationClickListener implements View.OnClickListener {
private FileSelectBean fileSelectBean;
FileInformationClickListener(FileSelectBean fileSelectBean) {
this.fileSelectBean = fileSelectBean;
}
@Override
public void onClick(View view) {
fileInformationShowListener.onClickFileInformation(fileSelectBean);
}
}
private class ContextMenuBuilder implements View.OnCreateContextMenuListener {
private FileSelectBean fileSelectBean;
public ContextMenuBuilder(FileSelectBean fileSelectBean) {
this.fileSelectBean = fileSelectBean;
}
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
MenuItem clearMenu = contextMenu.add(Menu.NONE, MENU_CLEAR, Menu.NONE, R.string.remove_from_filelist);
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
}
private MenuItem.OnMenuItemClickListener mOnMyActionClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (fileSelectClearListener == null)
return false;
switch ( item.getItemId() ) {
case MENU_CLEAR:
return fileSelectClearListener.onFileSelectClearListener(fileSelectBean);
default:
return false;
}
}
};
}
}

View File

@@ -0,0 +1,89 @@
/*
* Copyright 2017 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 2 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.keepassdroid.fileselect;
import android.content.Context;
import android.net.Uri;
import android.support.v4.provider.DocumentFile;
import java.io.File;
import java.io.Serializable;
import java.util.Date;
public class FileSelectBean implements Serializable {
private static final String EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents";
private String fileName = "";
private Uri fileUri;
private Date lastModification = new Date();
private long size = 0;
public FileSelectBean(Context context, String pathFile) {
fileUri = Uri.parse(pathFile);
if (EXTERNAL_STORAGE_AUTHORITY.equals(fileUri.getAuthority())) {
DocumentFile file = DocumentFile.fromSingleUri(context, fileUri);
size = file.length();
fileName = file.getName();
lastModification = new Date(file.lastModified());
} else {
File file = new File(fileUri.getPath());
size = file.length();
fileName = file.getName();
lastModification = new Date(file.lastModified());
}
}
public boolean notFound() {
return getSize() == 0;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Uri getFileUri() {
return fileUri;
}
public void setFileUri(Uri fileUri) {
this.fileUri = fileUri;
}
public Date getLastModification() {
return lastModification;
}
public void setLastModification(Date lastModification) {
this.lastModification = lastModification;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
@@ -17,18 +17,25 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid.settings;
package com.keepassdroid.fileselect;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
public class PrefsUtil {
public static float getListTextSize(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return Float.parseFloat(prefs.getString(ctx.getString(R.string.list_size_key), ctx.getString(R.string.list_size_default)));
class FileSelectViewHolder extends RecyclerView.ViewHolder {
}
View fileContainer;
TextView fileName;
ImageView fileInformation;
FileSelectViewHolder(View itemView) {
super(itemView);
fileContainer = itemView.findViewById(R.id.file_container);
fileName = (TextView) itemView.findViewById(R.id.file_filename);
fileInformation = (ImageView) itemView.findViewById(R.id.file_information);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 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.keepassdroid.fileselect;
import android.os.AsyncTask;
class OpenFileHistoryAsyncTask extends AsyncTask<Integer, Void, Void> {
private AfterOpenFileHistoryListener afterOpenFileHistoryListener;
private RecentFileHistory fileHistory;
private String fileName;
private String keyFile;
OpenFileHistoryAsyncTask(AfterOpenFileHistoryListener afterOpenFileHistoryListener, RecentFileHistory fileHistory) {
this.afterOpenFileHistoryListener = afterOpenFileHistoryListener;
this.fileHistory = fileHistory;
}
protected Void doInBackground(Integer... args) {
int position = args[0];
fileName = fileHistory.getDatabaseAt(position);
keyFile = fileHistory.getKeyfileAt(position);
return null;
}
protected void onPostExecute(Void v) {
afterOpenFileHistoryListener.afterOpenFile(fileName, keyFile);
}
public interface AfterOpenFileHistoryListener {
void afterOpenFile(String fileName, String keyFile);
}
}

View File

@@ -19,14 +19,6 @@
*/
package com.keepassdroid.fileselect;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import com.kunzisoft.keepass.R;
import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.utils.UriUtil;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
@@ -34,6 +26,14 @@ import android.database.Cursor;
import android.net.Uri;
import android.preference.PreferenceManager;
import com.keepassdroid.compat.EditorCompat;
import com.keepassdroid.utils.UriUtil;
import com.kunzisoft.keepass.R;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class RecentFileHistory {
private static String DB_KEY = "recent_databases";

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2017 Brian 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 2 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.keepassdroid.fingerprint;
import android.content.Context;
import android.graphics.drawable.Animatable2;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.widget.ImageView;
import com.kunzisoft.keepass.R;
@RequiresApi(api = Build.VERSION_CODES.M)
public class FingerPrintAnimatedVector {
private AnimatedVectorDrawable scanFingerprint;
public FingerPrintAnimatedVector(Context context, ImageView imageView) {
scanFingerprint = (AnimatedVectorDrawable) context.getDrawable(R.drawable.scan_fingerprint);
imageView.setImageDrawable(scanFingerprint);
}
public void startScan() {
scanFingerprint.registerAnimationCallback(new Animatable2.AnimationCallback() {
public void onAnimationEnd(Drawable drawable) {
scanFingerprint.start();
}
});
scanFingerprint.start();
}
public void stopScan() {
scanFingerprint.stop();
}
}

View File

@@ -20,7 +20,6 @@
package com.keepassdroid.fingerprint;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.KeyguardManager;
import android.content.Context;
import android.os.Build;
@@ -32,18 +31,23 @@ import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
import android.support.v4.os.CancellationSignal;
import android.util.Base64;
import com.keepassdroid.compat.BuildCompat;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
@RequiresApi(api = Build.VERSION_CODES.M)
public class FingerPrintHelper {
private static final String ALIAS_KEY = "example-key";
private static final String FINGERPRINT_KEYSTORE_KEY = "example-key";
private FingerprintManagerCompat fingerprintManager;
private KeyStore keyStore = null;
@@ -74,7 +78,7 @@ public class FingerPrintHelper {
}
public void stopListening() {
if (!isFingerprintInitialized()) {
if (!isFingerprintInitialized(false)) {
return;
}
if (cancellationSignal != null) {
@@ -83,14 +87,6 @@ public class FingerPrintHelper {
}
}
public interface FingerPrintCallback {
void handleEncryptedResult(String value, String ivSpec);
void handleDecryptedResult(String value);
void onInvalidKeyException();
void onFingerprintException(Exception e);
}
@TargetApi(BuildCompat.VERSION_CODE_M)
public FingerPrintHelper(
final Context context,
final FingerPrintCallback fingerPrintCallback) {
@@ -118,25 +114,29 @@ public class FingerPrintHelper {
setInitOk(true);
} catch (final Exception e) {
setInitOk(false);
fingerPrintCallback.onFingerprintException(e);
fingerPrintCallback.onFingerPrintException(e);
}
}
}
public boolean isFingerprintSupported(FingerprintManagerCompat fingerprintManager) {
return Build.VERSION.SDK_INT >= BuildCompat.VERSION_CODE_M
public static boolean isFingerprintSupported(FingerprintManagerCompat fingerprintManager) {
return fingerprintManager != null
&& fingerprintManager.isHardwareDetected();
}
public boolean isFingerprintInitialized() {
return isFingerprintInitialized(true);
}
public boolean isFingerprintInitialized(boolean throwException) {
boolean isFingerprintInit = hasEnrolledFingerprints() && initOk;
if (!isFingerprintInit && fingerPrintCallback != null) {
fingerPrintCallback.onFingerprintException(new Exception("FingerPrint not initialized"));
if(throwException)
fingerPrintCallback.onFingerPrintException(new Exception("FingerPrint not initialized"));
}
return isFingerprintInit;
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void initEncryptData() {
if (!isFingerprintInitialized()) {
return;
@@ -144,16 +144,16 @@ public class FingerPrintHelper {
try {
createNewKeyIfNeeded(false); // no need to keep deleting existing keys
keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
stopListening();
startListening();
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
deleteEntryKey();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
fingerPrintCallback.onInvalidKeyException();
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final Exception e) {
fingerPrintCallback.onFingerprintException(e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -164,7 +164,7 @@ public class FingerPrintHelper {
try {
// actual do encryption here
byte[] encrypted = cipher.doFinal(value.getBytes());
final String encryptedValue = Base64.encodeToString(encrypted, 0 /* flags */);
final String encryptedValue = Base64.encodeToString(encrypted, Base64.DEFAULT);
// passes updated iv spec on to callback so this can be stored for decryption
final IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
@@ -172,11 +172,10 @@ public class FingerPrintHelper {
fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
} catch (final Exception e) {
fingerPrintCallback.onFingerprintException(e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
public void initDecryptData(final String ivSpecValue) {
if (!isFingerprintInitialized()) {
return;
@@ -184,20 +183,20 @@ public class FingerPrintHelper {
try {
createNewKeyIfNeeded(false);
keyStore.load(null);
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
// important to restore spec here that was used for decryption
final byte[] iv = Base64.decode(ivSpecValue, Base64.DEFAULT);
final IvParameterSpec spec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
stopListening();
startListening();
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
fingerPrintCallback.onInvalidKeyException();
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
deleteEntryKey();
} catch (final Exception e) {
fingerPrintCallback.onFingerprintException(e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -207,14 +206,16 @@ public class FingerPrintHelper {
}
try {
// actual decryption here
final byte[] encrypted = Base64.decode(encryptedValue, 0);
final byte[] encrypted = Base64.decode(encryptedValue, Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(encrypted);
final String decryptedString = new String(decrypted);
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
fingerPrintCallback.handleDecryptedResult(decryptedString);
} catch (final BadPaddingException badPaddingException) {
fingerPrintCallback.onInvalidKeyException(badPaddingException);
} catch (final Exception e) {
fingerPrintCallback.onFingerprintException(e);
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -226,18 +227,17 @@ public class FingerPrintHelper {
try {
keyStore.load(null);
if (allowDeleteExisting
&& keyStore.containsAlias(ALIAS_KEY)) {
keyStore.deleteEntry(ALIAS_KEY);
&& keyStore.containsAlias(FINGERPRINT_KEYSTORE_KEY)) {
keyStore.deleteEntry(FINGERPRINT_KEYSTORE_KEY);
}
// Create new key if needed
if (!keyStore.containsAlias(ALIAS_KEY)) {
if (!keyStore.containsAlias(FINGERPRINT_KEYSTORE_KEY)) {
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
keyGenerator.init(
new KeyGenParameterSpec.Builder(
ALIAS_KEY,
FINGERPRINT_KEYSTORE_KEY,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
@@ -249,7 +249,21 @@ public class FingerPrintHelper {
keyGenerator.generateKey();
}
} catch (final Exception e) {
fingerPrintCallback.onFingerprintException(e);
fingerPrintCallback.onFingerPrintException(e);
}
}
public void deleteEntryKey() {
try {
keyStore.load(null);
keyStore.deleteEntry(FINGERPRINT_KEYSTORE_KEY);
} catch (KeyStoreException
| CertificateException
| NoSuchAlgorithmException
| IOException
| NullPointerException e) {
if (fingerPrintCallback != null)
fingerPrintCallback.onFingerPrintException(e);
}
}
@@ -257,8 +271,6 @@ public class FingerPrintHelper {
public boolean hasEnrolledFingerprints() {
// fingerprint hardware supported and api level OK
return isFingerprintSupported(fingerprintManager)
&& fingerprintManager != null
&& fingerprintManager.isHardwareDetected()
// fingerprints enrolled
&& fingerprintManager.hasEnrolledFingerprints()
// and lockscreen configured
@@ -269,4 +281,44 @@ public class FingerPrintHelper {
this.initOk = initOk;
}
/**
* Remove entry key in keystore
*/
public static void deleteEntryKeyInKeystoreForFingerprints(final Context context,
final FingerPrintErrorCallback fingerPrintCallback) {
FingerPrintHelper fingerPrintHelper = new FingerPrintHelper(
context, new FingerPrintCallback() {
@Override
public void handleEncryptedResult(String value, String ivSpec) {}
@Override
public void handleDecryptedResult(String value) {}
@Override
public void onInvalidKeyException(Exception e) {
fingerPrintCallback.onInvalidKeyException(e);
}
@Override
public void onFingerPrintException(Exception e) {
fingerPrintCallback.onFingerPrintException(e);
}
});
fingerPrintHelper.deleteEntryKey();
}
public interface FingerPrintErrorCallback {
void onInvalidKeyException(Exception e);
void onFingerPrintException(Exception e);
}
public interface FingerPrintCallback extends FingerPrintErrorCallback {
void handleEncryptedResult(String value, String ivSpec);
void handleDecryptedResult(String value);
}
public enum Mode {
NOT_CONFIGURED_MODE, STORE_MODE, OPEN_MODE
}
}

View File

@@ -0,0 +1,274 @@
/*
* 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 2 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.keepassdroid.fragments;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import com.keepassdroid.utils.EmptyUtils;
import com.keepassdroid.utils.UriUtil;
import com.keepassdroid.view.KeyFileHelper;
import com.kunzisoft.keepass.R;
public class AssignMasterKeyDialogFragment extends DialogFragment {
private String masterPassword;
private Uri mKeyfile;
private View rootView;
private CompoundButton passwordCheckBox;
private TextView passView;
private TextView passConfView;
private CompoundButton keyfileCheckBox;
private TextView keyfileView;
private AssignPasswordDialogListener mListener;
private KeyFileHelper keyFileHelper;
public interface AssignPasswordDialogListener {
void onAssignKeyDialogPositiveClick(boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile);
void onAssignKeyDialogNegativeClick(boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile);
}
@Override
public void onAttach(Context activity) {
super.onAttach(activity);
try {
mListener = (AssignPasswordDialogListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement " + AssignPasswordDialogListener.class.getName());
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
rootView = inflater.inflate(R.layout.set_password, null);
builder.setView(rootView)
.setTitle(R.string.assign_master_key)
// Add action buttons
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
passwordCheckBox = (CompoundButton) rootView.findViewById(R.id.password_checkbox);
passView = (TextView) rootView.findViewById(R.id.pass_password);
passView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
passwordCheckBox.setChecked(true);
}
});
passConfView = (TextView) rootView.findViewById(R.id.pass_conf_password);
keyfileCheckBox = (CompoundButton) rootView.findViewById(R.id.keyfile_checkox);
keyfileView = (TextView) rootView.findViewById(R.id.pass_keyfile);
keyfileView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
keyfileCheckBox.setChecked(true);
}
});
keyFileHelper = new KeyFileHelper(this);
rootView.findViewById(R.id.browse_button)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
keyFileHelper.getOpenFileOnClickViewListener().onClick(view);
}
});
AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(final DialogInterface dialog) {
Button positiveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
masterPassword = "";
mKeyfile = null;
boolean error = verifyPassword() || verifyFile();
if (!passwordCheckBox.isChecked() && !keyfileCheckBox.isChecked()) {
error = true;
showNoKeyConfirmationDialog();
}
if (!error) {
mListener.onAssignKeyDialogPositiveClick(
passwordCheckBox.isChecked(), masterPassword,
keyfileCheckBox.isChecked(), mKeyfile);
dismiss();
}
}
});
Button negativeButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
mListener.onAssignKeyDialogNegativeClick(
passwordCheckBox.isChecked(), masterPassword,
keyfileCheckBox.isChecked(), mKeyfile);
dismiss();
}
});
}
});
return dialog;
}
private boolean verifyPassword() {
boolean error = false;
if (passwordCheckBox.isChecked()) {
masterPassword = passView.getText().toString();
String confpass = passConfView.getText().toString();
// Verify that passwords match
if (!masterPassword.equals(confpass)) {
error = true;
// Passwords do not match
Toast.makeText(getContext(), R.string.error_pass_match, Toast.LENGTH_LONG).show();
}
if (masterPassword == null || masterPassword.isEmpty()) {
error = true;
showEmptyPasswordConfirmationDialog();
}
}
return error;
}
private boolean verifyFile() {
boolean error = false;
if (keyfileCheckBox.isChecked()) {
Uri keyfile = UriUtil.parseDefaultFile(keyfileView.getText().toString());
mKeyfile = keyfile;
// Verify that a keyfile is set
if (EmptyUtils.isNullOrEmpty(keyfile)) {
error = true;
Toast.makeText(getContext(), R.string.error_nokeyfile, Toast.LENGTH_LONG).show();
}
}
return error;
}
private void showEmptyPasswordConfirmationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.warning_empty_password)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
if (!verifyFile()) {
mListener.onAssignKeyDialogPositiveClick(
passwordCheckBox.isChecked(), masterPassword,
keyfileCheckBox.isChecked(), mKeyfile);
AssignMasterKeyDialogFragment.this.dismiss();
}
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {}
});
builder.create().show();
}
private void showNoKeyConfirmationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.warning_no_encryption_key)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mListener.onAssignKeyDialogPositiveClick(
passwordCheckBox.isChecked(), masterPassword,
keyfileCheckBox.isChecked(), mKeyfile);
AssignMasterKeyDialogFragment.this.dismiss();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {}
});
builder.create().show();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
new KeyFileHelper.KeyFileCallback() {
@Override
public void onKeyFileResultCallback(Uri uri) {
if(uri != null) {
Uri pathString = UriUtil.parseDefaultFile(uri.toString());
if (pathString != null) {
keyfileCheckBox.setChecked(true);
keyfileView.setText(pathString.toString());
}
}
}
});
}
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2017 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 2 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.keepassdroid.fragments;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import com.keepassdroid.fileselect.FilePickerStylishActivity;
import com.keepassdroid.utils.UriUtil;
import com.kunzisoft.keepass.R;
import com.nononsenseapps.filepicker.FilePickerActivity;
import com.nononsenseapps.filepicker.Utils;
import java.io.File;
public class CreateFileDialogFragment extends DialogFragment implements AdapterView.OnItemSelectedListener{
private final int FILE_CODE = 3853;
private EditText folderPathView;
private EditText fileNameView;
private DefinePathDialogListener mListener;
private String extension;
private Uri uriPath;
public interface DefinePathDialogListener {
boolean onDefinePathDialogPositiveClick(Uri pathFile);
boolean onDefinePathDialogNegativeClick(Uri pathFile);
}
@Override
public void onAttach(Context activity) {
super.onAttach(activity);
try {
mListener = (DefinePathDialogListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement " + DefinePathDialogListener.class.getName());
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View rootView = inflater.inflate(R.layout.file_creation, null);
builder.setView(rootView)
.setTitle(R.string.create_keepass_file)
// Add action buttons
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {}
});
// Folder selection
View browseView = rootView.findViewById(R.id.browse_button);
folderPathView = (EditText) rootView.findViewById(R.id.folder_path);
fileNameView = (EditText) rootView.findViewById(R.id.filename);
String defaultPath = Environment.getExternalStorageDirectory().getPath()
+ getString(R.string.database_file_path_default);
folderPathView.setText(defaultPath);
browseView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(getContext(), FilePickerStylishActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
i.putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().getPath());
startActivityForResult(i, FILE_CODE);
}
});
// Init path
uriPath = null;
// Extension
extension = getString(R.string.database_file_extension_default);
Spinner spinner = (Spinner) rootView.findViewById(R.id.file_types);
spinner.setOnItemSelectedListener(this);
// Spinner Drop down elements
String[] fileTypes = getResources().getStringArray(R.array.file_types);
ArrayAdapter<String> dataAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, fileTypes);
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(dataAdapter);
AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(final DialogInterface dialog) {
Button positiveButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(mListener.onDefinePathDialogPositiveClick(buildPath()))
CreateFileDialogFragment.this.dismiss();
}
});
Button negativeButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(mListener.onDefinePathDialogNegativeClick(buildPath()))
CreateFileDialogFragment.this.dismiss();
}
});
}
});
return dialog;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FILE_CODE && resultCode == Activity.RESULT_OK) {
uriPath = data.getData();
if (uriPath != null) {
File file = Utils.getFileForUri(uriPath);
folderPathView.setText(file.getPath());
}
}
}
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
extension = adapterView.getItemAtPosition(position).toString();
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {
// Do nothing
}
private Uri buildPath() {
Uri path = new Uri.Builder().path(folderPathView.getText().toString())
.appendPath(fileNameView.getText().toString() + extension)
.build();
path = UriUtil.translate(getContext(), path);
return path;
}
}

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.keepassdroid;
package com.keepassdroid.fragments;
import android.app.Dialog;
import android.content.Context;
@@ -30,15 +30,18 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.Toast;
import com.kunzisoft.keepass.R;
import com.keepassdroid.password.PasswordGenerator;
import com.keepassdroid.settings.PreferencesUtil;
import com.kunzisoft.keepass.R;
public class GeneratePasswordFragment extends DialogFragment {
import java.util.Set;
public class GeneratePasswordDialogFragment extends DialogFragment {
public static final String KEY_PASSWORD_ID = "KEY_PASSWORD_ID";
@@ -46,6 +49,15 @@ public class GeneratePasswordFragment extends DialogFragment {
private View root;
private EditText lengthTextView;
private CompoundButton uppercaseBox;
private CompoundButton lowercaseBox;
private CompoundButton digitsBox;
private CompoundButton minusBox;
private CompoundButton underlineBox;
private CompoundButton spaceBox;
private CompoundButton specialsBox;
private CompoundButton bracketsBox;
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -66,6 +78,17 @@ public class GeneratePasswordFragment extends DialogFragment {
lengthTextView = (EditText) root.findViewById(R.id.length);
uppercaseBox = (CompoundButton) root.findViewById(R.id.cb_uppercase);
lowercaseBox = (CompoundButton) root.findViewById(R.id.cb_lowercase);
digitsBox = (CompoundButton) root.findViewById(R.id.cb_digits);
minusBox = (CompoundButton) root.findViewById(R.id.cb_minus);
underlineBox = (CompoundButton) root.findViewById(R.id.cb_underline);
spaceBox = (CompoundButton) root.findViewById(R.id.cb_space);
specialsBox = (CompoundButton) root.findViewById(R.id.cb_specials);
bracketsBox = (CompoundButton) root.findViewById(R.id.cb_brackets);
assignDefaultCharacters();
SeekBar seekBar = (SeekBar) root.findViewById(R.id.seekbar_length);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
@@ -79,6 +102,7 @@ public class GeneratePasswordFragment extends DialogFragment {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
seekBar.setProgress(PreferencesUtil.getDefaultPasswordLength(getContext().getApplicationContext()));
Button genPassButton = (Button) root.findViewById(R.id.generate_password_button);
genPassButton.setOnClickListener(new OnClickListener() {
@@ -113,6 +137,46 @@ public class GeneratePasswordFragment extends DialogFragment {
return builder.create();
}
private void assignDefaultCharacters() {
uppercaseBox.setChecked(false);
lowercaseBox.setChecked(false);
digitsBox.setChecked(false);
minusBox.setChecked(false);
underlineBox.setChecked(false);
spaceBox.setChecked(false);
specialsBox.setChecked(false);
bracketsBox.setChecked(false);
Set<String> defaultPasswordChars =
PreferencesUtil.getDefaultPasswordCharacters(getContext().getApplicationContext());
for(String passwordChar : defaultPasswordChars) {
if (passwordChar.equals(getString(R.string.value_password_uppercase))) {
uppercaseBox.setChecked(true);
}
else if (passwordChar.equals(getString(R.string.value_password_lowercase))) {
lowercaseBox.setChecked(true);
}
else if (passwordChar.equals(getString(R.string.value_password_digits))) {
digitsBox.setChecked(true);
}
else if (passwordChar.equals(getString(R.string.value_password_minus))) {
minusBox.setChecked(true);
}
else if (passwordChar.equals(getString(R.string.value_password_underline))) {
underlineBox.setChecked(true);
}
else if (passwordChar.equals(getString(R.string.value_password_space))) {
spaceBox.setChecked(true);
}
else if (passwordChar.equals(getString(R.string.value_password_special))) {
specialsBox.setChecked(true);
}
else if (passwordChar.equals(getString(R.string.value_password_brackets))) {
bracketsBox.setChecked(true);
}
}
}
private void fillPassword() {
EditText txtPassword = (EditText) root.findViewById(R.id.password);
@@ -121,23 +185,19 @@ public class GeneratePasswordFragment extends DialogFragment {
public String generatePassword() {
String password = "";
try {
int length = Integer.valueOf(((EditText) root.findViewById(R.id.length)).getText().toString());
((CheckBox) root.findViewById(R.id.cb_uppercase)).isChecked();
PasswordGenerator generator = new PasswordGenerator(getActivity());
password = generator.generatePassword(length,
((CheckBox) root.findViewById(R.id.cb_uppercase)).isChecked(),
((CheckBox) root.findViewById(R.id.cb_lowercase)).isChecked(),
((CheckBox) root.findViewById(R.id.cb_digits)).isChecked(),
((CheckBox) root.findViewById(R.id.cb_minus)).isChecked(),
((CheckBox) root.findViewById(R.id.cb_underline)).isChecked(),
((CheckBox) root.findViewById(R.id.cb_space)).isChecked(),
((CheckBox) root.findViewById(R.id.cb_specials)).isChecked(),
((CheckBox) root.findViewById(R.id.cb_brackets)).isChecked());
uppercaseBox.isChecked(),
lowercaseBox.isChecked(),
digitsBox.isChecked(),
minusBox.isChecked(),
underlineBox.isChecked(),
spaceBox.isChecked(),
specialsBox.isChecked(),
bracketsBox.isChecked());
} catch (NumberFormatException e) {
Toast.makeText(getContext(), R.string.error_wrong_length, Toast.LENGTH_LONG).show();
} catch (IllegalArgumentException e) {

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