Compare commits

...

296 Commits

Author SHA1 Message Date
J-Jamet
38c264e38d Merge branch 'release/2.5.0.0beta7' 2018-03-25 20:40:00 +02:00
J-Jamet
8da6b882fc Fix compilation 2018-03-25 20:23:54 +02:00
J-Jamet
6e03c2ebfc Upgrade changelogs 2018-03-25 20:10:53 +02:00
J-Jamet
a5cb5ed896 Temp Merge branch 'feature/RefactorModel' to correct serializable crash 2018-03-25 20:03:41 +02:00
J-Jamet
32bef7b099 Add constructors 2018-03-25 15:49:13 +02:00
J-Jamet
af0359132e Move comparators in SortNodeEnum 2018-03-25 15:29:50 +02:00
J-Jamet
85990879de Add serializable to binary to solve crash, refactor UUID and icon 2018-03-25 14:05:19 +02:00
J-Jamet
9436a4c3a5 Merge remote-tracking branch 'origin/develop' into develop 2018-03-24 20:06:45 +01:00
J-Jamet
efc786e318 Description for error API #49 2018-03-24 20:06:27 +01:00
J-Jamet
ed299b35e9 Change clones (To redefine) 2018-03-24 17:13:16 +01:00
J-Jamet
0defe9401f Solve password notification 2018-03-24 12:53:47 +01:00
J-Jamet
b66678f028 Refactor Groups and Entries methods 2018-03-24 00:02:08 +01:00
J-Jamet
b0cfd4292f Make attributes private in PwGroup and PwEntry 2018-03-23 21:30:50 +01:00
J-Jamet
8e390685cd Upgrade changelog and change extended visual 2018-03-23 19:14:58 +01:00
J-Jamet
9bcacdca4a Change extended visual 2018-03-23 19:04:19 +01:00
J-Jamet
c1969402f1 Add extended Ascii (ñæËÌÂÝÜ...) #36 2018-03-23 16:54:32 +01:00
J-Jamet
50bf22a4c7 Better fingerprint error management #38 #52 2018-03-23 13:48:51 +01:00
J-Jamet
cf93044d3f Start factorise 2018-03-22 23:06:05 +01:00
J-Jamet
33404add38 Verify nullity of group 2018-03-22 16:48:02 +01:00
J-Jamet
49ba74c38f Add Copy of password in settings 2018-03-22 15:48:46 +01:00
J-Jamet
3ed5db9819 Change search label in settings 2018-03-22 15:10:24 +01:00
J-Jamet
4535204d1e Move down file_browser settings 2018-03-22 15:03:44 +01:00
J-Jamet
18c656a555 Solve flickering and architecture for timeout #43 2018-03-22 14:36:23 +01:00
J-Jamet
42c28b5b95 Add timeout in launch 2018-03-22 13:04:45 +01:00
J-Jamet
9fc47fdf05 Solve timeout bug and remove compat class for invalidateOptionMenu 2018-03-21 23:17:40 +01:00
J-Jamet
9ef34599ce Solve bug of refresh entry 2018-03-21 22:25:35 +01:00
J-Jamet
bd3e5c6917 Solve color bugs 2018-03-21 22:14:57 +01:00
J-Jamet
7e9bed1e53 #55 Fix dispatch event 2018-03-21 21:59:55 +01:00
J-Jamet
ff2215929c Add menu_edit strings 2018-03-21 17:49:15 +01:00
J-Jamet
36d53e9788 Add file_browser strings 2018-03-21 17:32:35 +01:00
J-Jamet
6b9db7d40d Merge branch 'develop' of https://github.com/Kunzisoft/KeePassDX into develop 2018-03-21 17:02:00 +01:00
J-Jamet
30f805f5a9 #55 Fix listener to add node (dispatch event must to be refactored) 2018-03-21 16:13:40 +01:00
J-Jamet
64448ef218 #58 Horizontal scroll to see password and fix layout issues 2018-03-21 15:08:57 +01:00
J-Jamet
6e312e0420 #53 Fix password checkbox and add lambda 2018-03-21 14:36:42 +01:00
J-Jamet
1f7b86d34f #52 Add strike-through for 0 and check setting by default 2018-03-21 14:17:44 +01:00
J-Jamet
ae00fd5782 Allow notifications for all fields 2018-03-20 15:04:44 +01:00
J-Jamet
62f6f02467 Add notification to copy extra field, remove unused string 2018-03-20 14:47:49 +01:00
J-Jamet
55ee89314f Merge remote-tracking branch 'origin/develop' into develop 2018-03-20 13:43:28 +01:00
J-Jamet
b2f985aa03 New Notification engine 2018-03-20 13:43:11 +01:00
J-Jamet
b3a34c0138 Auto open select file #32 2018-03-19 18:17:29 +01:00
J-Jamet
1f06b09d38 Remove manual compat dependencies in settings 2018-03-19 16:49:26 +01:00
J-Jamet
758914a80a Solve bug of empty filename #51 2018-03-19 16:33:43 +01:00
J-Jamet
f2566abdcd Solve bug of icon information color 2018-03-19 16:20:38 +01:00
J-Jamet
cb6d479350 Default file as hint, change color 2018-03-19 16:09:33 +01:00
J-Jamet
846930a4f9 Refactor packages 2018-03-19 01:39:34 +01:00
J-Jamet
e788d89b18 Fix null in entry menu 2018-03-19 01:17:52 +01:00
J-Jamet
07f68cfe2f Add 30 minutes to timeout #43 2018-03-18 15:46:25 +01:00
J-Jamet
1c6ef51925 Launch notification only one time 2018-03-18 15:31:47 +01:00
J-Jamet
9995bc4d9f Fix App timeout for main cases #43 2018-03-18 15:31:22 +01:00
J-Jamet
accb931831 Upgrade clipboard and timeout 2018-03-18 13:26:13 +01:00
J-Jamet
42ac83c814 Upgrade version 2018-03-18 11:31:35 +01:00
J-Jamet
9ee9063c4d Refactor Timeout, delete useless Intents class 2018-03-17 20:18:06 +01:00
J-Jamet
8c38b361ea New notifications to copy fields 2018-03-17 20:01:13 +01:00
J-Jamet
cb919b2de5 Fix action "add group" for v3 #46 2018-03-17 11:07:33 +01:00
J-Jamet
679ea7c58f Fix Autofill settings crash #22 2018-03-16 19:15:39 +01:00
J-Jamet
21ebbd25f8 Update changelogs 2018-03-16 17:19:06 +01:00
J-Jamet
3c232ac5b6 Change HTTP to HTTPS 2018-03-16 16:36:23 +01:00
J-Jamet
d36d2408d7 Upgrade Notifications 2018-03-15 23:37:21 +01:00
J-Jamet
9fd59f850c Fix flickering and timeout toast in settings 2018-03-14 17:36:11 +01:00
J-Jamet
4cc7d1e74d Fix ActivityNotFound in FilePicker #37 2018-03-14 16:52:40 +01:00
J-Jamet
66f4353c3e Fix ActivityNotFound for AutoFill Service #37 2018-03-14 14:38:56 +01:00
J-Jamet
0acc83b066 Merge tag '2.5.0.0beta6' into develop
2.5.0.0beta6
2018-03-10 13:56:07 +01:00
J-Jamet
943d7ca6b9 Merge branch 'release/2.5.0.0beta6' 2018-03-10 13:55:52 +01:00
J-Jamet
65d1c7376b Upgrade app and changelogs 2018-03-10 13:49:53 +01:00
J-Jamet
871a624313 Fix assign key crash 2018-03-10 13:32:58 +01:00
J-Jamet
8cf515120f Fix crash for dismiss dialog 2018-03-10 13:22:37 +01:00
J-Jamet
30c5db92e6 Fix getMasterKey for V3 2018-03-10 13:15:42 +01:00
J-Jamet
19ebc40bdb French trad for font 2018-03-10 13:06:38 +01:00
J-Jamet
4db2e6baf9 Fix application timeout 2018-03-10 13:01:06 +01:00
J-Jamet
a9c1369cbf Capture exception when create password 2018-03-10 00:09:06 +01:00
J-Jamet
dd478d7cd4 Fix null pointer emulator bug for Node children 2018-03-09 23:29:20 +01:00
J-Jamet
53e7cc7f72 Merge branch 'master' into develop 2018-03-09 23:14:34 +01:00
J-Jamet
e5d3a0a931 Cut Changelogs for PlayStore 2018-03-09 23:13:53 +01:00
J-Jamet
23e6b12326 Merge tag 'fix_compil_translation' into develop
2.5.0.0beta5fix
2018-03-09 22:29:32 +01:00
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
5a54955941 Merge tag '2.5.0.0beta5' into develop
2.5.0.0beta5
2018-03-09 20:56:20 +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
J-Jamet
b8b106eb65 Remove unused libs and upgrade version 2017-11-18 16:28:29 +01:00
319 changed files with 15724 additions and 8892 deletions

15
.gitignore vendored
View File

@@ -6,6 +6,7 @@
# Built application files # Built application files
*.apk *.apk
*.ap_ *.ap_
app/free_google/*
# Files for the ART/Dalvik VM # Files for the ART/Dalvik VM
*.dex *.dex
@@ -39,7 +40,6 @@ captures/
# Intellij # Intellij
*.iml *.iml
<<<<<<< HEAD
.idea/workspace.xml .idea/workspace.xml
.idea/tasks.xml .idea/tasks.xml
.idea/gradle.xml .idea/gradle.xml
@@ -49,19 +49,16 @@ captures/
# Keystore files # Keystore files
# Uncomment the following line if you do not want to check your keystore files in. # Uncomment the following line if you do not want to check your keystore files in.
#*.jks #*.jks
=======
.idea/* .idea/*
# Keystore files # Keystore files
*.jks *.jks
>>>>>>> master
# External native build folder generated in Android Studio 2.2 and later # External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild .externalNativeBuild
# Google Services (e.g. APIs or Firebase) # Google Services (e.g. APIs or Firebase)
google-services.json google-services.json
<<<<<<< HEAD
# Freeline # Freeline
freeline.py freeline.py
@@ -70,5 +67,11 @@ freeline_project_description.json
# Iml Files # Iml Files
app/app.iml 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> </value>
</option> </option>
</component> </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" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -1,3 +1,59 @@
KeepassDX (2.5.0.0beta7)
* Rebuild Notifications
* Change links to https
* Add extended Ascii (ñæËÌÂÝÜ...)
* Upgrade custom visibility font
* Best fingerprint error management
* Add setting to prevent the password copy
* Fix bugs
KeepassDX (2.5.0.0beta6)
* Fix crash
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) KeepassDX (2.5.0.0beta1)
* Fork KeepassDroid * Fork KeepassDroid
* Add Material Design * Add Material Design
@@ -7,6 +63,37 @@ KeepassDX (2.5.0.0beta1)
* Update French translation * Update French translation
* Change donation (see KeepassDroid to contribute on both projects) * Change donation (see KeepassDroid to contribute on both projects)
KeePassDroid (2.2.1)
* Fix kdbx4 date corruption
* 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) KeePassDroid (2.2.0.2)
* Fix non fingerprint password layout * Fix non fingerprint password layout

View File

@@ -14,14 +14,14 @@ Tadashi Saito
vhschlenker vhschlenker
bumper314 - Samsung multiwindow support bumper314 - Samsung multiwindow support
Hans Cappelle - fingerprint sensor integration Hans Cappelle - fingerprint sensor integration
Jeremy Jamet - Material Design - Patches Jeremy Jamet - Keepass DX Material Design - Patches
Translations: Translations:
Diego Pierotto - Italian Diego Pierotto - Italian
Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French
Maciej Bieniek, cod3r - Polish Maciej Bieniek, cod3r - Polish
Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian
MaWi, rvs2008, meviox, MaDill - German MaWi, rvs2008, meviox, MaDill, EdlerProgrammierer, Jan Thomas - German
yslandro - Norwegian Nynorsk yslandro - Norwegian Nynorsk
王科峰 - Chinese 王科峰 - Chinese
Typhoon - Slovak Typhoon - Slovak
@@ -30,7 +30,7 @@ Matsuu Takuto - Japanese
Carlos Schlyter - Portugese (Brazil) Carlos Schlyter - Portugese (Brazil)
YSmhXQDd6Z - Portugese (Portugal) YSmhXQDd6Z - Portugese (Portugal)
andriykopanytsia - Ukranian andriykopanytsia - Ukranian
intel - Hungarian intel, Zoltán Antal - Hungarian
H Vanek - Czech H Vanek - Czech
jipanos - Spanish jipanos - Spanish
Erik Fdevriendt, Erik Jan Meijer - Dutch Erik Fdevriendt, Erik Jan Meijer - Dutch

View File

@@ -1,17 +1,19 @@
# Android Keepass DX # 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 ### Features
- Create database file / entries and groups * Create database files / entries and groups
- Open database, copy username / password, open URI / URL * Support for .kdb and .kdbx files (version 1 to 4)
- Fingerprint for fast unlocking * Open database, copy username / password, open URI / URL
- Material design with themes * Fingerprint for fast unlocking
- Device integration and AutoFill (In progress) * 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/fastlane/metadata/android/en-US/images/phoneScreenshots/screen1.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/screen2.jpg" width="220">
## What is KeePass? ## 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. 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/fastlane/metadata/android/en-US/images/phoneScreenshots/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/screen5.jpg" width="220">
### JNI ## Download
Native library build instructions: [<img src="https://f-droid.org/badge/get-it-on.png"
1. Make sure you have the latest MIPS Android NDK installed: alt="Get it on F-Droid"
https://developer.android.com/tools/sdk/ndk/index.html height="80">](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/)
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.
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 ## 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 You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. along with 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' apply plugin: 'com.android.application'
android { android {
compileSdkVersion = 25 compileSdkVersion = 27
buildToolsVersion = "26.0.2" buildToolsVersion = "27.0.3"
defaultConfig { defaultConfig {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 25 targetSdkVersion 27
versionCode = 1 versionCode = 7
versionName = "2.5.0.0beta1" versionName = "2.5.0.0beta7"
multiDexEnabled true
testApplicationId = "com.keepassdroid.tests" testApplicationId = "com.keepassdroid.tests"
testInstrumentationRunner = "android.test.InstrumentationTestRunner" testInstrumentationRunner = "android.test.InstrumentationTestRunner"
ndk {
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
'arm64-v8a', 'mips', 'mips64'
}
} }
externalNativeBuild { externalNativeBuild {
@@ -21,6 +27,7 @@ android {
} }
} }
buildTypes { buildTypes {
release { release {
minifyEnabled = false minifyEnabled = false
@@ -52,18 +59,34 @@ android {
buildConfigField "boolean", "GOOGLE_PLAY_VERSION", "true" 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 spongycastleVersion = "1.58.0.0"
def permissionDispatcherVersion = "3.1.0"
dependencies { dependencies {
androidTestCompile files('libs/junit4.jar') implementation "com.android.support:appcompat-v7:$supportVersion"
compile "com.android.support:appcompat-v7:$supportVersion" implementation "com.android.support:design:$supportVersion"
compile "com.android.support:design:$supportVersion" implementation "com.android.support:preference-v7:$supportVersion"
compile "com.android.support:preference-v7:$supportVersion" implementation "com.android.support:preference-v14:$supportVersion"
compile "com.android.support:preference-v14:$supportVersion" implementation "com.android.support:cardview-v7:$supportVersion"
compile "com.madgag.spongycastle:core:$spongycastleVersion" implementation "com.madgag.spongycastle:core:$spongycastleVersion"
compile "com.madgag.spongycastle:prov:$spongycastleVersion" implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
compile "joda-time:joda-time:2.9.9" // 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'
} }

Binary file not shown.

Binary file not shown.

View File

@@ -42,7 +42,7 @@ public class PwEntryTestV3 extends AndroidTestCase {
} }
public void testName() { public void testName() {
assertTrue("Name was " + mPE.title, mPE.title.equals("Amazon")); assertTrue("Name was " + mPE.getTitle(), mPE.getTitle().equals("Amazon"));
} }
public void testPassword() throws UnsupportedEncodingException { public void testPassword() throws UnsupportedEncodingException {
@@ -54,7 +54,7 @@ public class PwEntryTestV3 extends AndroidTestCase {
public void testCreation() { public void testCreation() {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTime(mPE.tCreation.getJDate()); cal.setTime(mPE.getCreationTime().getDate());
assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009); assertEquals("Incorrect year.", cal.get(Calendar.YEAR), 2009);
assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3); assertEquals("Incorrect month.", cal.get(Calendar.MONTH), 3);

View File

@@ -19,10 +19,7 @@
*/ */
package com.keepassdroid.tests; package com.keepassdroid.tests;
import java.util.UUID; import com.keepassdroid.database.AutoType;
import junit.framework.TestCase;
import com.keepassdroid.database.PwEntryV4; import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.PwGroupV4; import com.keepassdroid.database.PwGroupV4;
import com.keepassdroid.database.PwIconCustom; import com.keepassdroid.database.PwIconCustom;
@@ -30,29 +27,33 @@ import com.keepassdroid.database.PwIconStandard;
import com.keepassdroid.database.security.ProtectedBinary; import com.keepassdroid.database.security.ProtectedBinary;
import com.keepassdroid.database.security.ProtectedString; import com.keepassdroid.database.security.ProtectedString;
import junit.framework.TestCase;
import java.util.UUID;
public class PwEntryTestV4 extends TestCase { public class PwEntryTestV4 extends TestCase {
public void testAssign() { public void testAssign() {
PwEntryV4 entry = new PwEntryV4(); PwEntryV4 entry = new PwEntryV4();
entry.additional = "test223"; entry.setAdditional("test223");
entry.autoType = entry.new AutoType(); entry.setAutoType(new AutoType());
entry.autoType.defaultSequence = "1324"; entry.getAutoType().defaultSequence = "1324";
entry.autoType.enabled = true; entry.getAutoType().enabled = true;
entry.autoType.obfuscationOptions = 123412432109L; entry.getAutoType().obfuscationOptions = 123412432109L;
entry.autoType.put("key", "value"); entry.getAutoType().put("key", "value");
entry.backgroupColor = "blue"; entry.setBackgroupColor("blue");
entry.binaries.put("key1", new ProtectedBinary(false, new byte[] {0,1})); entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
entry.customIcon = new PwIconCustom(UUID.randomUUID(), new byte[0]); entry.setCustomIcon(new PwIconCustom(UUID.randomUUID(), new byte[0]));
entry.foregroundColor = "red"; entry.setForegroundColor("red");
entry.history.add(new PwEntryV4()); entry.addToHistory(new PwEntryV4());
entry.icon = new PwIconStandard(5); entry.setIcon(new PwIconStandard(5));
entry.overrideURL = "override"; entry.setOverrideURL("override");
entry.parent = new PwGroupV4(); entry.setParent(new PwGroupV4());
entry.strings.put("key2", new ProtectedString(false, "value2")); entry.addField("key2", new ProtectedString(false, "value2"));
entry.url = "http://localhost"; entry.setUrl("http://localhost");
entry.uuid = UUID.randomUUID(); entry.setUUID(UUID.randomUUID());
PwEntryV4 target = new PwEntryV4(); PwEntryV4 target = new PwEntryV4();
target.assign(entry); target.assign(entry);

View File

@@ -38,7 +38,7 @@ public class PwGroupTest extends AndroidTestCase {
} }
public void testGroupName() { public void testGroupName() {
assertTrue("Name was " + mPG.name, mPG.name.equals("Internet")); assertTrue("Name was " + mPG.getName(), mPG.getName().equals("Internet"));
} }
} }

View File

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

View File

@@ -32,19 +32,21 @@ public class EntryV4 extends TestCase {
db.historyMaxItems = 2; db.historyMaxItems = 2;
PwEntryV4 entry = new PwEntryV4(); PwEntryV4 entry = new PwEntryV4();
entry.setTitle("Title1", db); entry.startToDecodeReference(db);
entry.setUsername("User1", db); entry.setTitle("Title1");
entry.setUsername("User1");
entry.createBackup(db); entry.createBackup(db);
entry.setTitle("Title2", db); entry.setTitle("Title2");
entry.setUsername("User2", db); entry.setUsername("User2");
entry.createBackup(db); entry.createBackup(db);
entry.setTitle("Title3", db); entry.setTitle("Title3");
entry.setUsername("User3", db); entry.setUsername("User3");
entry.createBackup(db); entry.createBackup(db);
PwEntryV4 backup = entry.history.get(0); PwEntryV4 backup = entry.getHistory().get(0);
entry.endToDecodeReference(db);
assertEquals("Title2", backup.getTitle()); assertEquals("Title2", backup.getTitle());
assertEquals("User2", backup.getUsername()); assertEquals("User2", backup.getUsername());
} }

View File

@@ -27,15 +27,16 @@ import android.content.res.AssetManager;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import biz.source_code.base64Coder.Base64Coder; import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV4; import com.keepassdroid.database.PwDatabaseV4;
import com.keepassdroid.database.PwEntryV4; import com.keepassdroid.database.PwEntryV4;
import com.keepassdroid.database.load.ImporterV4; import com.keepassdroid.database.load.ImporterV4;
import com.keepassdroid.utils.SprEngine; import com.keepassdroid.utils.SprEngineV4;
import com.keepassdroid.utils.Types; import com.keepassdroid.utils.Types;
public class SprEngineTest extends AndroidTestCase { public class SprEngineTest extends AndroidTestCase {
private PwDatabaseV4 db; private PwDatabaseV4 db;
private SprEngine spr; private SprEngineV4 spr;
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
@@ -51,7 +52,7 @@ public class SprEngineTest extends AndroidTestCase {
is.close(); is.close();
spr = SprEngine.getInstance(db); spr = new SprEngineV4();
} }
private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}"; private final String REF = "{REF:P@I:2B1D56590D961F48A8CE8C392CE6CD35}";
@@ -69,7 +70,7 @@ public class SprEngineTest extends AndroidTestCase {
private UUID decodeUUID(String encoded) { private UUID decodeUUID(String encoded) {
if (encoded == null || encoded.length() == 0 ) { if (encoded == null || encoded.length() == 0 ) {
return PwDatabaseV4.UUID_ZERO; return PwDatabase.UUID_ZERO;
} }
byte[] buf = Base64Coder.decode(encoded); byte[] buf = Base64Coder.decode(encoded);

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kunzisoft.keepass" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto"> package="com.kunzisoft.keepass"
android:installLocation="auto">
<supports-screens <supports-screens
android:smallScreens="true" android:smallScreens="true"
android:normalScreens="true" android:normalScreens="true"
@@ -19,10 +20,32 @@
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="@xml/backup" android:fullBackupContent="@xml/backup"
android:backupAgent="com.keepassdroid.backup.SettingsBackupAgent" android:backupAgent="com.keepassdroid.backup.SettingsBackupAgent"
android:theme="@style/KeepassDXStyle.Light"> android:theme="@style/KeepassDXStyle.Light"
tools:replace="android:theme">
<!-- TODO backup API Key --> <!-- TODO backup API Key -->
<meta-data android:name="com.google.android.backup.api_key" <meta-data
android:value="" /> 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" <activity android:name=".KeePass"
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
@@ -30,18 +53,26 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="com.keepassdroid.fileselect.FileSelectActivity" <activity
android:name="com.keepassdroid.fileselect.FileSelectActivity"
android:launchMode="singleInstance"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" /> android:windowSoftInputMode="stateHidden" />
<activity android:name="com.keepassdroid.AboutActivity" <activity
android:name="com.keepassdroid.activities.AboutActivity"
android:launchMode="singleInstance"
android:label="@string/menu_about" /> 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> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" /> <data android:scheme="file" />
<data android:mimeType="*/*" /> <data android:scheme="content" />
<data android:mimeType="application/octet-stream" />
<data android:host="*" /> <data android:host="*" />
<data android:pathPattern=".*\\.kdb" /> <data android:pathPattern=".*\\.kdb" />
<data android:pathPattern=".*\\..*\\.kdb" /> <data android:pathPattern=".*\\..*\\.kdb" />
@@ -64,46 +95,63 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" /> <data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
</intent-filter> </intent-filter>
</activity> <intent-filter tools:ignore="AppLinkUrlError">
<activity android:name="com.keepassdroid.GroupActivityV3" android:configChanges="orientation|keyboardHidden"> <action android:name="android.intent.action.VIEW"/>
<!-- This metadata entry causes .app.SearchQueryResults to be the default context --> <category android:name="android.intent.category.DEFAULT"/>
<!-- whenever the user invokes search while in this Activity. --> <category android:name="android.intent.category.BROWSABLE"/>
<meta-data android:name="android.app.default_searchable" <data android:mimeType="application/octet-stream"/>
android:value="com.keepassdroid.search.SearchResults" /> </intent-filter>
</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" />
</activity> </activity>
<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:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" /> android:windowSoftInputMode="stateHidden" />
<activity <activity
android:name="com.keepassdroid.EntryActivityV4" android:name="com.keepassdroid.activities.EntryEditActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" /> android:windowSoftInputMode="stateHidden" />
<activity <activity
android:name="com.keepassdroid.EntryEditActivityV3" android:name="com.keepassdroid.search.SearchResultsActivity"
android:configChanges="orientation|keyboardHidden" android:launchMode="standard">
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">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEARCH" /> <action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </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>
<activity android:name="com.keepassdroid.settings.SettingsActivity" /> <activity android:name="com.keepassdroid.settings.SettingsActivity" />
<activity android:name="com.keepassdroid.autofill.AutoFillAuthActivity"
android:configChanges="orientation|keyboardHidden" />
<activity android:name="com.keepassdroid.settings.SettingsAutofillActivity" />
<service android:name="com.keepassdroid.services.TimeoutService" /> <service android:name="com.keepassdroid.timeout.TimeoutService" />
<service
android:name="com.keepassdroid.notifications.NotificationCopyingService"
android:enabled="true"
android:exported="false" />
<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" /> <meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
</application> </application>
</manifest> </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,491 +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.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
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.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;
import android.view.MenuItem;
import android.view.View;
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.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.utils.EmptyUtils;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.utils.Types;
import com.keepassdroid.utils.Util;
import java.text.DateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
public class EntryActivity extends LockCloseHideActivity {
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);
}
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);
}
}
@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();
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
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
if ( ! db.Loaded() ) {
finish();
return;
}
readOnly = db.readOnly;
setResult(KeePass.EXIT_NORMAL);
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();
finish();
return;
}
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
ActivityCompat.invalidateOptionsMenu(this);
// 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);
}
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()));
}
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intents.COPY_USERNAME);
filter.addAction(Intents.COPY_PASSWORD);
registerReceiver(mIntentReceiver, filter);
}
@Override
protected void onDestroy() {
// These members might never get initialized if the app timed out
if ( mIntentReceiver != null ) {
unregisterReceiver(mIntentReceiver);
}
if ( mNM != null ) {
try {
mNM.cancelAll();
} catch (SecurityException e) {
// Some android devices give a SecurityException when trying to cancel notifications without the WAKE_LOCK permission,
// we'll ignore these.
}
}
super.onDestroy();
}
private Notification getNotification(String intentText, int descResId) {
String desc = getString(descResId);
Intent intent = new Intent(intentText);
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
// no longer supported for api level >22
// notify.setLatestEventInfo(this, getString(R.string.app_name), desc, pending);
// so instead using compat builder and create new notification
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
Notification notify = builder.setContentIntent(pending).setContentText(desc).setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.notify).setTicker(desc).setWhen(System.currentTimeMillis()).build();
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);
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()));
Date expires = mEntry.getExpiryTime();
if ( mEntry.expires() ) {
populateText(R.id.entry_expires, getDateTime(expires));
} else {
populateText(R.id.entry_expires, 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);
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.donationMenuInflater(inflater, menu);
inflater.inflate(R.menu.entry, menu);
inflater.inflate(R.menu.lock_database, 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);
}
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();
if (EmptyUtils.isNullOrEmpty(url)) {
// 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) {
switch ( item.getItemId() ) {
case R.id.menu_donate:
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();
return true;
case R.id.menu_goto_url:
String url;
url = mEntry.getUrl();
// Default http:// if no protocol specified
if ( ! url.contains("://") ) {
url = "http://" + url;
}
try {
Util.gotoUrl(this, url);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show();
}
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);
finish();
return true;
case android.R.id.home :
finish(); // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item);
}
private void timeoutCopyToClipboard(String text) {
try {
Util.copyToClipboard(this, text);
} catch (SamsungClipboardException e) {
showSamsungDialog();
return;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String sClipClear = prefs.getString(getString(R.string.clipboard_timeout_key), getString(R.string.clipboard_timeout_default));
long clipClearTime = Long.parseLong(sClipClear);
if ( clipClearTime > 0 ) {
mTimer.schedule(new ClearClipboardTask(this, text), clipClearTime);
}
}
// 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.
private class ClearClipboardTask extends TimerTask {
private final String mClearText;
private final Context mCtx;
ClearClipboardTask(Context ctx, String clearText) {
mClearText = clearText;
mCtx = ctx;
}
@Override
public void run() {
String currentClip = Util.getClipboard(mCtx);
if ( currentClip.equals(mClearText) ) {
try {
Util.copyToClipboard(mCtx, "");
uiThreadCallback.post(new UIToastTask(mCtx, R.string.ClearClipboard));
} catch (SamsungClipboardException e) {
uiThreadCallback.post(new UIToastTask(mCtx, R.string.clipboard_error_clear));
}
}
}
}
private void showSamsungDialog() {
String text = getString(R.string.clipboard_error).concat(System.getProperty("line.separator")).concat(getString(R.string.clipboard_error_url));
SpannableString s = new SpannableString(text);
TextView tv = new TextView(this);
tv.setText(s);
tv.setAutoLinkMask(RESULT_OK);
tv.setMovementMethod(LinkMovementMethod.getInstance());
Linkify.addLinks(s, Linkify.WEB_URLS);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.clipboard_error_title)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setView(tv)
.show();
}
}

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,154 +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;
import android.content.DialogInterface;
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.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.Toast;
import com.kunzisoft.keepass.R;
import com.keepassdroid.password.PasswordGenerator;
public class GeneratePasswordFragment extends DialogFragment {
public static final String KEY_PASSWORD_ID = "KEY_PASSWORD_ID";
private GeneratePasswordListener mListener;
private View root;
private EditText lengthTextView;
@Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mListener = (GeneratePasswordListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement " + GeneratePasswordListener.class.getName());
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
root = inflater.inflate(R.layout.generate_password, null);
lengthTextView = (EditText) root.findViewById(R.id.length);
SeekBar seekBar = (SeekBar) root.findViewById(R.id.seekbar_length);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
lengthTextView.setText(String.valueOf(progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
Button genPassButton = (Button) root.findViewById(R.id.generate_password_button);
genPassButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
fillPassword();
}
});
builder.setView(root)
.setPositiveButton(R.string.accept, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
EditText password = (EditText) root.findViewById(R.id.password);
Bundle bundle = new Bundle();
bundle.putString(KEY_PASSWORD_ID, password.getText().toString());
mListener.acceptPassword(bundle);
dismiss();
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Bundle bundle = new Bundle();
mListener.cancelPassword(bundle);
dismiss();
}
});
// Pre-populate a password to possibly save the user a few clicks
fillPassword();
return builder.create();
}
private void fillPassword() {
EditText txtPassword = (EditText) root.findViewById(R.id.password);
txtPassword.setText(generatePassword());
}
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());
} catch (NumberFormatException e) {
Toast.makeText(getContext(), R.string.error_wrong_length, Toast.LENGTH_LONG).show();
} catch (IllegalArgumentException e) {
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
return password;
}
public interface GeneratePasswordListener {
void acceptPassword(Bundle bundle);
void cancelPassword(Bundle bundle);
}
}

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,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/>. * 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.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@@ -27,8 +27,10 @@ import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.TextView; import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.keepassdroid.stylish.StylishActivity; import com.keepassdroid.stylish.StylishActivity;
import com.kunzisoft.keepass.R;
import org.joda.time.DateTime;
public class AboutActivity extends StylishActivity { public class AboutActivity extends StylishActivity {
@@ -56,6 +58,9 @@ public class AboutActivity extends StylishActivity {
version = getString(R.string.version_label) + " " + version; version = getString(R.string.version_label) + " " + version;
TextView versionText = (TextView) findViewById(R.id.activity_about_version); TextView versionText = (TextView) findViewById(R.id.activity_about_version);
versionText.setText(version); versionText.setText(version);
TextView disclaimerText = (TextView) findViewById(R.id.disclaimer);
disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear()));
} }
@Override @Override

View File

@@ -0,0 +1,375 @@
/*
*
* 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.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
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 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.PwEntry;
import com.keepassdroid.notifications.NotificationCopyingService;
import com.keepassdroid.notifications.NotificationField;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.timeout.ClipboardHelper;
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.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
import static com.keepassdroid.settings.PreferencesUtil.isClipboardNotificationsEnable;
public class EntryActivity extends LockingHideActivity {
private final static String TAG = EntryActivity.class.getName();
public static final String KEY_ENTRY = "entry";
private ImageView titleIconView;
private TextView titleView;
private EntryContentsView entryContentsView;
protected PwEntry mEntry;
private boolean mShowPassword;
protected boolean readOnly = false;
private ClipboardHelper clipboardHelper;
private boolean firstLaunchOfActivity;
public static void launch(Activity act, PwEntry pw) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
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) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_view);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
Database db = App.getDB();
// Likely the app has been killed exit the activity
if ( ! db.Loaded() ) {
finish();
return;
}
readOnly = db.readOnly;
mShowPassword = !PreferencesUtil.isPasswordMask(this);
// Get Entry from UUID
Intent i = getIntent();
UUID uuid = Types.bytestoUUID(i.getByteArrayExtra(KEY_ENTRY));
mEntry = db.pm.entries.get(uuid);
if (mEntry == null) {
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show();
finish();
return;
}
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
invalidateOptionsMenu();
// Update last access time.
mEntry.touch(false, false);
// Get views
titleIconView = findViewById(R.id.entry_icon);
titleView = findViewById(R.id.entry_title);
entryContentsView = findViewById(R.id.entry_contents);
entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this));
// Setup Edit Buttons
View edit = findViewById(R.id.entry_edit);
edit.setOnClickListener(v -> EntryEditActivity.Launch(EntryActivity.this, mEntry));
if (readOnly) {
edit.setVisibility(View.GONE);
}
// Init the clipboard helper
clipboardHelper = new ClipboardHelper(this);
firstLaunchOfActivity = true;
}
@Override
protected void onResume() {
super.onResume();
// Fill data in resume to update from EntryEditActivity
fillData();
invalidateOptionsMenu();
// TODO Start decode
// If notifications enabled in settings
// Don't if application timeout
if (firstLaunchOfActivity && !App.isShutdown() && isClipboardNotificationsEnable(getApplicationContext())) {
if (mEntry.getUsername().length() > 0
|| (mEntry.getPassword().length() > 0 && PreferencesUtil.allowCopyPassword(this))
|| mEntry.containsExtraFields()) {
// username already copied, waiting for user's action before copy password.
Intent intent = new Intent(this, NotificationCopyingService.class);
intent.setAction(NotificationCopyingService.ACTION_NEW_NOTIFICATION);
if (mEntry.getTitle() != null)
intent.putExtra(NotificationCopyingService.EXTRA_ENTRY_TITLE, mEntry.getTitle());
// Construct notification fields
ArrayList<NotificationField> notificationFields = new ArrayList<>();
// Add username if exists to notifications
if (mEntry.getUsername().length() > 0)
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.USERNAME,
mEntry.getUsername(),
getResources()));
// Add password to notifications
if (PreferencesUtil.allowCopyPassword(this)) {
if (mEntry.getPassword().length() > 0)
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.PASSWORD,
mEntry.getPassword(),
getResources()));
}
// Add extra fields
if (mEntry.allowExtraFields()) {
try {
int anonymousFieldNumber = 0;
for (Map.Entry<String, String> entry : mEntry.getExtraFields().entrySet()) {
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.getAnonymousFieldId()[anonymousFieldNumber],
entry.getValue(),
entry.getKey(),
getResources()));
anonymousFieldNumber++;
}
} catch (ArrayIndexOutOfBoundsException e) {
Log.w(TAG, "Only " + NotificationField.NotificationFieldId.getAnonymousFieldId().length +
" anonymous notifications are available");
}
}
// Add notifications
intent.putParcelableArrayListExtra(NotificationCopyingService.EXTRA_FIELDS, notificationFields);
startService(intent);
}
// TODO end decode
}
firstLaunchOfActivity = false;
}
private void populateTitle(Drawable drawIcon, String text) {
titleIconView.setImageDrawable(drawIcon);
titleView.setText(text);
}
protected void fillData() {
Database db = App.getDB();
PwDatabase pm = db.pm;
mEntry.startToDecodeReference(pm);
// Assign title
populateTitle(db.drawFactory.getIconDrawable(getResources(), mEntry.getIcon()),
mEntry.getTitle());
// Assign basic fields
entryContentsView.assignUserName(mEntry.getUsername());
entryContentsView.assignUserNameCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
getString(R.string.copy_field, getString(R.string.entry_user_name)))
);
entryContentsView.assignPassword(mEntry.getPassword());
if (PreferencesUtil.allowCopyPassword(this)) {
entryContentsView.assignPasswordCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
getString(R.string.copy_field, getString(R.string.entry_password)))
);
}
entryContentsView.assignURL(mEntry.getUrl());
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
entryContentsView.assignComment(mEntry.getNotes());
// Assign custom fields
if (mEntry.allowExtraFields()) {
entryContentsView.clearExtraFields();
for (Map.Entry<String, String> field : mEntry.getExtraFields().entrySet()) {
final String label = field.getKey();
final String value = field.getValue();
entryContentsView.addExtraField(label, value, view ->
clipboardHelper.timeoutCopyToClipboard(value, getString(R.string.copy_field, label)));
}
}
// Assign dates
entryContentsView.assignCreationDate(mEntry.getCreationTime().getDate());
entryContentsView.assignModificationDate(mEntry.getLastModificationTime().getDate());
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime().getDate());
Date expires = mEntry.getExpiryTime().getDate();
if ( mEntry.expires() ) {
entryContentsView.assignExpiresDate(expires);
} else {
entryContentsView.assignExpiresDate(getString(R.string.never));
}
mEntry.endToDecodeReference(pm);
}
@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:
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);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.donationMenuInflater(inflater, menu);
inflater.inflate(R.menu.entry, menu);
inflater.inflate(R.menu.database_lock, menu);
MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass);
if (entryContentsView != null && togglePassword != null) {
if (!entryContentsView.isPasswordPresent()) {
togglePassword.setVisible(false);
} else {
changeShowPasswordIcon(togglePassword);
}
}
MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url);
if (gotoUrl != null) {
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so mEntry may not be set
if (mEntry == null) {
gotoUrl.setVisible(false);
} else {
String url = mEntry.getUrl();
if (EmptyUtils.isNullOrEmpty(url)) {
// disable button if url is not available
gotoUrl.setVisible(false);
}
}
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_donate:
return MenuUtil.onDonationItemSelected(this);
case R.id.menu_toggle_pass:
mShowPassword = !mShowPassword;
changeShowPasswordIcon(item);
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
return true;
case R.id.menu_goto_url:
String url;
url = mEntry.getUrl();
// Default http:// if no protocol specified
if ( ! url.contains("://") ) {
url = "http://" + url;
}
try {
Util.gotoUrl(this, url);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show();
}
return true;
case R.id.menu_lock:
lockAndExit();
return true;
case android.R.id.home :
finish(); // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item);
}
@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();
}
}

View File

@@ -0,0 +1,397 @@
/*
* 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.ViewGroup;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.Toast;
import com.keepassdroid.app.App;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDate;
import com.keepassdroid.database.PwEntry;
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.dialogs.GeneratePasswordDialogFragment;
import com.keepassdroid.dialogs.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.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 EditText entryTitleView;
private EditText entryUserNameView;
private EditText entryUrlView;
private EditText entryPasswordView;
private EditText entryConfirmationPasswordView;
private EditText 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) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
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) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
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 = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
scrollView = findViewById(R.id.entry_scroll);
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
entryTitleView = findViewById(R.id.entry_title);
entryUserNameView = findViewById(R.id.entry_user_name);
entryUrlView = findViewById(R.id.entry_url);
entryPasswordView = findViewById(R.id.entry_password);
entryConfirmationPasswordView = findViewById(R.id.entry_confpassword);
entryCommentView = findViewById(R.id.entry_comment);
entryExtraFieldsContainer = 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(v ->
IconPickerDialogFragment.launch(EntryEditActivity.this));
// Generate password button
View generatePassword = findViewById(R.id.generate_button);
generatePassword.setOnClickListener(v -> {
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
});
// Save button
View save = findViewById(R.id.entry_save);
save.setOnClickListener(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(v -> {
EntryEditNewField ees = new EntryEditNewField(EntryEditActivity.this);
ees.setData("", new ProtectedString(false, ""));
entryExtraFieldsContainer.addView(ees);
// Scroll bottom
scrollView.post(() -> 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() {
PwDatabase db = App.getDB().pm;
PwEntry newEntry = mEntry.clone();
newEntry.startToDecodeReference(db);
newEntry.createBackup(db);
newEntry.setLastAccessTime(new PwDate());
newEntry.setLastModificationTime(new PwDate());
newEntry.setTitle(entryTitleView.getText().toString());
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.getIconStandard());
}
}
newEntry.setUrl(entryUrlView.getText().toString());
newEntry.setUsername(entryUserNameView.getText().toString());
newEntry.setNotes(entryCommentView.getText().toString());
newEntry.setPassword(entryPasswordView.getText().toString());
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));
}
}
newEntry.endToDecodeReference(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 = findViewById(R.id.icon_button);
App.getDB().drawFactory.assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
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());
boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this);
if (visibilityFontActivated) {
Util.applyFontVisibilityTo(entryUserNameView);
Util.applyFontVisibilityTo(entryPasswordView);
Util.applyFontVisibilityTo(entryConfirmationPasswordView);
Util.applyFontVisibilityTo(entryCommentView);
}
if (mEntry.allowExtraFields()) {
LinearLayout container = 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(visibilityFontActivated);
container.addView(entryEditNewField);
}
}
}
@Override
public void iconPicked(Bundle bundle) {
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
ImageButton currIconButton = 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) {
Bundle bundle = new Bundle();
Intent intentEntry = new Intent();
bundle.putSerializable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
intentEntry.putExtras(bundle);
if (mIsNew) {
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
} else {
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,442 @@
/*
* 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.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.dialogs.AssignMasterKeyDialogFragment;
import com.keepassdroid.dialogs.GroupEditDialogFragment;
import com.keepassdroid.dialogs.IconPickerDialogFragment;
import com.keepassdroid.dialogs.ReadOnlyDialog;
import com.keepassdroid.search.SearchResultsActivity;
import com.keepassdroid.tasks.ProgressTask;
import com.keepassdroid.view.AddNodeButtonView;
import com.kunzisoft.keepass.R;
public class GroupActivity extends ListNodesActivity
implements GroupEditDialogFragment.EditGroupListener, IconPickerDialogFragment.IconPickerListener {
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private AddNodeButtonView addNodeButtonView;
protected boolean addGroupEnabled = false;
protected boolean addEntryEnabled = false;
protected boolean isRoot = false;
protected boolean readOnly = false;
protected EditGroupDialogAction editGroupDialogAction = EditGroupDialogAction.NONE;
private AutofillHelper autofillHelper;
private enum EditGroupDialogAction {
CREATION, UPDATE, NONE
}
private static final String TAG = "Group Activity:";
public static void launch(Activity act) {
LockingActivity.recordFirstTimeBeforeLaunch(act);
launch(act, (PwGroup) null);
}
public static void launch(Activity act, PwGroup group) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
act.startActivityForResult(intent, 0);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity act, AssistStructure assistStructure) {
if ( assistStructure != null ) {
LockingActivity.recordFirstTimeBeforeLaunch(act);
launch(act, null, assistStructure);
} else {
launch(act);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launch(Activity act, PwGroup group, AssistStructure assistStructure) {
if ( assistStructure != null ) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, 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
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
addNodeButtonView = findViewById(R.id.add_node_button);
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
// Hide when scroll
RecyclerView recyclerView = findViewById(R.id.nodes_list);
recyclerView.addOnScrollListener(addNodeButtonView.hideButtonOnScrollListener());
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("");
setSupportActionBar(toolbar);
if ( mCurrentGroup.getParent() != null )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
addNodeButtonView.setAddGroupClickListener(v -> {
editGroupDialogAction = EditGroupDialogAction.CREATION;
GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment();
groupEditDialogFragment.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
});
addNodeButtonView.setAddEntryClickListener(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(GROUP_ID_KEY);
if ( pwGroupId == null ) {
currentGroup = root;
} else {
currentGroup = db.pm.groups.get(pwGroupId);
}
if (currentGroup != null) {
addGroupEnabled = !readOnly;
addEntryEnabled = !readOnly; // TODO 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
addNodeButtonView.showButton();
}
@Override
protected void onStop() {
super.onStop();
// Hide button
addNodeButtonView.hideButton();
}
@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
addNodeButtonView.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) {
// TODO Flickering when locking, will be better with content provider
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:
lockAndExit();
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,315 @@
/*
* 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.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.dialogs.AssignMasterKeyDialogFragment;
import com.keepassdroid.dialogs.SortDialogFragment;
import com.keepassdroid.settings.PreferencesUtil;
import com.keepassdroid.tasks.UIToastTask;
import com.keepassdroid.utils.MenuUtil;
import com.keepassdroid.database.SortNodeEnum;
import com.keepassdroid.password.AssignPasswordHelper;
import com.kunzisoft.keepass.R;
public abstract class ListNodesActivity extends LockingActivity
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.OnNodeClickCallback,
SortDialogFragment.SortSelectionListener {
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);
invalidateOptionsMenu();
// TODO Move in search
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:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
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) {
// TODO BUG HERE
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 != null
&& 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,141 @@
/*
* 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.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
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 static final String TAG = LockingActivity.class.getName();
public static final int RESULT_EXIT_LOCK = 1450;
private static final String AT_LEAST_SECOND_SHOWN_KEY = "AT_LEAST_SECOND_SHOWN_KEY";
private ScreenReceiver screenReceiver;
private boolean exitLock;
protected static void recordFirstTimeBeforeLaunch(Activity activity) {
TimeoutHelper.recordTime(activity);
}
protected static boolean checkTimeIsAllowedOrFinish(Activity activity) {
return TimeoutHelper.checkTime(activity);
}
@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;
exitLock = false;
// WARNING TODO recordTime is not called after a back if was in backstack
}
public static void checkShutdown(Activity act) {
if (App.isShutdown() && App.getDB().Loaded()) {
Log.i(TAG, "Shutdown " + act.getLocalClassName() +
" after inactivity or manual lock");
act.setResult(RESULT_EXIT_LOCK);
act.finish();
}
}
protected void lockAndExit() {
App.setShutdown();
setResult(LockingActivity.RESULT_EXIT_LOCK);
finish();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_EXIT_LOCK) {
exitLock = true;
checkShutdown(this);
}
}
@Override
protected void onResume() {
super.onResume();
// After the first creation
// or If simply swipe with another application
// If the time is out -> close the Activity
TimeoutHelper.checkTime(this);
// If onCreate already record time
if (!exitLock)
TimeoutHelper.recordTime(this);
}
@Override
protected void onPause() {
super.onPause();
// If the time is out during our navigation in activity -> close the Activity
TimeoutHelper.checkTime(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if(screenReceiver != null)
unregisterReceiver(screenReceiver);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(AT_LEAST_SECOND_SHOWN_KEY, true);
}
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();
checkShutdown(LockingActivity.this);
}
}
}
}
}
}

View File

@@ -17,29 +17,30 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * 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.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle;
import android.view.WindowManager;
import com.kunzisoft.keepass.KeePass; import com.keepassdroid.compat.BuildCompat;
import com.keepassdroid.app.App;
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 @Override
protected void onResume() { protected void onCreate(Bundle savedInstanceState) {
super.onResume(); super.onCreate(savedInstanceState);
checkShutdown(); // 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) {
private void checkShutdown() { getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
if ( App.isShutdown() && App.getDB().Loaded() ) {
setResult(KeePass.EXIT_LOCK);
finish();
} }
} }
/* (non-Javadoc) Workaround for HTC Linkify issues /* (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,295 @@
/*
* 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();
if (group != null) {
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,18 +19,19 @@
*/ */
package com.keepassdroid.app; 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.compat.PRNGFixes;
import com.keepassdroid.database.Database;
import com.keepassdroid.fileselect.RecentFileHistory; import com.keepassdroid.fileselect.RecentFileHistory;
import com.keepassdroid.stylish.Stylish; import com.keepassdroid.stylish.Stylish;
import java.util.Calendar; import java.util.Calendar;
public class App extends Application { public class App extends MultiDexApplication {
private static Database db = null; private static Database db = null;
private static boolean shutdown = false; private static boolean shutdown = false;
private static CharSequence mMessage = "";
private static Calendar calendar = null; private static Calendar calendar = null;
private static RecentFileHistory fileHistory = null; private static RecentFileHistory fileHistory = null;
@@ -38,7 +39,6 @@ public class App extends Application {
if ( db == null ) { if ( db == null ) {
db = new Database(); db = new Database();
} }
return db; return db;
} }
@@ -56,17 +56,27 @@ public class App extends Application {
public static void setShutdown() { public static void setShutdown() {
shutdown = true; shutdown = true;
mMessage = "";
}
public static void setShutdown(CharSequence message) {
shutdown = true;
mMessage = message;
}
public static CharSequence getMessage() {
return mMessage;
} }
public static void clearShutdown() { public static void clearShutdown() {
shutdown = false; shutdown = false;
mMessage = "";
} }
public static Calendar getCalendar() { public static Calendar getCalendar() {
if ( calendar == null ) { if ( calendar == null ) {
calendar = Calendar.getInstance(); calendar = Calendar.getInstance();
} }
return calendar; return calendar;
} }
@@ -75,9 +85,7 @@ public class App extends Application {
super.onCreate(); super.onCreate();
Stylish.init(this); Stylish.init(this);
fileHistory = new RecentFileHistory(this); fileHistory = new RecentFileHistory(this);
PRNGFixes.apply(); PRNGFixes.apply();
} }
@@ -86,7 +94,6 @@ public class App extends Application {
if ( db != null ) { if ( db != null ) {
db.clear(); db.clear();
} }
super.onTerminate(); super.onTerminate();
} }
} }

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

@@ -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.compat;
import java.lang.reflect.Method;
import android.app.Activity;
public class ActivityCompat {
private static Method invalidateOptMenu;
static {
try {
invalidateOptMenu = Activity.class.getMethod("invalidateOptionsMenu", (Class<Activity>[]) null);
} catch (Exception e) {
// Do nothing if method dosen't exist
}
}
public static void invalidateOptionsMenu(Activity act) {
if (invalidateOptMenu != null) {
try {
invalidateOptMenu.invoke(act, (Object[]) null);
} catch (Exception e) {
// Do nothing
}
}
}
}

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; } if (!supportsStorageFramework()) { return false; }
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); 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

@@ -0,0 +1,37 @@
package com.keepassdroid.database;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class AutoType implements Cloneable, Serializable {
private static final long OBF_OPT_NONE = 0;
public boolean enabled = true;
public long obfuscationOptions = OBF_OPT_NONE;
public String defaultSequence = "";
private HashMap<String, String> windowSeqPairs = new HashMap<>();
@SuppressWarnings("unchecked")
public AutoType clone() {
AutoType auto;
try {
auto = (AutoType) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
auto.windowSeqPairs = (HashMap<String, String>) windowSeqPairs.clone();
return auto;
}
public void put(String key, String value) {
windowSeqPairs.put(key, value);
}
public Set<Map.Entry<String, String>> entrySet() {
return windowSeqPairs.entrySet();
}
}

View File

@@ -19,6 +19,7 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@@ -27,7 +28,7 @@ import java.util.Set;
import com.keepassdroid.database.security.ProtectedBinary; import com.keepassdroid.database.security.ProtectedBinary;
public class BinaryPool { public class BinaryPool {
private HashMap<Integer, ProtectedBinary> pool = new HashMap<Integer, ProtectedBinary>(); private HashMap<Integer, ProtectedBinary> pool = new HashMap<>();
public BinaryPool() { public BinaryPool() {
@@ -49,17 +50,23 @@ public class BinaryPool {
public Set<Entry<Integer, ProtectedBinary>> entrySet() { public Set<Entry<Integer, ProtectedBinary>> entrySet() {
return pool.entrySet(); return pool.entrySet();
} }
public void clear() {
pool.clear();
}
public Collection<ProtectedBinary> binaries() {
return pool.values();
}
private class AddBinaries extends EntryHandler<PwEntryV4> { private class AddBinaries extends EntryHandler<PwEntryV4> {
@Override @Override
public boolean operate(PwEntryV4 entry) { public boolean operate(PwEntryV4 entry) {
for (PwEntryV4 histEntry : entry.history) { for (PwEntryV4 histEntry : entry.getHistory()) {
poolAdd(histEntry.binaries); poolAdd(histEntry.getBinaries());
} }
poolAdd(entry.getBinaries());
poolAdd(entry.binaries);
return true; return true;
} }
@@ -72,7 +79,7 @@ public class BinaryPool {
} }
private void poolAdd(ProtectedBinary pb) { public void poolAdd(ProtectedBinary pb) {
assert(pb != null); assert(pb != null);
if (poolFind(pb) != -1) return; if (poolFind(pb) != -1) return;

View File

@@ -17,43 +17,40 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>. * 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.Context;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log; 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.ContentFileNotFoundException;
import com.keepassdroid.database.exception.InvalidDBException; import com.keepassdroid.database.exception.InvalidDBException;
import com.keepassdroid.database.exception.InvalidPasswordException;
import com.keepassdroid.database.exception.PwDbOutputException; import com.keepassdroid.database.exception.PwDbOutputException;
import com.keepassdroid.database.load.Importer; import com.keepassdroid.database.load.Importer;
import com.keepassdroid.database.load.ImporterFactory; import com.keepassdroid.database.load.ImporterFactory;
import com.keepassdroid.database.save.PwDbOutput; import com.keepassdroid.database.save.PwDbOutput;
import com.keepassdroid.icons.DrawableFactory; import com.keepassdroid.icons.DrawableFactory;
import com.keepassdroid.search.SearchDbHelper; import com.keepassdroid.search.SearchDbHelper;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.utils.UriUtil; 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 * @author bpellin
*/ */
public class Database { public class Database {
public Set<PwGroup> dirty = new HashSet<PwGroup>();
public PwDatabase pm; public PwDatabase pm;
public Uri mUri; public Uri mUri;
public SearchDbHelper searchHelper; public SearchDbHelper searchHelper;
@@ -92,6 +89,26 @@ public class Database {
readOnly = !file.canWrite(); 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; InputStream is, kfIs;
try { try {
is = UriUtil.getUriInputStream(ctx, uri); is = UriUtil.getUriInputStream(ctx, uri);
@@ -106,7 +123,7 @@ public class Database {
Log.e("KPD", "Database::LoadData", e); Log.e("KPD", "Database::LoadData", e);
throw ContentFileNotFoundException.getInstance(keyfile); 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 { 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 { 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); BufferedInputStream bis = new BufferedInputStream(is);
if ( ! bis.markSupported() ) { if ( ! bis.markSupported() ) {
@@ -127,7 +148,7 @@ public class Database {
bis.reset(); // Return to the start bis.reset(); // Return to the start
pm = imp.openDatabase(bis, password, kfIs, status); pm = imp.openDatabase(bis, password, kfIs, status, roundsFix);
if ( pm != null ) { if ( pm != null ) {
PwGroup root = pm.rootGroup; PwGroup root = pm.rootGroup;
pm.populateGlobals(root); pm.populateGlobals(root);
@@ -196,7 +217,6 @@ public class Database {
} }
public void clear() { public void clear() {
dirty.clear();
drawFactory.clear(); drawFactory.clear();
pm = null; pm = null;
@@ -204,16 +224,4 @@ public class Database {
loaded = false; loaded = false;
passwordEncodingError = 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

@@ -52,7 +52,7 @@ public abstract class EntrySearchHandler extends EntryHandler<PwEntry> {
return true; return true;
} }
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime())) { if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime().getDate())) {
return true; return true;
} }

View File

@@ -39,7 +39,7 @@ public class EntrySearchHandlerAll extends EntryHandler<PwEntry> {
return true; return true;
} }
if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime())) { if (sp.excludeExpired && entry.expires() && now.after(entry.getExpiryTime().getDate())) {
return true; return true;
} }

View File

@@ -37,7 +37,7 @@ public class EntrySearchHandlerV4 extends EntrySearchHandler {
protected boolean searchID(PwEntry e) { protected boolean searchID(PwEntry e) {
PwEntryV4 entry = (PwEntryV4) e; PwEntryV4 entry = (PwEntryV4) e;
if (sp.searchInUUIDs) { if (sp.searchInUUIDs) {
String hex = UuidUtil.toHexString(entry.uuid); String hex = UuidUtil.toHexString(entry.getUUID());
return StrUtil.indexOfIgnoreCase(hex, sp.searchString, Locale.ENGLISH) >= 0; return StrUtil.indexOfIgnoreCase(hex, sp.searchString, Locale.ENGLISH) >= 0;
} }

View File

@@ -0,0 +1,19 @@
package com.keepassdroid.database;
public interface ISmallTimeLogger {
PwDate getLastModificationTime();
void setLastModificationTime(PwDate date);
PwDate getCreationTime();
void setCreationTime(PwDate date);
PwDate getLastAccessTime();
void setLastAccessTime(PwDate date);
PwDate getExpiryTime();
void setExpiryTime(PwDate date);
boolean expires();
void setExpires(boolean exp);
}

View File

@@ -19,28 +19,12 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import java.util.Date; public interface ITimeLogger extends ISmallTimeLogger {
public interface ITimeLogger {
Date getLastModificationTime();
void setLastModificationTime(Date date);
Date getCreationTime();
void setCreationTime(Date date);
Date getLastAccessTime();
void setLastAccessTime(Date date);
Date getExpiryTime();
void setExpiryTime(Date date);
boolean expires();
void setExpires(boolean exp);
long getUsageCount(); long getUsageCount();
void setUsageCount(long count); void setUsageCount(long count);
Date getLocationChanged(); PwDate getLocationChanged();
void setLocationChanged(Date date); void setLocationChanged(PwDate date);
} }

View File

@@ -19,12 +19,8 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -36,8 +32,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import android.os.DropBoxManager.Entry;
import com.keepassdroid.crypto.finalkey.FinalKey; import com.keepassdroid.crypto.finalkey.FinalKey;
import com.keepassdroid.crypto.finalkey.FinalKeyFactory; import com.keepassdroid.crypto.finalkey.FinalKeyFactory;
import com.keepassdroid.database.exception.InvalidKeyFileException; import com.keepassdroid.database.exception.InvalidKeyFileException;
@@ -47,13 +41,14 @@ import com.keepassdroid.utils.Util;
public abstract class PwDatabase { public abstract class PwDatabase {
public static final UUID UUID_ZERO = new UUID(0,0);
public byte masterKey[] = new byte[32]; public byte masterKey[] = new byte[32];
public byte[] finalKey; public byte[] finalKey;
public String name = "KeePass database"; public String name = "KeePass database";
public PwGroup rootGroup; public PwGroup rootGroup;
public PwIconFactory iconFactory = new PwIconFactory(); public PwIconFactory iconFactory = new PwIconFactory();
public Map<PwGroupId, PwGroup> groups = new HashMap<PwGroupId, PwGroup>(); public Map<PwGroupId, PwGroup> groups = new HashMap<>();
public Map<UUID, PwEntry> entries = new HashMap<UUID, PwEntry>(); public Map<UUID, PwEntry> entries = new HashMap<>();
private static boolean isKDBExtension(String filename) { private static boolean isKDBExtension(String filename) {
@@ -252,7 +247,7 @@ public abstract class PwDatabase {
public abstract void setNumRounds(long rounds) throws NumberFormatException; public abstract void setNumRounds(long rounds) throws NumberFormatException;
public abstract boolean appSettingsEnabled(); public abstract boolean algorithmSettingsEnabled();
public abstract PwEncryptionAlgorithm getEncAlgorithm(); public abstract PwEncryptionAlgorithm getEncAlgorithm();
@@ -262,7 +257,7 @@ public abstract class PwDatabase {
parent = rootGroup; parent = rootGroup;
} }
parent.childGroups.add(newGroup); parent.addChildGroup(newGroup);
newGroup.setParent(parent); newGroup.setParent(parent);
groups.put(newGroup.getId(), newGroup); groups.put(newGroup.getId(), newGroup);
@@ -271,15 +266,16 @@ public abstract class PwDatabase {
public void removeGroupFrom(PwGroup remove, PwGroup parent) { public void removeGroupFrom(PwGroup remove, PwGroup parent) {
// Remove tree from parent tree // Remove tree from parent tree
parent.childGroups.remove(remove); if (parent != null) {
parent.removeChildGroup(remove);
}
groups.remove(remove.getId()); groups.remove(remove.getId());
} }
public void addEntryTo(PwEntry newEntry, PwGroup parent) { public void addEntryTo(PwEntry newEntry, PwGroup parent) {
// Add entry to parent // Add entry to parent
if (parent != null) { if (parent != null) {
parent.childEntries.add(newEntry); parent.addChildEntry(newEntry);
} }
newEntry.setParent(parent); newEntry.setParent(parent);
@@ -289,7 +285,7 @@ public abstract class PwDatabase {
public void removeEntryFrom(PwEntry remove, PwGroup parent) { public void removeEntryFrom(PwEntry remove, PwGroup parent) {
// Remove entry for parent // Remove entry for parent
if (parent != null) { if (parent != null) {
parent.childEntries.remove(remove); parent.removeChildEntry(remove);
} }
entries.remove(remove.getUUID()); entries.remove(remove.getUUID());
} }
@@ -322,8 +318,8 @@ public abstract class PwDatabase {
public void populateGlobals(PwGroup currentGroup) { public void populateGlobals(PwGroup currentGroup) {
List<PwGroup> childGroups = currentGroup.childGroups; List<PwGroup> childGroups = currentGroup.getChildGroups();
List<PwEntry> childEntries = currentGroup.childEntries; List<PwEntry> childEntries = currentGroup.getChildEntries();
for (int i = 0; i < childEntries.size(); i++ ) { for (int i = 0; i < childEntries.size(); i++ ) {
PwEntry cur = childEntries.get(i); PwEntry cur = childEntries.get(i);
@@ -337,28 +333,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) { public boolean canRecycle(PwGroup group) {
return false; 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) { public boolean canRecycle(PwEntry entry) {
return false; 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) { public void recycle(PwEntry entry) {
// Assume calls to this are protected by calling inRecyleBin // Assume calls to this are protected by calling inRecyleBin
throw new RuntimeException("Call not valid for .kdb databases."); 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) { public void undoRecycle(PwEntry entry, PwGroup origParent) {
throw new RuntimeException("Call not valid for .kdb databases."); 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) { public void deleteEntry(PwEntry entry) {
PwGroup parent = entry.getParent(); PwGroup parent = entry.getParent();
removeEntryFrom(entry, parent); removeEntryFrom(entry, parent);
parent.touch(false, true); parent.touch(false, true);
}
// TODO Delete group
public void undoDeleteGroup(PwGroup group, PwGroup origParent) {
addGroupTo(group, origParent);
} }
public void undoDeleteEntry(PwEntry entry, PwGroup 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.UUID;
import com.keepassdroid.database.exception.InvalidKeyFileException; import com.keepassdroid.database.exception.InvalidKeyFileException;
@@ -70,13 +69,13 @@ public class PwDatabaseV3 extends PwDatabase {
public PwEntry metaInfo; public PwEntry metaInfo;
// all entries // all entries
public List<PwEntry> entries = new ArrayList<PwEntry>(); public List<PwEntry> entries = new ArrayList<>();
// all groups // all groups
public List<PwGroup> groups = new ArrayList<PwGroup>(); public List<PwGroup> groups = new ArrayList<>();
// Algorithm used to encrypt the database // Algorithm used to encrypt the database
public PwEncryptionAlgorithm algorithm; public PwEncryptionAlgorithm algorithm;
public int numKeyEncRounds; public int numKeyEncRounds;
@Override @Override
public PwEncryptionAlgorithm getEncAlgorithm() { public PwEncryptionAlgorithm getEncAlgorithm() {
return algorithm; return algorithm;
@@ -106,7 +105,7 @@ public class PwDatabaseV3 extends PwDatabase {
List<PwGroup> kids = new ArrayList<PwGroup>(); List<PwGroup> kids = new ArrayList<PwGroup>();
for (int i = 0; i < groups.size(); i++) { for (int i = 0; i < groups.size(); i++) {
PwGroupV3 grp = (PwGroupV3) groups.get(i); PwGroupV3 grp = (PwGroupV3) groups.get(i);
if (grp.level == target) if (grp.getLevel() == target)
kids.add(grp); kids.add(grp);
} }
return kids; return kids;
@@ -115,8 +114,8 @@ public class PwDatabaseV3 extends PwDatabase {
public int getRootGroupId() { public int getRootGroupId() {
for (int i = 0; i < groups.size(); i++) { for (int i = 0; i < groups.size(); i++) {
PwGroupV3 grp = (PwGroupV3) groups.get(i); PwGroupV3 grp = (PwGroupV3) groups.get(i);
if (grp.level == 0) { if (grp.getLevel() == 0) {
return grp.groupId; return grp.getGroupId();
} }
} }
@@ -125,13 +124,13 @@ public class PwDatabaseV3 extends PwDatabase {
public List<PwGroup> getGrpChildren(PwGroupV3 parent) { public List<PwGroup> getGrpChildren(PwGroupV3 parent) {
int idx = groups.indexOf(parent); int idx = groups.indexOf(parent);
int target = parent.level + 1; int target = parent.getLevel() + 1;
List<PwGroup> kids = new ArrayList<PwGroup>(); List<PwGroup> kids = new ArrayList<PwGroup>();
while (++idx < groups.size()) { while (++idx < groups.size()) {
PwGroupV3 grp = (PwGroupV3) groups.get(idx); PwGroupV3 grp = (PwGroupV3) groups.get(idx);
if (grp.level < target) if (grp.getLevel() < target)
break; break;
else if (grp.level == target) else if (grp.getLevel() == target)
kids.add(grp); kids.add(grp);
} }
return kids; return kids;
@@ -146,7 +145,7 @@ public class PwDatabaseV3 extends PwDatabase {
*/ */
for (int i = 0; i < entries.size(); i++) { for (int i = 0; i < entries.size(); i++) {
PwEntryV3 ent = (PwEntryV3) entries.get(i); PwEntryV3 ent = (PwEntryV3) entries.get(i);
if (ent.groupId == parent.groupId) if (ent.getGroupId() == parent.getGroupId())
kids.add(ent); kids.add(ent);
} }
return kids; return kids;
@@ -164,11 +163,11 @@ public class PwDatabaseV3 extends PwDatabase {
List<PwGroup> rootChildGroups = getGrpRoots(); List<PwGroup> rootChildGroups = getGrpRoots();
root.setGroups(rootChildGroups); root.setGroups(rootChildGroups);
root.childEntries = new ArrayList<PwEntry>(); root.setEntries(new ArrayList<>());
root.level = -1; root.setLevel(-1);
for (int i = 0; i < rootChildGroups.size(); i++) { for (int i = 0; i < rootChildGroups.size(); i++) {
PwGroupV3 grp = (PwGroupV3) rootChildGroups.get(i); PwGroupV3 grp = (PwGroupV3) rootChildGroups.get(i);
grp.parent = root; grp.setParent(root);
constructTree(grp); constructTree(grp);
} }
return; return;
@@ -177,18 +176,18 @@ public class PwDatabaseV3 extends PwDatabase {
// I'm in non-root // I'm in non-root
// get child groups // get child groups
currentGroup.setGroups(getGrpChildren(currentGroup)); currentGroup.setGroups(getGrpChildren(currentGroup));
currentGroup.childEntries = getEntries(currentGroup); currentGroup.setEntries(getEntries(currentGroup));
// set parent in child entries // 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); PwEntryV3 entry = (PwEntryV3) currentGroup.getChildEntryAt(i);
entry.parent = currentGroup; entry.setParent(currentGroup);
} }
// recursively construct child groups // 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); PwGroupV3 grp = (PwGroupV3) currentGroup.getChildGroupAt(i);
grp.parent = currentGroup; grp.setParent(currentGroup);
constructTree((PwGroupV3) currentGroup.childGroups.get(i)); constructTree((PwGroupV3) currentGroup.getChildGroupAt(i));
} }
return; return;
} }
@@ -222,18 +221,16 @@ public class PwDatabaseV3 extends PwDatabase {
public byte[] getMasterKey(String key, InputStream keyInputStream) public byte[] getMasterKey(String key, InputStream keyInputStream)
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert (key != null);
if (key.length() > 0 && keyInputStream != null) { if (key != null && key.length() > 0 && keyInputStream != null) {
return getCompositeKey(key, keyInputStream); return getCompositeKey(key, keyInputStream);
} else if (key.length() > 0) { } else if (key != null && key.length() > 0) {
return getPasswordKey(key); return getPasswordKey(key);
} else if (keyInputStream != null) { } else if (keyInputStream != null) {
return getFileKey(keyInputStream); return getFileKey(keyInputStream);
} else { } else {
throw new IllegalArgumentException("Key cannot be empty."); throw new IllegalArgumentException("Key cannot be empty.");
} }
} }
@Override @Override
@@ -263,7 +260,7 @@ public class PwDatabaseV3 extends PwDatabase {
} }
@Override @Override
public boolean appSettingsEnabled() { public boolean algorithmSettingsEnabled() {
return true; return true;
} }
@@ -319,11 +316,11 @@ public class PwDatabaseV3 extends PwDatabase {
public boolean isBackup(PwGroup group) { public boolean isBackup(PwGroup group) {
PwGroupV3 g = (PwGroupV3) group; PwGroupV3 g = (PwGroupV3) group;
while (g != null) { while (g != null) {
if (g.level == 0 && g.name.equalsIgnoreCase("Backup")) { if (g.getLevel() == 0 && g.getName().equalsIgnoreCase("Backup")) {
return true; return true;
} }
g = g.parent; g = (PwGroupV3) g.getParent();
} }
return false; return false;
@@ -341,7 +338,7 @@ public class PwDatabaseV3 extends PwDatabase {
private void initAndAddGroup(String name, int iconId, PwGroup parent) { private void initAndAddGroup(String name, int iconId, PwGroup parent) {
PwGroup group = createGroup(); PwGroup group = createGroup();
group.initNewGroup(name, newGroupId()); group.initNewGroup(name, newGroupId());
group.icon = iconFactory.getIcon(iconId); group.setIcon(iconFactory.getIcon(iconId));
addGroupTo(group, parent); addGroupTo(group, parent);
} }

View File

@@ -19,12 +19,29 @@
*/ */
package com.keepassdroid.database; 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.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.acl.Group;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
@@ -36,34 +53,11 @@ import java.util.UUID;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; 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 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 { public class PwDatabaseV4 extends PwDatabase {
public static final Date DEFAULT_NOW = new Date();
public static final UUID UUID_ZERO = new UUID(0,0);
public static final int DEFAULT_ROUNDS = 6000; public static final int DEFAULT_ROUNDS = 6000;
private static final int DEFAULT_HISTORY_MAX_ITEMS = 10; // -1 unlimited private static final int DEFAULT_HISTORY_MAX_ITEMS = 10; // -1 unlimited
private static final long DEFAULT_HISTORY_MAX_SIZE = 6 * 1024 * 1024; // -1 unlimited private static final long DEFAULT_HISTORY_MAX_SIZE = 6 * 1024 * 1024; // -1 unlimited
@@ -73,15 +67,16 @@ public class PwDatabaseV4 extends PwDatabase {
public UUID dataCipher = AesEngine.CIPHER_UUID; public UUID dataCipher = AesEngine.CIPHER_UUID;
public CipherEngine dataEngine = new AesEngine(); public CipherEngine dataEngine = new AesEngine();
public PwCompressionAlgorithm compressionAlgorithm = PwCompressionAlgorithm.Gzip; public PwCompressionAlgorithm compressionAlgorithm = PwCompressionAlgorithm.Gzip;
// TODO: Refactor me away to get directly from kdfParameters
public long numKeyEncRounds = 6000; public long numKeyEncRounds = 6000;
public Date nameChanged = DEFAULT_NOW; public Date nameChanged = new Date();
public Date settingsChanged = DEFAULT_NOW; public Date settingsChanged = new Date();
public String description = ""; public String description = "";
public Date descriptionChanged = DEFAULT_NOW; public Date descriptionChanged = new Date();
public String defaultUserName = ""; public String defaultUserName = "";
public Date defaultUserNameChanged = DEFAULT_NOW; public Date defaultUserNameChanged = new Date();
public Date keyLastChanged = DEFAULT_NOW; public Date keyLastChanged = new Date();
public long keyChangeRecDays = -1; public long keyChangeRecDays = -1;
public long keyChangeForceDays = 1; public long keyChangeForceDays = 1;
public boolean keyChangeForceOnce = false; public boolean keyChangeForceOnce = false;
@@ -90,20 +85,21 @@ public class PwDatabaseV4 extends PwDatabase {
public String color = ""; public String color = "";
public boolean recycleBinEnabled = true; public boolean recycleBinEnabled = true;
public UUID recycleBinUUID = UUID_ZERO; public UUID recycleBinUUID = UUID_ZERO;
public Date recycleBinChanged = DEFAULT_NOW; public Date recycleBinChanged = new Date();
public UUID entryTemplatesGroup = UUID_ZERO; public UUID entryTemplatesGroup = UUID_ZERO;
public Date entryTemplatesGroupChanged = DEFAULT_NOW; public Date entryTemplatesGroupChanged = new Date();
public int historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS; public int historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS;
public long historyMaxSize = DEFAULT_HISTORY_MAX_SIZE; public long historyMaxSize = DEFAULT_HISTORY_MAX_SIZE;
public UUID lastSelectedGroup = UUID_ZERO; public UUID lastSelectedGroup = UUID_ZERO;
public UUID lastTopVisibleGroup = UUID_ZERO; public UUID lastTopVisibleGroup = UUID_ZERO;
public MemoryProtectionConfig memoryProtection = new MemoryProtectionConfig(); public MemoryProtectionConfig memoryProtection = new MemoryProtectionConfig();
public List<PwDeletedObject> deletedObjects = new ArrayList<PwDeletedObject>(); public List<PwDeletedObject> deletedObjects = new ArrayList<>();
public List<PwIconCustom> customIcons = new ArrayList<PwIconCustom>(); public List<PwIconCustom> customIcons = new ArrayList<>();
public Map<String, String> customData = new HashMap<String, String>(); public Map<String, String> customData = new HashMap<>();
public KdfParameters kdfParameters = KdfFactory.getDefaultParameters(); public KdfParameters kdfParameters = KdfFactory.getDefaultParameters();
public VariantDictionary publicCustomData = new VariantDictionary(); public VariantDictionary publicCustomData = new VariantDictionary();
public BinaryPool binPool = new BinaryPool();
public String localizedAppName = "KeePassDroid"; public String localizedAppName = "KeePassDroid";
public class MemoryProtectionConfig { public class MemoryProtectionConfig {
@@ -131,7 +127,7 @@ public class PwDatabaseV4 extends PwDatabase {
throws InvalidKeyFileException, IOException { throws InvalidKeyFileException, IOException {
assert(key != null); assert(key != null);
byte[] fKey; byte[] fKey = new byte[]{};
if ( key.length() > 0 && keyInputStream != null) { if ( key.length() > 0 && keyInputStream != null) {
return getCompositeKey(key, keyInputStream); return getCompositeKey(key, keyInputStream);
@@ -139,8 +135,6 @@ public class PwDatabaseV4 extends PwDatabase {
fKey = getPasswordKey(key); fKey = getPasswordKey(key);
} else if ( keyInputStream != null) { } else if ( keyInputStream != null) {
fKey = getFileKey(keyInputStream); fKey = getFileKey(keyInputStream);
} else {
throw new IllegalArgumentException( "Key cannot be empty." );
} }
MessageDigest md; MessageDigest md;
@@ -175,13 +169,24 @@ public class PwDatabaseV4 extends PwDatabase {
Arrays.fill(cmpKey, (byte)0); Arrays.fill(cmpKey, (byte)0);
} }
} }
public void makeFinalKey(byte[] masterSeed, KdfParameters kdfP) throws IOException { 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); KdfEngine kdfEngine = KdfFactory.get(kdfP.kdfUUID);
if (kdfEngine == null) { if (kdfEngine == null) {
throw new IOException("Unknown key derivation function"); 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); byte[] transformedMasterKey = kdfEngine.transform(masterKey, kdfP);
if (transformedMasterKey.length != 32) { if (transformedMasterKey.length != 32) {
transformedMasterKey = CryptoUtil.hashSha256(transformedMasterKey); transformedMasterKey = CryptoUtil.hashSha256(transformedMasterKey);
@@ -262,25 +267,42 @@ public class PwDatabaseV4 extends PwDatabase {
public List<PwGroup> getGroups() { public List<PwGroup> getGroups() {
List<PwGroup> list = new ArrayList<PwGroup>(); List<PwGroup> list = new ArrayList<PwGroup>();
PwGroupV4 root = (PwGroupV4) rootGroup; PwGroupV4 root = (PwGroupV4) rootGroup;
root.buildChildGroupsRecursive(list); buildChildGroupsRecursive(root, list);
return list; return list;
} }
private static void buildChildGroupsRecursive(PwGroupV4 root, List<PwGroup> list) {
list.add(root);
for ( int i = 0; i < root.numbersOfChildGroups(); i++) {
PwGroupV4 child = (PwGroupV4) root.getChildGroupAt(i);
buildChildGroupsRecursive(child, list);
}
}
@Override @Override
public List<PwGroup> getGrpRoots() { public List<PwGroup> getGrpRoots() {
return rootGroup.childGroups; return rootGroup.getChildGroups();
} }
@Override @Override
public List<PwEntry> getEntries() { public List<PwEntry> getEntries() {
List<PwEntry> list = new ArrayList<PwEntry>(); List<PwEntry> list = new ArrayList<PwEntry>();
PwGroupV4 root = (PwGroupV4) rootGroup; PwGroupV4 root = (PwGroupV4) rootGroup;
root.buildChildEntriesRecursive(list); buildChildEntriesRecursive(root, list);
return list; return list;
} }
private static void buildChildEntriesRecursive(PwGroupV4 root, List<PwEntry> list) {
for ( int i = 0; i < root.numbersOfChildEntries(); i++ ) {
list.add(root.getChildEntryAt(i));
}
for ( int i = 0; i < root.numbersOfChildGroups(); i++ ) {
PwGroupV4 child = (PwGroupV4) root.getChildGroupAt(i);
buildChildEntriesRecursive(child, list);
}
}
@Override @Override
public long getNumRounds() { public long getNumRounds() {
return numKeyEncRounds; return numKeyEncRounds;
@@ -293,7 +315,7 @@ public class PwDatabaseV4 extends PwDatabase {
} }
@Override @Override
public boolean appSettingsEnabled() { public boolean algorithmSettingsEnabled() {
return false; return false;
} }
@@ -344,24 +366,32 @@ public class PwDatabaseV4 extends PwDatabase {
if (getRecycleBin() == null) { if (getRecycleBin() == null) {
// Create recycle bin // Create recycle bin
PwGroupV4 recycleBin = new PwGroupV4(true, true, RECYCLEBIN_NAME, iconFactory.getIcon(PwIconStandard.TRASH_BIN)); PwGroupV4 recycleBin = new PwGroupV4(RECYCLEBIN_NAME, iconFactory.getIcon(PwIconStandard.TRASH_BIN));
recycleBin.enableAutoType = false; recycleBin.setEnableAutoType(false);
recycleBin.enableSearching = false; recycleBin.setEnableSearching(false);
recycleBin.isExpanded = false; recycleBin.setExpanded(false);
addGroupTo(recycleBin, rootGroup); addGroupTo(recycleBin, rootGroup);
recycleBinUUID = recycleBin.uuid; recycleBinUUID = recycleBin.getUUID();
} }
} }
@Override
public boolean isRecycleBinAvailable() {
return true;
}
@Override
public boolean isRecycleBinEnable() {
return recycleBinEnabled;
}
@Override @Override
public boolean canRecycle(PwGroup group) { public boolean canRecycle(PwGroup group) {
if (!recycleBinEnabled) { if (!recycleBinEnabled) {
return false; return false;
} }
PwGroup recycle = getRecycleBin(); PwGroup recycle = getRecycleBin();
return (recycle == null) || (!group.isContainedIn(recycle)); return (recycle == null) || (!group.isContainedIn(recycle));
} }
@@ -370,11 +400,25 @@ public class PwDatabaseV4 extends PwDatabase {
if (!recycleBinEnabled) { if (!recycleBinEnabled) {
return false; return false;
} }
PwGroup parent = entry.getParent(); PwGroup parent = entry.getParent();
return (parent != null) && canRecycle(parent); 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 @Override
public void recycle(PwEntry entry) { public void recycle(PwEntry entry) {
ensureRecycleBin(); ensureRecycleBin();
@@ -390,6 +434,15 @@ public class PwDatabaseV4 extends PwDatabase {
entry.touchLocation(); entry.touchLocation();
} }
@Override
public void undoRecycle(PwGroup group, PwGroup origParent) {
PwGroup recycleBin = getRecycleBin();
removeGroupFrom(group, recycleBin);
addGroupTo(group, origParent);
}
@Override @Override
public void undoRecycle(PwEntry entry, PwGroup origParent) { public void undoRecycle(PwEntry entry, PwGroup origParent) {
@@ -402,15 +455,14 @@ public class PwDatabaseV4 extends PwDatabase {
@Override @Override
public void deleteEntry(PwEntry entry) { public void deleteEntry(PwEntry entry) {
super.deleteEntry(entry); super.deleteEntry(entry);
deletedObjects.add(new PwDeletedObject(entry.getUUID())); deletedObjects.add(new PwDeletedObject(entry.getUUID()));
} }
@Override @Override
public void undoDeleteEntry(PwEntry entry, PwGroup origParent) { public void undoDeleteEntry(PwEntry entry, PwGroup origParent) {
super.undoDeleteEntry(entry, origParent); super.undoDeleteEntry(entry, origParent);
// TODO undo delete entry
deletedObjects.remove(entry); deletedObjects.remove(entry);
} }
@Override @Override
@@ -443,7 +495,7 @@ public class PwDatabaseV4 extends PwDatabase {
public void initNew(String dbPath) { public void initNew(String dbPath) {
String filename = URLUtil.guessFileName(dbPath, null, null); String filename = URLUtil.guessFileName(dbPath, null, null);
rootGroup = new PwGroupV4(true, true, dbNameFromPath(dbPath), iconFactory.getIcon(PwIconStandard.FOLDER)); rootGroup = new PwGroupV4(dbNameFromPath(dbPath), iconFactory.getIcon(PwIconStandard.FOLDER));
groups.put(rootGroup.getId(), rootGroup); groups.put(rootGroup.getId(), rootGroup);
} }
@@ -471,7 +523,7 @@ public class PwDatabaseV4 extends PwDatabase {
return true; return true;
} }
PwGroupV4 g4 = (PwGroupV4) group; PwGroupV4 g4 = (PwGroupV4) group;
if (g4.customData.size() > 0) { if (g4.containsCustomData()) {
hasCustomData = true; hasCustomData = true;
return false; return false;
} }
@@ -491,7 +543,7 @@ public class PwDatabaseV4 extends PwDatabase {
} }
PwEntryV4 e4 = (PwEntryV4)entry; PwEntryV4 e4 = (PwEntryV4)entry;
if (e4.customData.size() > 0) { if (e4.containsCustomData()) {
hasCustomData = true; hasCustomData = true;
return false; return false;
} }

View File

@@ -23,16 +23,8 @@ import android.annotation.SuppressLint;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.TimeZone; import java.util.TimeZone;
@SuppressLint("SimpleDateFormat")
public class PwDatabaseV4XML { 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 ElemDocNode = "KeePassFile";
public static final String ElemMeta = "Meta"; public static final String ElemMeta = "Meta";
public static final String ElemRoot = "Root"; public static final String ElemRoot = "Root";
@@ -134,4 +126,15 @@ public class PwDatabaseV4XML {
public static final String ElemCustomData = "CustomData"; public static final String ElemCustomData = "CustomData";
public static final String ElemStringDictExItem = "Item"; 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,21 +19,22 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import com.keepassdroid.app.App;
import com.keepassdroid.utils.Types;
import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import com.keepassdroid.app.App;
import com.keepassdroid.utils.Types;
/** Converting from the C Date format to the Java data format is /** Converting from the C Date format to the Java data format is
* expensive when done for every record at once. I use this class to * expensive when done for every record at once. I use this class to
* allow lazy conversions between the formats. * allow lazy conversions between the formats.
* @author bpellin * @author bpellin
* *
*/ */
public class PwDate implements Cloneable { public class PwDate implements Cloneable, Serializable {
private static final int DATE_SIZE = 5; private static final int DATE_SIZE = 5;
private boolean cDateBuilt = false; private boolean cDateBuilt = false;
@@ -41,6 +42,56 @@ public class PwDate implements Cloneable {
private Date jDate; private Date jDate;
private byte[] cDate; private byte[] cDate;
public static final Date NEVER_EXPIRE = getNeverExpire();
public static final Date NEVER_EXPIRE_BUG = getNeverExpireBug();
public static final Date DEFAULT_DATE = getDefaultDate();
public static final PwDate PW_NEVER_EXPIRE = new PwDate(NEVER_EXPIRE);
public static final PwDate PW_NEVER_EXPIRE_BUG = new PwDate(NEVER_EXPIRE_BUG);
public static final PwDate DEFAULT_PWDATE = new PwDate(DEFAULT_DATE);
private static Date getDefaultDate() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2004);
cal.set(Calendar.MONTH, Calendar.JANUARY);
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
return cal.getTime();
}
private static Date getNeverExpire() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2999);
cal.set(Calendar.MONTH, 11);
cal.set(Calendar.DAY_OF_MONTH, 28);
cal.set(Calendar.HOUR, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
return cal.getTime();
}
/** This date was was accidentally being written
* out when an entry was supposed to be marked as
* expired. We'll use this to silently correct those
* entries.
* @return
*/
private static Date getNeverExpireBug() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2999);
cal.set(Calendar.MONTH, 11);
cal.set(Calendar.DAY_OF_MONTH, 30);
cal.set(Calendar.HOUR, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
return cal.getTime();
}
public PwDate(byte[] buf, int offset) { public PwDate(byte[] buf, int offset) {
cDate = new byte[DATE_SIZE]; cDate = new byte[DATE_SIZE];
@@ -58,12 +109,13 @@ public class PwDate implements Cloneable {
jDateBuilt = true; jDateBuilt = true;
} }
private PwDate() { public PwDate() {
jDate = new Date();
jDateBuilt = true;
} }
@Override @Override
public Object clone() { public PwDate clone() {
PwDate copy = new PwDate(); PwDate copy = new PwDate();
if ( cDateBuilt ) { if ( cDateBuilt ) {
@@ -81,9 +133,7 @@ public class PwDate implements Cloneable {
return copy; return copy;
} }
public Date getDate() {
public Date getJDate() {
if ( ! jDateBuilt ) { if ( ! jDateBuilt ) {
jDate = readTime(cDate, 0, App.getCalendar()); jDate = readTime(cDate, 0, App.getCalendar());
jDateBuilt = true; jDateBuilt = true;
@@ -101,7 +151,6 @@ public class PwDate implements Cloneable {
return cDate; return cDate;
} }
/** /**
* Unpack date from 5 byte format. The five bytes at 'offset' are unpacked * Unpack date from 5 byte format. The five bytes at 'offset' are unpacked
* to a java.util.Date instance. * to a java.util.Date instance.
@@ -188,7 +237,7 @@ public class PwDate implements Cloneable {
} else if ( cDateBuilt && date.jDateBuilt ) { } else if ( cDateBuilt && date.jDateBuilt ) {
return Arrays.equals(date.getCDate(), cDate); return Arrays.equals(date.getCDate(), cDate);
} else { } else {
return IsSameDate(date.getJDate(), jDate); return IsSameDate(date.getDate(), jDate);
} }
} }

View File

@@ -95,7 +95,6 @@ public class PwDbHeaderV4 extends PwDbHeader {
public byte[] streamStartBytes = new byte[32]; public byte[] streamStartBytes = new byte[32];
public CrsAlgorithm innerRandomStream; public CrsAlgorithm innerRandomStream;
public long version; public long version;
public List<ProtectedBinary> binaries = new ArrayList<ProtectedBinary>();
public PwDbHeaderV4(PwDatabaseV4 d) { public PwDbHeaderV4(PwDatabaseV4 d) {
db = d; db = d;
@@ -194,7 +193,9 @@ public class PwDbHeaderV4 extends PwDbHeader {
if (!db.kdfParameters.kdfUUID.equals(kdfR.uuid)) { if (!db.kdfParameters.kdfUUID.equals(kdfR.uuid)) {
db.kdfParameters = kdfR.getDefaultParameters(); 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; break;
case PwDbHeaderV4Fields.EncryptionIV: case PwDbHeaderV4Fields.EncryptionIV:

View File

@@ -19,36 +19,26 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import java.util.Comparator; import com.keepassdroid.database.iterator.EntrySearchStringIterator;
import java.util.Date; import com.keepassdroid.database.security.ProtectedString;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import com.keepassdroid.database.iterator.EntrySearchStringIterator; public abstract class PwEntry extends PwNode implements Cloneable {
import com.keepassdroid.utils.SprEngine;
public abstract class PwEntry implements Cloneable { private static final String PMS_TAN_ENTRY = "<TAN>";
protected static final String PMS_TAN_ENTRY = "<TAN>"; protected UUID uuid = PwDatabase.UUID_ZERO;
public static class EntryNameComparator implements Comparator<PwEntry> {
public int compare(PwEntry object1, PwEntry object2) { @Override
return object1.getTitle().compareToIgnoreCase(object2.getTitle()); protected void construct() {
} super.construct();
uuid = UUID.randomUUID();
} }
public PwIconStandard icon = PwIconStandard.FIRST;
public PwEntry() {
}
public static PwEntry getInstance(PwGroup parent) { 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) { if (parent instanceof PwGroupV3) {
return new PwEntryV3((PwGroupV3)parent); return new PwEntryV3((PwGroupV3)parent);
} }
@@ -59,86 +49,65 @@ public abstract class PwEntry implements Cloneable {
throw new RuntimeException("Unknown PwGroup instance."); throw new RuntimeException("Unknown PwGroup instance.");
} }
} }
@Override @Override
public Object clone() { public PwEntry clone() {
PwEntry newEntry; PwEntry newEntry;
try { try {
newEntry = (PwEntry) super.clone(); newEntry = (PwEntry) super.clone();
} catch (CloneNotSupportedException e) { } catch (CloneNotSupportedException e) {
assert(false);
throw new RuntimeException("Clone should be supported"); throw new RuntimeException("Clone should be supported");
} }
return newEntry; return newEntry;
} }
public PwEntry clone(boolean deepStrings) {
return (PwEntry) clone();
}
public void assign(PwEntry source) {
icon = source.icon;
}
public abstract UUID getUUID();
public abstract void setUUID(UUID u);
public String getTitle() {
return getTitle(false, null);
}
public String getUsername() {
return getUsername(false, null);
}
public String getPassword() { @Override
return getPassword(false, null); protected void addCloneAttributesToNewEntry(PwEntry newEntry) {
} super.addCloneAttributesToNewEntry(newEntry);
// uuid is clone automatically (IMMUTABLE)
public String getUrl() {
return getUrl(false, null);
}
public String getNotes() {
return getNotes(false, null);
}
public abstract String getTitle(boolean decodeRef, PwDatabase db);
public abstract String getUsername(boolean decodeRef, PwDatabase db);
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);
public abstract void setPassword(String pass, PwDatabase db);
public abstract void setUrl(String url, PwDatabase db);
public abstract void setNotes(String notes, PwDatabase db);
public abstract void setCreationTime(Date create);
public abstract void setLastModificationTime(Date mod);
public abstract void setLastAccessTime(Date access);
public abstract void setExpires(boolean exp);
public abstract void setExpiryTime(Date expires);
public PwIcon getIcon() {
return icon;
}
public void setIcon(PwIconStandard icon) {
this.icon = icon;
} }
@Override
public Type getType() {
return Type.ENTRY;
}
public void assign(PwEntry source) {
super.assign(source);
uuid = source.uuid;
}
public UUID getUUID() {
return uuid;
}
public void setUUID(UUID uuid) {
this.uuid = uuid;
}
public void startToDecodeReference(PwDatabase db) {}
public void endToDecodeReference(PwDatabase db) {}
public abstract String getTitle();
public abstract void setTitle(String title);
public abstract String getUsername();
public abstract void setUsername(String user);
public abstract String getPassword();
public abstract void setPassword(String pass);
public abstract String getUrl();
public abstract void setUrl(String url);
public abstract String getNotes();
public abstract void setNotes(String notes);
public boolean isTan() { private boolean isTan() {
return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0); return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0);
} }
@Override
public String getDisplayTitle() { public String getDisplayTitle() {
if ( isTan() ) { if ( isTan() ) {
return PMS_TAN_ENTRY + " " + getUsername(); return PMS_TAN_ENTRY + " " + getUsername();
@@ -147,18 +116,72 @@ 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
* @return Map of label/value
*/
public Map<String, String> getExtraFields() {
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<>();
}
/**
* If entry contains extra fields
* @return true if there is extra fields
*/
public boolean containsExtraFields() {
return !getExtraProtectedFields().keySet().isEmpty();
}
/**
* 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() { public boolean isMetaStream() {
return false; return false;
} }
/**
* Create a backup of entry
*/
public void createBackup(PwDatabase db) {}
public EntrySearchStringIterator stringIterator() { public EntrySearchStringIterator stringIterator() {
return EntrySearchStringIterator.getInstance(this); return EntrySearchStringIterator.getInstance(this);
} }
public void touch(boolean modified, boolean touchParents) { public void touch(boolean modified, boolean touchParents) {
Date now = new Date(); PwDate now = new PwDate();
setLastAccessTime(now); setLastAccessTime(now);
if (modified) { if (modified) {
@@ -173,10 +196,22 @@ public abstract class PwEntry implements Cloneable {
public void touchLocation() { } public void touchLocation() { }
public abstract void setParent(PwGroup parent);
public boolean isSearchingEnabled() { public boolean isSearchingEnabled() {
return false; 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;
}
} }

View File

@@ -42,15 +42,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
package com.keepassdroid.database; package com.keepassdroid.database;
// PhoneID
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.UUID; import java.util.UUID;
import com.keepassdroid.utils.Types;
/** /**
* Structure containing information about one entry. * Structure containing information about one entry.
@@ -71,138 +65,204 @@ import com.keepassdroid.utils.Types;
* @author Naomaru Itoi <nao@phoneid.org> * @author Naomaru Itoi <nao@phoneid.org>
* @author Bill Zwicky <wrzwicky@pobox.com> * @author Bill Zwicky <wrzwicky@pobox.com>
* @author Dominik Reichl <dominik.reichl@t-online.de> * @author Dominik Reichl <dominik.reichl@t-online.de>
* @author Jeremy Jamet <jeremy.jamet@kunzisoft.com>
*/ */
public class PwEntryV3 extends PwEntry { public class PwEntryV3 extends PwEntry {
public static final Date NEVER_EXPIRE = getNeverExpire();
public static final Date NEVER_EXPIRE_BUG = getNeverExpireBug();
public static final Date DEFAULT_DATE = getDefaultDate();
public static final PwDate PW_NEVER_EXPIRE = new PwDate(NEVER_EXPIRE);
public static final PwDate PW_NEVER_EXPIRE_BUG = new PwDate(NEVER_EXPIRE_BUG);
public static final PwDate DEFAULT_PWDATE = new PwDate(DEFAULT_DATE);
/** Size of byte buffer needed to hold this struct. */ /** Size of byte buffer needed to hold this struct. */
public static final String PMS_ID_BINDESC = "bin-stream"; private static final String PMS_ID_BINDESC = "bin-stream";
public static final String PMS_ID_TITLE = "Meta-Info"; private static final String PMS_ID_TITLE = "Meta-Info";
public static final String PMS_ID_USER = "SYSTEM"; private static final String PMS_ID_USER = "SYSTEM";
public static final String PMS_ID_URL = "$"; private static final String PMS_ID_URL = "$";
// for tree traversing
private PwGroupV3 parent = null;
private int groupId;
private String title;
public int groupId; private String username;
public String username; private byte[] password;
private byte[] password; private String url;
private byte[] uuid; private String additional;
public String title;
public String url;
public String additional;
public PwDate tCreation;
public PwDate tLastMod;
public PwDate tLastAccess;
public PwDate tExpire;
/** A string describing what is in pBinaryData */ /** A string describing what is in pBinaryData */
public String binaryDesc; private String binaryDesc;
private byte[] binaryData; private byte[] binaryData;
private static Date getDefaultDate() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2004);
cal.set(Calendar.MONTH, Calendar.JANUARY);
cal.set(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
return cal.getTime();
}
private static Date getNeverExpire() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2999);
cal.set(Calendar.MONTH, 11);
cal.set(Calendar.DAY_OF_MONTH, 28);
cal.set(Calendar.HOUR, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
return cal.getTime();
}
/** This date was was accidentally being written
* out when an entry was supposed to be marked as
* expired. We'll use this to silently correct those
* entries.
* @return
*/
private static Date getNeverExpireBug() {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, 2999);
cal.set(Calendar.MONTH, 11);
cal.set(Calendar.DAY_OF_MONTH, 30);
cal.set(Calendar.HOUR, 23);
cal.set(Calendar.MINUTE, 59);
cal.set(Calendar.SECOND, 59);
return cal.getTime();
}
public static boolean IsNever(Date date) {
return PwDate.IsSameDate(NEVER_EXPIRE, date);
}
// for tree traversing
public PwGroupV3 parent = null;
public PwEntryV3() { public PwEntryV3() {
super(); super();
} }
/*
public PwEntryV3(PwEntryV3 source) {
assign(source);
}
*/
public PwEntryV3(PwGroupV3 p) { public PwEntryV3(PwGroupV3 p) {
this(p, true, true); construct();
}
public PwEntryV3(PwGroupV3 p, boolean initId, boolean initDates) {
parent = p; parent = p;
groupId = ((PwGroupIdV3)parent.getId()).getId(); groupId = ((PwGroupIdV3)parent.getId()).getId(); // TODO remove
if (initId) {
Random random = new Random();
uuid = new byte[16];
random.nextBytes(uuid);
}
if (initDates) {
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
tCreation = new PwDate(now);
tLastAccess = new PwDate(now);
tLastMod = new PwDate(now);
tExpire = new PwDate(NEVER_EXPIRE);
}
} }
@Override
public void assign(PwEntry source) {
if ( ! (source instanceof PwEntryV3) ) {
throw new RuntimeException("DB version mix");
}
super.assign(source);
PwEntryV3 src = (PwEntryV3) source;
parent = src.parent;
groupId = src.groupId;
title = src.title;
username = src.username;
int passLen = src.password.length;
password = new byte[passLen];
System.arraycopy(src.password, 0, password, 0, passLen);
url = src.url;
additional = src.additional;
binaryDesc = src.binaryDesc;
if ( src.binaryData != null ) {
int descLen = src.binaryData.length;
binaryData = new byte[descLen];
System.arraycopy(src.binaryData, 0, binaryData, 0, descLen);
}
}
@Override
public PwEntryV3 clone() {
PwEntryV3 newEntry = (PwEntryV3) super.clone();
// Attributes in parent
addCloneAttributesToNewEntry(newEntry);
// Attributes here
// newEntry.parent stay the same in copy
// newEntry.groupId stay the same in copy
// newEntry.title stay the same in copy
// newEntry.username stay the same in copy
if (password != null) {
int passLen = password.length;
password = new byte[passLen];
System.arraycopy(password, 0, newEntry.password, 0, passLen);
}
// newEntry.url stay the same in copy
// newEntry.additional stay the same in copy
// newEntry.binaryDesc stay the same in copy
if ( binaryData != null ) {
int descLen = binaryData.length;
newEntry.binaryData = new byte[descLen];
System.arraycopy(binaryData, 0, newEntry.binaryData, 0, descLen);
}
return newEntry;
}
@Override
public PwGroupV3 getParent() {
return parent;
}
@Override
public void setParent(PwGroup parent) {
this.parent = (PwGroupV3) parent;
}
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) {
this.groupId = groupId;
}
@Override
public String getUsername() {
if (username == null) {
return "";
}
return username;
}
@Override
public void setUsername(String user) {
username = user;
}
@Override
public String getTitle() {
return title;
}
@Override
public void setTitle(String title) {
this.title = title;
}
@Override
public String getNotes() {
return additional;
}
@Override
public void setNotes(String notes) {
additional = notes;
}
@Override
public String getUrl() {
return url;
}
@Override
public void setUrl(String url) {
this.url = url;
}
public void populateBlankFields(PwDatabaseV3 db) {
// TODO verify and remove
if (icon == null) {
icon = db.iconFactory.getIcon(1);
}
if (username == null) {
username = "";
}
if (password == null) {
password = new byte[0];
}
if (uuid == null) {
uuid = UUID.randomUUID();
}
if (title == null) {
title = "";
}
if (url == null) {
url = "";
}
if (additional == null) {
additional = "";
}
if (binaryDesc == null) {
binaryDesc = "";
}
if (binaryData == null) {
binaryData = new byte[0];
}
}
/** /**
* @return the actual password byte array. * @return the actual password byte array.
*/ */
@Override @Override
public String getPassword(boolean decodeRef, PwDatabase db) { public String getPassword() {
if (password == null) { if (password == null) {
return ""; return "";
} }
return new String(password); return new String(password);
} }
@@ -210,7 +270,6 @@ public class PwEntryV3 extends PwEntry {
return password; return password;
} }
/** /**
* fill byte array * fill byte array
*/ */
@@ -231,10 +290,8 @@ public class PwEntryV3 extends PwEntry {
System.arraycopy( buf, offset, password, 0, len ); System.arraycopy( buf, offset, password, 0, len );
} }
@Override @Override
public void setPassword(String pass, PwDatabase db) { public void setPassword(String pass) {
byte[] password; byte[] password;
try { try {
password = pass.getBytes("UTF-8"); password = pass.getBytes("UTF-8");
@@ -253,8 +310,6 @@ public class PwEntryV3 extends PwEntry {
return binaryData; return binaryData;
} }
/** Securely erase old data before copying new. */ /** Securely erase old data before copying new. */
public void setBinaryData( byte[] buf, int offset, int len ) { public void setBinaryData( byte[] buf, int offset, int len ) {
if( binaryData != null ) { if( binaryData != null ) {
@@ -265,6 +320,14 @@ public class PwEntryV3 extends PwEntry {
System.arraycopy( buf, offset, binaryData, 0, len ); System.arraycopy( buf, offset, binaryData, 0, len );
} }
public String getBinaryDesc() {
return binaryDesc;
}
public void setBinaryDesc(String binaryDesc) {
this.binaryDesc = binaryDesc;
}
// Determine if this is a MetaStream entry // Determine if this is a MetaStream entry
@Override @Override
public boolean isMetaStream() { public boolean isMetaStream() {
@@ -281,250 +344,4 @@ public class PwEntryV3 extends PwEntry {
return true; return true;
} }
@Override
public void assign(PwEntry source) {
if ( ! (source instanceof PwEntryV3) ) {
throw new RuntimeException("DB version mix");
}
super.assign(source);
PwEntryV3 src = (PwEntryV3) source;
assign(src);
}
private void assign(PwEntryV3 source) {
title = source.title;
url = source.url;
groupId = source.groupId;
username = source.username;
additional = source.additional;
uuid = source.uuid;
int passLen = source.password.length;
password = new byte[passLen];
System.arraycopy(source.password, 0, password, 0, passLen);
tCreation = (PwDate) source.tCreation.clone();
tLastMod = (PwDate) source.tLastMod.clone();
tLastAccess = (PwDate) source.tLastAccess.clone();
tExpire = (PwDate) source.tExpire.clone();
binaryDesc = source.binaryDesc;
if ( source.binaryData != null ) {
int descLen = source.binaryData.length;
binaryData = new byte[descLen];
System.arraycopy(source.binaryData, 0, binaryData, 0, descLen);
}
parent = source.parent;
}
@Override
public Object clone() {
PwEntryV3 newEntry = (PwEntryV3) super.clone();
if (password != null) {
int passLen = password.length;
password = new byte[passLen];
System.arraycopy(password, 0, newEntry.password, 0, passLen);
}
newEntry.tCreation = (PwDate) tCreation.clone();
newEntry.tLastMod = (PwDate) tLastMod.clone();
newEntry.tLastAccess = (PwDate) tLastAccess.clone();
newEntry.tExpire = (PwDate) tExpire.clone();
newEntry.binaryDesc = binaryDesc;
if ( binaryData != null ) {
int descLen = binaryData.length;
newEntry.binaryData = new byte[descLen];
System.arraycopy(binaryData, 0, newEntry.binaryData, 0, descLen);
}
newEntry.parent = parent;
return newEntry;
}
@Override
public Date getLastAccessTime() {
return tLastAccess.getJDate();
}
@Override
public Date getCreationTime() {
return tCreation.getJDate();
}
@Override
public Date getExpiryTime() {
return tExpire.getJDate();
}
@Override
public Date getLastModificationTime() {
return tLastMod.getJDate();
}
@Override
public void setCreationTime(Date create) {
tCreation = new PwDate(create);
}
@Override
public void setLastModificationTime(Date mod) {
tLastMod = new PwDate(mod);
}
@Override
public void setLastAccessTime(Date access) {
tLastAccess = new PwDate(access);
}
@Override
public void setExpires(boolean expires) {
if (!expires) {
tExpire = PW_NEVER_EXPIRE;
}
}
@Override
public void setExpiryTime(Date expires) {
tExpire = new PwDate(expires);
}
@Override
public PwGroupV3 getParent() {
return parent;
}
@Override
public UUID getUUID() {
return Types.bytestoUUID(uuid);
}
@Override
public void setUUID(UUID u) {
uuid = Types.UUIDtoBytes(u);
}
@Override
public String getUsername(boolean decodeRef, PwDatabase db) {
if (username == null) {
return "";
}
return username;
}
@Override
public void setUsername(String user, PwDatabase db) {
username = user;
}
@Override
public String getTitle(boolean decodeRef, PwDatabase db) {
return title;
}
@Override
public void setTitle(String title, PwDatabase db) {
this.title = title;
}
@Override
public String getNotes(boolean decodeRef, PwDatabase db) {
return additional;
}
@Override
public void setNotes(String notes, PwDatabase db) {
additional = notes;
}
@Override
public String getUrl(boolean decodeRef, PwDatabase db) {
return url;
}
@Override
public void setUrl(String url, PwDatabase db) {
this.url = url;
}
@Override
public boolean expires() {
return ! IsNever(tExpire.getJDate());
}
public void populateBlankFields(PwDatabaseV3 db) {
if (icon == null) {
icon = db.iconFactory.getIcon(1);
}
if (username == null) {
username = "";
}
if (password == null) {
password = new byte[0];
}
if (uuid == null) {
uuid = Types.UUIDtoBytes(UUID.randomUUID());
}
if (title == null) {
title = "";
}
if (url == null) {
url = "";
}
if (additional == null) {
additional = "";
}
if (tCreation == null) {
tCreation = DEFAULT_PWDATE;
}
if (tLastMod == null) {
tLastMod = DEFAULT_PWDATE;
}
if (tLastAccess == null) {
tLastAccess = DEFAULT_PWDATE;
}
if (tExpire == null) {
tExpire = PW_NEVER_EXPIRE;
}
if (binaryDesc == null) {
binaryDesc = "";
}
if (binaryData == null) {
binaryData = new byte[0];
}
}
@Override
public void setParent(PwGroup parent) {
this.parent = (PwGroupV3) parent;
}
} }

View File

@@ -19,313 +19,225 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
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.ProtectedBinary;
import com.keepassdroid.database.security.ProtectedString; import com.keepassdroid.database.security.ProtectedString;
import com.keepassdroid.utils.SprEngine; import com.keepassdroid.utils.SprEngineV4;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class PwEntryV4 extends PwEntry implements ITimeLogger { public class PwEntryV4 extends PwEntry implements ITimeLogger {
public static final String STR_TITLE = "Title"; public static final String STR_TITLE = "Title";
public static final String STR_USERNAME = "UserName"; public static final String STR_USERNAME = "UserName";
public static final String STR_PASSWORD = "Password"; public static final String STR_PASSWORD = "Password";
public static final String STR_URL = "URL"; public static final String STR_URL = "URL";
public static final String STR_NOTES = "Notes"; public static final String STR_NOTES = "Notes";
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>();
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>();
private Date parentGroupLastMod = PwDatabaseV4.DEFAULT_NOW;
private Date creation = PwDatabaseV4.DEFAULT_NOW;
private Date lastMod = PwDatabaseV4.DEFAULT_NOW;
private Date lastAccess = PwDatabaseV4.DEFAULT_NOW;
private Date expireDate = PwDatabaseV4.DEFAULT_NOW;
private boolean expires = false;
private long usageCount = 0;
public String url = "";
public String additional = "";
public String tags = "";
public Map<String, String> customData = new HashMap<String, String>();
public class AutoType implements Cloneable { // To decode each field not serializable
private static final long OBF_OPT_NONE = 0; private transient PwDatabase mDatabase = null;
private transient boolean mDecodeRef = false;
public boolean enabled = true;
public long obfuscationOptions = OBF_OPT_NONE;
public String defaultSequence = "";
private HashMap<String, String> windowSeqPairs = new HashMap<String, String>();
@SuppressWarnings("unchecked")
public Object clone() {
AutoType auto;
try {
auto = (AutoType) super.clone();
}
catch (CloneNotSupportedException e) {
assert(false);
throw new RuntimeException(e);
}
auto.windowSeqPairs = (HashMap<String, String>) windowSeqPairs.clone();
return auto;
}
public void put(String key, String value) {
windowSeqPairs.put(key, value);
}
public Set<Entry<String, String>> entrySet() {
return windowSeqPairs.entrySet();
}
}
private PwGroupV4 parent;
private PwIconCustom customIcon = PwIconCustom.ZERO;
private long usageCount = 0;
private PwDate parentGroupLastMod = new PwDate();
private Map<String, String> customData = new HashMap<>();
private HashMap<String, ProtectedString> fields = new HashMap<>();
private HashMap<String, ProtectedBinary> binaries = new HashMap<>();
private String foregroundColor = "";
private String backgroupColor = "";
private String overrideURL = "";
private AutoType autoType = new AutoType();
private ArrayList<PwEntryV4> history = new ArrayList<>();
private String url = "";
private String additional = "";
private String tags = "";
public PwEntryV4() { public PwEntryV4() {
super();
} }
public PwEntryV4(PwGroupV4 p) { public PwEntryV4(PwGroupV4 p) {
this(p, true, true); construct();
}
public PwEntryV4(PwGroupV4 p, boolean initId, boolean initDates) {
parent = p; parent = p;
if (initId) {
uuid = UUID.randomUUID();
}
if (initDates) {
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
creation = now;
lastAccess = now;
lastMod = now;
expires = false;
}
} }
@SuppressWarnings("unchecked") @Override
public void assign(PwEntry source) {
if ( ! (source instanceof PwEntryV4) ) {
throw new RuntimeException("DB version mix.");
}
super.assign(source);
PwEntryV4 src = (PwEntryV4) source;
parent = src.parent;
customIcon = src.customIcon;
usageCount = src.usageCount;
parentGroupLastMod = src.parentGroupLastMod;
// TODO customData
fields = src.fields;
binaries = src.binaries;
foregroundColor = src.foregroundColor;
backgroupColor = src.backgroupColor;
overrideURL = src.overrideURL;
autoType = src.autoType;
history = src.history;
url = src.url;
additional = src.additional;
tags = src.tags;
}
@SuppressWarnings("unchecked")
@Override
public PwEntryV4 clone() {
PwEntryV4 newEntry = (PwEntryV4) super.clone();
// Attributes in parent
addCloneAttributesToNewEntry(newEntry);
// Attributes here
// newEntry.parent stay the same in copy
newEntry.customIcon = new PwIconCustom(this.customIcon);
// newEntry.usageCount stay the same in copy
newEntry.parentGroupLastMod = this.parentGroupLastMod.clone();
// TODO customData make copy from hashmap
newEntry.fields = (HashMap<String, ProtectedString>) this.fields.clone();
newEntry.binaries = (HashMap<String, ProtectedBinary>) this.binaries.clone();
// newEntry.foregroundColor stay the same in copy
// newEntry.backgroupColor stay the same in copy
// newEntry.overrideURL stay the same in copy
newEntry.autoType = autoType.clone();
newEntry.history = (ArrayList<PwEntryV4>) this.history.clone();
// newEntry.url stay the same in copy
// newEntry.additional stay the same in copy
// newEntry.tags stay the same in copy
return newEntry;
}
@Override @Override
public PwEntry clone(boolean deepStrings) { public void startToDecodeReference(PwDatabase db) {
PwEntryV4 entry = (PwEntryV4) super.clone(deepStrings); this.mDatabase = db;
this.mDecodeRef = true;
if (deepStrings) {
entry.strings = (HashMap<String, ProtectedString>) strings.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 @Override
public void assign(PwEntry source) { public void endToDecodeReference(PwDatabase db) {
this.mDatabase = null;
if ( ! (source instanceof PwEntryV4) ) { this.mDecodeRef = false;
throw new RuntimeException("DB version mix.");
}
super.assign(source);
PwEntryV4 src = (PwEntryV4) source;
assign(src);
}
private void assign(PwEntryV4 source) {
parent = source.parent;
uuid = source.uuid;
strings = source.strings;
binaries = source.binaries;
customIcon = source.customIcon;
foregroundColor = source.foregroundColor;
backgroupColor = source.backgroupColor;
overrideURL = source.overrideURL;
autoType = source.autoType;
history = source.history;
parentGroupLastMod = source.parentGroupLastMod;
creation = source.creation;
lastMod = source.lastMod;
lastAccess = source.lastAccess;
expireDate = source.expireDate;
expires = source.expires;
usageCount = source.usageCount;
url = source.url;
additional = source.additional;
} }
@Override private String decodeRefKey(boolean decodeRef, String key) {
public Object clone() {
PwEntryV4 newEntry = (PwEntryV4) super.clone();
return newEntry;
}
private String decodeRefKey(boolean decodeRef, String key, PwDatabase db) {
String text = getString(key); String text = getString(key);
if (decodeRef) { if (decodeRef) {
text = decodeRef(text, db); text = decodeRef(text, mDatabase);
} }
return text; return text;
} }
private String decodeRef(String text, PwDatabase db) { private String decodeRef(String text, PwDatabase db) {
if (db == null) { return text; } if (db == null) { return text; }
SprEngineV4 spr = new SprEngineV4();
SprEngine spr = SprEngine.getInstance(db);
return spr.compile(text, this, db); return spr.compile(text, this, db);
} }
@Override @Override
public String getUsername(boolean decodeRef, PwDatabase db) { public String getUsername() {
return decodeRefKey(decodeRef, STR_USERNAME, db); return decodeRefKey(mDecodeRef, STR_USERNAME);
} }
@Override @Override
public String getTitle(boolean decodeRef, PwDatabase db) { public String getTitle() {
return decodeRefKey(decodeRef, STR_TITLE, db); return decodeRefKey(mDecodeRef, STR_TITLE);
} }
@Override @Override
public String getPassword(boolean decodeRef, PwDatabase db) { public String getPassword() {
return decodeRefKey(decodeRef, STR_PASSWORD, db); return decodeRefKey(mDecodeRef, STR_PASSWORD);
} }
@Override @Override
public Date getLastAccessTime() { public void setTitle(String title) {
return lastAccess; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
}
@Override
public Date getCreationTime() {
return creation;
}
@Override
public Date getExpiryTime() {
return expireDate;
}
@Override
public Date getLastModificationTime() {
return lastMod;
}
@Override
public void setTitle(String title, PwDatabase d) {
PwDatabaseV4 db = (PwDatabaseV4) d;
boolean protect = db.memoryProtection.protectTitle; boolean protect = db.memoryProtection.protectTitle;
setString(STR_TITLE, title, protect); setString(STR_TITLE, title, protect);
} }
@Override @Override
public void setUsername(String user, PwDatabase d) { public void setUsername(String user) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectUserName; boolean protect = db.memoryProtection.protectUserName;
setString(STR_USERNAME, user, protect); setString(STR_USERNAME, user, protect);
} }
@Override @Override
public void setPassword(String pass, PwDatabase d) { public void setPassword(String pass) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectPassword; boolean protect = db.memoryProtection.protectPassword;
setString(STR_PASSWORD, pass, protect); setString(STR_PASSWORD, pass, protect);
} }
@Override @Override
public void setUrl(String url, PwDatabase d) { public void setUrl(String url) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectUrl; boolean protect = db.memoryProtection.protectUrl;
setString(STR_URL, url, protect); setString(STR_URL, url, protect);
} }
@Override @Override
public void setNotes(String notes, PwDatabase d) { public void setNotes(String notes) {
PwDatabaseV4 db = (PwDatabaseV4) d; PwDatabaseV4 db = (PwDatabaseV4) mDatabase;
boolean protect = db.memoryProtection.protectNotes; boolean protect = db.memoryProtection.protectNotes;
setString(STR_NOTES, notes, protect); setString(STR_NOTES, notes, protect);
} }
public void setCreationTime(Date date) {
creation = date;
}
public void setExpiryTime(Date date) {
expireDate = date;
}
public void setLastAccessTime(Date date) {
lastAccess = date;
}
public void setLastModificationTime(Date date) {
lastMod = date;
}
@Override @Override
public PwGroupV4 getParent() { public PwGroupV4 getParent() {
return parent; return parent;
} }
@Override @Override
public UUID getUUID() { public void setParent(PwGroup parent) {
return uuid; this.parent = (PwGroupV4) parent;
} }
@Override
public void setUUID(UUID u) {
uuid = u;
}
public String getString(String key) { public String getString(String key) {
ProtectedString value = strings.get(key); ProtectedString value = fields.get(key);
if ( value == null ) return new String(""); if ( value == null ) return "";
return value.toString(); return value.toString();
} }
public void setString(String key, String value, boolean protect) { public void setString(String key, String value, boolean protect) {
ProtectedString ps = new ProtectedString(protect, value); ProtectedString ps = new ProtectedString(protect, value);
strings.put(key, ps); fields.put(key, ps);
} }
public Date getLocationChanged() { public PwIconCustom getCustomIcon() {
return customIcon;
}
public void setCustomIcon(PwIconCustom icon) {
this.customIcon = icon;
}
public PwDate getLocationChanged() {
return parentGroupLastMod; return parentGroupLastMod;
} }
@@ -333,112 +245,179 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
return usageCount; return usageCount;
} }
public void setLocationChanged(Date date) { public void setLocationChanged(PwDate date) {
parentGroupLastMod = date; parentGroupLastMod = date;
} }
public void setUsageCount(long count) { public void setUsageCount(long count) {
usageCount = count; usageCount = count;
} }
@Override
public boolean expires() {
return expires;
}
public void setExpires(boolean exp) { @Override
expires = exp; public String getNotes() {
return decodeRefKey(mDecodeRef, STR_NOTES);
} }
@Override @Override
public String getNotes(boolean decodeRef, PwDatabase db) { public String getUrl() {
return decodeRefKey(decodeRef, STR_NOTES, db); return decodeRefKey(mDecodeRef, STR_URL);
}
@Override
public String getUrl(boolean decodeRef, PwDatabase db) {
return decodeRefKey(decodeRef, STR_URL, db);
} }
@Override @Override
public PwIcon getIcon() { public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabaseV4.UUID_ZERO)) { if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
return super.getIcon(); return super.getIcon();
} else { } else {
return customIcon; return customIcon;
} }
} }
public static boolean IsStandardString(String key) { @Override
return key.equals(STR_TITLE) || key.equals(STR_USERNAME) public boolean allowExtraFields() {
|| key.equals(STR_PASSWORD) || key.equals(STR_URL) return true;
|| key.equals(STR_NOTES);
} }
public void createBackup(PwDatabaseV4 db) { public Map<String, ProtectedString> getFields() {
PwEntryV4 copy = cloneDeep(); return fields;
copy.history = new ArrayList<PwEntryV4>(); }
history.add(copy);
@Override
if (db != null) maintainBackups(db); public Map<String, ProtectedString> getExtraProtectedFields() {
} Map<String, ProtectedString> protectedFields = super.getExtraProtectedFields();
if (fields.size() > 0) {
private boolean maintainBackups(PwDatabaseV4 db) { for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
boolean deleted = false; String key = pair.getKey();
if (!PwEntryV4.isStandardField(key)) {
int maxItems = db.historyMaxItems; protectedFields.put(key, pair.getValue());
if (maxItems >= 0) { }
while (history.size() > maxItems) { }
removeOldestBackup(); }
deleted = true; return protectedFields;
} }
}
@Override
long maxSize = db.historyMaxSize; public Map<String, String> getExtraFields() {
if (maxSize >= 0) { Map<String, String> extraFields = super.getExtraFields();
while(true) { SprEngineV4 spr = new SprEngineV4();
long histSize = 0; // Display custom fields
for (PwEntryV4 entry : history) { if (fields.size() > 0) {
histSize += entry.getSize(); for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
} String key = pair.getKey();
// TODO Add hidden style for protection field
if (histSize > maxSize) { if (!PwEntryV4.isStandardField(key)) {
removeOldestBackup(); extraFields.put(key, spr.compile(pair.getValue().toString(), this, mDatabase));
deleted = true;
} else {
break;
} }
} }
} }
return extraFields;
return deleted;
} }
private void removeOldestBackup() { private static boolean isStandardField(String key) {
Date min = null; return key.equals(STR_TITLE) || key.equals(STR_USERNAME)
int index = -1; || key.equals(STR_PASSWORD) || key.equals(STR_URL)
|| key.equals(STR_NOTES);
for (int i = 0; i < history.size(); i++) { }
PwEntry entry = history.get(i);
Date lastMod = entry.getLastModificationTime(); public void addField(String label, ProtectedString value) {
if ((min == null) || lastMod.before(min)) { fields.put(label, value);
index = i; }
min = lastMod;
} @Override
} public void removeExtraFields() {
Iterator<Entry<String, ProtectedString>> iter = fields.entrySet().iterator();
if (index != -1) { while (iter.hasNext()) {
history.remove(index); 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 HashMap<String, ProtectedBinary> getBinaries() {
return binaries;
}
public void putProtectedBinary(String key, ProtectedBinary value) {
binaries.put(key, value);
}
public String getForegroundColor() {
return foregroundColor;
}
public void setForegroundColor(String color) {
this.foregroundColor = color;
}
public String getBackgroupColor() {
return backgroupColor;
}
public void setBackgroupColor(String color) {
this.backgroupColor = color;
}
public String getOverrideURL() {
return overrideURL;
}
public void setOverrideURL(String overrideURL) {
this.overrideURL = overrideURL;
}
public AutoType getAutoType() {
return autoType;
}
public void setAutoType(AutoType autoType) {
this.autoType = autoType;
}
public ArrayList<PwEntryV4> getHistory() {
return history;
}
public void setHistory(ArrayList<PwEntryV4> history) {
this.history = history;
}
public void addToHistory(PwEntryV4 entry) {
history.add(entry);
}
public int sizeOfHistory() {
return history.size();
}
public String getAdditional() {
return additional;
}
public void setAdditional(String additional) {
this.additional = additional;
}
public String getTags() {
return tags;
}
public void setTags(String tags) {
this.tags = tags;
}
public void putCustomData(String key, String value) {
customData.put(key, value);
}
public boolean containsCustomData() {
return customData.size() > 0;
}
private static final long FIXED_LENGTH_SIZE = 128; // Approximate fixed length size
public long getSize() { public long getSize() {
long size = FIXED_LENGTH_SIZE; 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.getKey().length();
size += pair.getValue().length(); size += pair.getValue().length();
} }
@@ -464,6 +443,68 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
return size; return size;
} }
@Override
public void createBackup(PwDatabase db) {
super.createBackup(db);
PwEntryV4 copy = clone();
copy.history = new ArrayList<>();
history.add(copy);
if (db != null)
if (db instanceof PwDatabaseV4)
maintainBackups((PwDatabaseV4) db);
}
private boolean maintainBackups(PwDatabaseV4 db) {
boolean deleted = false;
int maxItems = db.historyMaxItems;
if (maxItems >= 0) {
while (history.size() > maxItems) {
removeOldestBackup();
deleted = true;
}
}
long maxSize = db.historyMaxSize;
if (maxSize >= 0) {
while(true) {
long histSize = 0;
for (PwEntryV4 entry : history) {
histSize += entry.getSize();
}
if (histSize > maxSize) {
removeOldestBackup();
deleted = true;
} else {
break;
}
}
}
return deleted;
}
private void removeOldestBackup() {
Date min = null;
int index = -1;
for (int i = 0; i < history.size(); i++) {
PwEntry entry = history.get(i);
Date lastMod = entry.getLastModificationTime().getDate();
if ((min == null) || lastMod.before(min)) {
index = i;
min = lastMod;
}
}
if (index != -1) {
history.remove(index);
}
}
@Override @Override
public void touch(boolean modified, boolean touchParents) { public void touch(boolean modified, boolean touchParents) {
super.touch(modified, touchParents); super.touch(modified, touchParents);
@@ -473,19 +514,13 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
@Override @Override
public void touchLocation() { public void touchLocation() {
parentGroupLastMod = new Date(); parentGroupLastMod = new PwDate();
}
@Override
public void setParent(PwGroup parent) {
this.parent = (PwGroupV4) parent;
} }
public boolean isSearchingEnabled() { public boolean isSearchingEnabled() {
if (parent != null) { if (parent != null) {
return parent.isSearchEnabled(); return parent.isSearchEnabled();
} }
return PwGroupV4.DEFAULT_SEARCHING_ENABLED; return PwGroupV4.DEFAULT_SEARCHING_ENABLED;
} }
} }

View File

@@ -19,75 +19,126 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import com.keepassdroid.utils.StrUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date;
import java.util.List; import java.util.List;
import com.keepassdroid.utils.StrUtil; public abstract class PwGroup extends PwNode {
public abstract class PwGroup { protected String name = "";
public List<PwGroup> childGroups = new ArrayList<PwGroup>();
public List<PwEntry> childEntries = new ArrayList<PwEntry>(); protected List<PwGroup> childGroups = new ArrayList<>();
public String name = ""; protected List<PwEntry> childEntries = new ArrayList<>();
public PwIconStandard icon;
public void initNewGroup(String nm, PwGroupId newId) {
setId(newId);
name = nm;
}
public List<PwGroup> getChildGroups() {
return childGroups;
}
public List<PwEntry> getChildEntries() {
return childEntries;
}
public void setGroups(List<PwGroup> groups) {
childGroups = groups;
}
public void setEntries(List<PwEntry> entries) {
childEntries = entries;
}
public void addChildGroup(PwGroup group) {
this.childGroups.add(group);
}
public void addChildEntry(PwEntry entry) {
this.childEntries.add(entry);
}
// Todo parameter type
public PwGroup getChildGroupAt(int number) {
return this.childGroups.get(number);
}
public PwEntry getChildEntryAt(int number) {
return this.childEntries.get(number);
}
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() {
List<PwNode> children = new ArrayList<>();
children.addAll(childGroups);
for(PwEntry child : childEntries) {
if (!child.isMetaStream())
children.add(child);
}
return children;
}
public boolean isContainedIn(PwGroup container) {
PwGroup cur = this;
while (cur != null) {
if (cur == container) {
return true;
}
cur = cur.getParent();
}
return false;
}
public abstract PwGroup getParent();
public abstract void setParent(PwGroup parent);
public abstract PwGroupId getId(); public abstract PwGroupId getId();
public abstract void setId(PwGroupId id); public abstract void setId(PwGroupId id);
public abstract String getName(); @Override
public String getDisplayTitle() {
public abstract Date getLastMod(); return getName();
}
public PwIcon getIcon() {
return icon;
}
public void sortGroupsByName() { public String getName() {
Collections.sort(childGroups, new GroupNameComparator()); return name;
} }
public static class GroupNameComparator implements Comparator<PwGroup> { public void setName(String name) {
this.name = name;
}
public int compare(PwGroup object1, PwGroup object2) { public boolean allowAddEntryIfIsRoot() {
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();
}
return false; return false;
} }
public void touch(boolean modified, boolean touchParents) { public void touch(boolean modified, boolean touchParents) {
Date now = new Date(); PwDate now = new PwDate();
setLastAccessTime(now); setLastAccessTime(now);
if (modified) { if (modified) {
@@ -100,7 +151,6 @@ public abstract class PwGroup {
} }
} }
public void searchEntries(SearchParameters sp, List<PwEntry> listStorage) { public void searchEntries(SearchParameters sp, List<PwEntry> listStorage) {
if (sp == null) { return; } if (sp == null) { return; }
if (listStorage == null) { return; } if (listStorage == null) { return; }
@@ -147,7 +197,6 @@ public abstract class PwGroup {
complement.add(entry); complement.add(entry);
} }
} }
pg = complement; pg = complement;
} }
else { else {
@@ -181,19 +230,30 @@ public abstract class PwGroup {
if (entryHandler != null) { if (entryHandler != null) {
for (PwEntry entry : childEntries) { for (PwEntry entry : childEntries) {
if (!entryHandler.operate(entry)) return false; if (!entryHandler.operate(entry)) return false;
} }
} }
for (PwGroup group : childGroups) { for (PwGroup group : childGroups) {
if ((groupHandler != null) && !groupHandler.operate(group)) return false; if ((groupHandler != null) && !groupHandler.operate(group)) return false;
group.preOrderTraverseTree(groupHandler, entryHandler); group.preOrderTraverseTree(groupHandler, entryHandler);
} }
return true; 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;
}
} }

View File

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

View File

@@ -32,20 +32,17 @@ public class PwGroupIdV3 extends PwGroupId {
if ( ! (compare instanceof PwGroupIdV3) ) { if ( ! (compare instanceof PwGroupIdV3) ) {
return false; return false;
} }
PwGroupIdV3 cmp = (PwGroupIdV3) compare; PwGroupIdV3 cmp = (PwGroupIdV3) compare;
return id == cmp.id; return id == cmp.id;
} }
@Override @Override
public int hashCode() { public int hashCode() {
Integer i = Integer.valueOf(id); Integer i = id;
return i.hashCode(); return i.hashCode();
} }
public int getId() { public int getId() {
return id; return id;
} }
} }

View File

@@ -14,9 +14,7 @@ public class PwGroupIdV4 extends PwGroupId {
if ( ! (id instanceof PwGroupIdV4) ) { if ( ! (id instanceof PwGroupIdV4) ) {
return false; return false;
} }
PwGroupIdV4 v4 = (PwGroupIdV4) id; PwGroupIdV4 v4 = (PwGroupIdV4) id;
return uuid.equals(v4.uuid); return uuid.equals(v4.uuid);
} }
@@ -28,5 +26,4 @@ public class PwGroupIdV4 extends PwGroupId {
public UUID getId() { public UUID getId() {
return uuid; return uuid;
} }
} }

View File

@@ -1,15 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
This file was derived from
Copyright 2007 Naomaru Itoi <nao@phoneid.org>
This file was derived from
Java clone of KeePass - A KeePass file viewer for Java
Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
* *
* This file is part of KeePass DX. * This file is part of KeePass DX.
* *
@@ -30,12 +20,6 @@ Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
package com.keepassdroid.database; package com.keepassdroid.database;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/** /**
* @author Brian Pellin <bpellin@gmail.com> * @author Brian Pellin <bpellin@gmail.com>
* @author Naomaru Itoi <nao@phoneid.org> * @author Naomaru Itoi <nao@phoneid.org>
@@ -43,42 +27,47 @@ import java.util.List;
* @author Dominik Reichl <dominik.reichl@t-online.de> * @author Dominik Reichl <dominik.reichl@t-online.de>
*/ */
public class PwGroupV3 extends PwGroup { public class PwGroupV3 extends PwGroup {
public PwGroupV3() {
}
public String toString() {
return name;
}
public static final Date NEVER_EXPIRE = PwEntryV3.NEVER_EXPIRE;
/** Size of byte buffer needed to hold this struct. */
public static final int BUF_SIZE = 124;
// for tree traversing // for tree traversing
public PwGroupV3 parent = null; private PwGroupV3 parent = null;
private int groupId;
public int groupId; private int level = 0; // short
public PwDate tCreation;
public PwDate tLastMod;
public PwDate tLastAccess;
public PwDate tExpire;
public int level; // short
/** Used by KeePass internally, don't use */ /** Used by KeePass internally, don't use */
public int flags; private int flags;
public void setGroups(List<PwGroup> groups) { public PwGroupV3() {
childGroups = groups; super();
} }
@Override @Override
public PwGroup getParent() { public PwGroup getParent() {
return parent; return parent;
} }
@Override
public void setParent(PwGroup prt) {
parent = (PwGroupV3) prt;
level = parent.getLevel() + 1;
}
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) {
this.groupId = groupId;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
@Override @Override
public PwGroupId getId() { public PwGroupId getId() {
return new PwGroupIdV3(groupId); return new PwGroupIdV3(groupId);
@@ -90,69 +79,27 @@ public class PwGroupV3 extends PwGroup {
groupId = id3.getId(); groupId = id3.getId();
} }
@Override public int getFlags() {
public String getName() { return flags;
return name; }
}
public void setFlags(int flags) {
this.flags = flags;
}
@Override @Override
public Date getLastMod() { public String toString() {
return tLastMod.getJDate(); return getName();
} }
@Override public void populateBlankFields(PwDatabaseV3 db) {
public void setParent(PwGroup prt) { // TODO populate blanck field
parent = (PwGroupV3) prt; if (icon == null) {
level = parent.level + 1; icon = db.iconFactory.getIcon(1);
}
}
@Override
public void initNewGroup(String nm, PwGroupId newId) {
super.initNewGroup(nm, newId);
Date now = Calendar.getInstance().getTime();
tCreation = new PwDate(now);
tLastAccess = new PwDate(now);
tLastMod = new PwDate(now);
tExpire = new PwDate(PwGroupV3.NEVER_EXPIRE);
}
public void populateBlankFields(PwDatabaseV3 db) {
if (icon == null) {
icon = db.iconFactory.getIcon(1);
}
if (name == null) {
name = "";
}
if (tCreation == null) {
tCreation = PwEntryV3.DEFAULT_PWDATE;
}
if (tLastMod == null) {
tLastMod = PwEntryV3.DEFAULT_PWDATE;
}
if (tLastAccess == null) {
tLastAccess = PwEntryV3.DEFAULT_PWDATE;
}
if (tExpire == null) {
tExpire = PwEntryV3.DEFAULT_PWDATE;
}
}
@Override
public void setLastAccessTime(Date date) {
tLastAccess = new PwDate(date);
}
@Override
public void setLastModificationTime(Date date) {
tLastMod = new PwDate(date);
}
if (name == null) {
name = "";
}
}
} }

View File

@@ -19,107 +19,84 @@
*/ */
package com.keepassdroid.database; package com.keepassdroid.database;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public class PwGroupV4 extends PwGroup implements ITimeLogger { public class PwGroupV4 extends PwGroup implements ITimeLogger {
//public static final int FOLDER_ICON = 48;
public static final boolean DEFAULT_SEARCHING_ENABLED = true; public static final boolean DEFAULT_SEARCHING_ENABLED = true;
public PwGroupV4 parent = null; private PwGroupV4 parent = null;
public UUID uuid = PwDatabaseV4.UUID_ZERO; private UUID uuid = PwDatabase.UUID_ZERO;
public String notes = ""; private PwIconCustom customIcon = PwIconCustom.ZERO;
public PwIconCustom customIcon = PwIconCustom.ZERO; private long usageCount = 0;
public boolean isExpanded = true; private PwDate parentGroupLastMod = new PwDate();
public String defaultAutoTypeSequence = ""; private Map<String, String> customData = new HashMap<>();
public Boolean enableAutoType = null;
public Boolean enableSearching = null; private boolean expires = false;
public UUID lastTopVisibleEntry = PwDatabaseV4.UUID_ZERO;
private Date parentGroupLastMod = PwDatabaseV4.DEFAULT_NOW; private String notes = "";
private Date creation = PwDatabaseV4.DEFAULT_NOW; private boolean isExpanded = true;
private Date lastMod = PwDatabaseV4.DEFAULT_NOW; private String defaultAutoTypeSequence = "";
private Date lastAccess = PwDatabaseV4.DEFAULT_NOW; private Boolean enableAutoType = null;
private Date expireDate = PwDatabaseV4.DEFAULT_NOW; private Boolean enableSearching = null;
private boolean expires = false; private UUID lastTopVisibleEntry = PwDatabase.UUID_ZERO;
private long usageCount = 0;
public Map<String, String> customData = new HashMap<String, String>();
public PwGroupV4() { public PwGroupV4() {
super();
} }
public PwGroupV4(boolean createUUID, boolean setTimes, String name, PwIconStandard icon) { public PwGroupV4(String name, PwIconStandard icon) {
if (createUUID) { super.construct();
uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
}
if (setTimes) {
creation = lastMod = lastAccess = new Date();
}
this.name = name; this.name = name;
this.icon = icon; this.icon = icon;
} }
@Override
public void initNewGroup(String nm, PwGroupId newId) {
super.initNewGroup(nm, newId);
parentGroupLastMod = new PwDate();
}
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership) { public void AddGroup(PwGroupV4 subGroup) {
AddGroup(subGroup, takeOwnership, false);
}
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership, boolean updateLocationChanged) {
if ( subGroup == null ) throw new RuntimeException("subGroup"); if ( subGroup == null ) throw new RuntimeException("subGroup");
childGroups.add(subGroup); childGroups.add(subGroup);
subGroup.parent = this;
if ( takeOwnership ) subGroup.parent = this; }
if ( updateLocationChanged ) subGroup.parentGroupLastMod = new Date(System.currentTimeMillis());
}
public void AddEntry(PwEntryV4 pe, boolean takeOwnership) { public void AddEntry(PwEntryV4 pe) {
AddEntry(pe, takeOwnership, false);
}
public void AddEntry(PwEntryV4 pe, boolean takeOwnership, boolean updateLocationChanged) {
assert(pe != null); assert(pe != null);
addChildEntry(pe);
childEntries.add(pe); pe.setParent(this);
}
if ( takeOwnership ) pe.parent = this;
if ( updateLocationChanged ) pe.setLocationChanged(new Date(System.currentTimeMillis()));
}
@Override @Override
public PwGroup getParent() { public PwGroup getParent() {
return parent; return parent;
} }
public void buildChildGroupsRecursive(List<PwGroup> list) {
list.add(this);
for ( int i = 0; i < childGroups.size(); i++) {
PwGroupV4 child = (PwGroupV4) childGroups.get(i);
child.buildChildGroupsRecursive(list);
}
}
public void buildChildEntriesRecursive(List<PwEntry> list) { @Override
for ( int i = 0; i < childEntries.size(); i++ ) { public void setParent(PwGroup prt) {
list.add(childEntries.get(i)); parent = (PwGroupV4) prt;
} }
for ( int i = 0; i < childGroups.size(); i++ ) { public UUID getUUID() {
PwGroupV4 child = (PwGroupV4) childGroups.get(i); return uuid;
child.buildChildEntriesRecursive(list); }
}
public void setUUID(UUID uuid) {
} this.uuid = uuid;
}
public PwIconCustom getCustomIcon() {
return customIcon;
}
public void setCustomIcon(PwIconCustom icon) {
this.customIcon = icon;
}
@Override @Override
public PwGroupId getId() { public PwGroupId getId() {
@@ -133,97 +110,106 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
} }
@Override @Override
public String getName() { public PwDate getLocationChanged() {
return name;
}
@Override
public Date getLastMod() {
return parentGroupLastMod; return parentGroupLastMod;
} }
public Date getCreationTime() {
return creation;
}
public Date getExpiryTime() {
return expireDate;
}
public Date getLastAccessTime() {
return lastAccess;
}
public Date getLastModificationTime() {
return lastMod;
}
public Date getLocationChanged() {
return parentGroupLastMod;
}
public long getUsageCount() {
return usageCount;
}
public void setCreationTime(Date date) {
creation = date;
}
public void setExpiryTime(Date date) {
expireDate = date;
}
@Override @Override
public void setLastAccessTime(Date date) { public void setLocationChanged(PwDate date) {
lastAccess = date;
}
@Override
public void setLastModificationTime(Date date) {
lastMod = date;
}
public void setLocationChanged(Date date) {
parentGroupLastMod = date; parentGroupLastMod = date;
} }
@Override
public long getUsageCount() {
return usageCount;
}
@Override
public void setUsageCount(long count) { public void setUsageCount(long count) {
usageCount = count; usageCount = count;
} }
@Override
public boolean expires() { public boolean expires() {
return expires; return expires;
} }
@Override
public void setExpires(boolean exp) { public void setExpires(boolean exp) {
expires = exp; expires = exp;
} }
@Override @Override
public void setParent(PwGroup prt) { public boolean allowAddEntryIfIsRoot() {
parent = (PwGroupV4) prt; return true;
} }
@Override @Override
public PwIcon getIcon() { public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabaseV4.UUID_ZERO)) { if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
return super.getIcon(); return super.getIcon();
} else { } else {
return customIcon; return customIcon;
} }
} }
@Override public void putCustomData(String key, String value) {
public void initNewGroup(String nm, PwGroupId newId) { customData.put(key, value);
super.initNewGroup(nm, newId); }
lastAccess = lastMod = creation = parentGroupLastMod = new Date(); public boolean containsCustomData() {
} return customData.size() > 0;
}
public boolean isSearchEnabled() {
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public boolean isExpanded() {
return isExpanded;
}
public void setExpanded(boolean expanded) {
isExpanded = expanded;
}
public String getDefaultAutoTypeSequence() {
return defaultAutoTypeSequence;
}
public void setDefaultAutoTypeSequence(String defaultAutoTypeSequence) {
this.defaultAutoTypeSequence = defaultAutoTypeSequence;
}
public Boolean getEnableAutoType() {
return enableAutoType;
}
public void setEnableAutoType(Boolean enableAutoType) {
this.enableAutoType = enableAutoType;
}
public Boolean getEnableSearching() {
return enableSearching;
}
public void setEnableSearching(Boolean enableSearching) {
this.enableSearching = enableSearching;
}
public UUID getLastTopVisibleEntry() {
return lastTopVisibleEntry;
}
public void setLastTopVisibleEntry(UUID lastTopVisibleEntry) {
this.lastTopVisibleEntry = lastTopVisibleEntry;
}
public boolean isSearchEnabled() {
PwGroupV4 group = this; PwGroupV4 group = this;
while (group != null) { while (group != null) {
Boolean search = group.enableSearching; Boolean search = group.enableSearching;

View File

@@ -1,12 +1,10 @@
package com.keepassdroid.database; package com.keepassdroid.database;
public abstract class PwIcon { import java.io.Serializable;
public abstract class PwIcon implements Serializable {
public boolean isMetaStreamIcon() { public boolean isMetaStreamIcon() {
return false; return false;
} }
public void writeBytes() {
}
} }

View File

@@ -22,7 +22,7 @@ package com.keepassdroid.database;
import java.util.UUID; import java.util.UUID;
public class PwIconCustom extends PwIcon { public class PwIconCustom extends PwIcon {
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabaseV4.UUID_ZERO, new byte[0]); public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
public final UUID uuid; public final UUID uuid;
public byte[] imageData; public byte[] imageData;
@@ -32,6 +32,11 @@ public class PwIconCustom extends PwIcon {
imageData = data; imageData = data;
} }
public PwIconCustom(PwIconCustom icon) {
uuid = icon.uuid;
imageData = icon.imageData;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

@@ -32,6 +32,10 @@ public class PwIconStandard extends PwIcon {
this.iconId = iconId; this.iconId = iconId;
} }
public PwIconStandard(PwIconStandard icon) {
this.iconId = icon.iconId;
}
@Override @Override
public boolean isMetaStreamIcon() { public boolean isMetaStreamIcon() {
return iconId == 0; return iconId == 0;

View File

@@ -0,0 +1,165 @@
/*
* 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 static com.keepassdroid.database.PwDate.NEVER_EXPIRE;
import static com.keepassdroid.database.PwDate.PW_NEVER_EXPIRE;
/**
* Abstract class who manage Groups and Entries
*/
public abstract class PwNode implements ISmallTimeLogger, Serializable {
protected PwIconStandard icon = PwIconStandard.FIRST;
protected PwDate creation = new PwDate();
protected PwDate lastMod = new PwDate();
protected PwDate lastAccess = new PwDate();
protected PwDate expireDate = new PwDate(NEVER_EXPIRE);
protected void construct() {
}
public void assign(PwNode source) {
this.icon = source.icon;
this.creation = source.creation;
this.lastMod = source.lastMod;
this.lastAccess = source.lastAccess;
this.expireDate = source.expireDate;
}
protected void addCloneAttributesToNewEntry(PwEntry newEntry) {
newEntry.icon = new PwIconStandard(this.icon);
newEntry.creation = creation.clone();
newEntry.lastMod = lastMod.clone();
newEntry.lastAccess = lastAccess.clone();
newEntry.expireDate = expireDate.clone();
}
/**
* 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 PwIcon getIcon() {
return icon;
}
public PwIconStandard getIconStandard() {
return icon;
}
public void setIcon(PwIconStandard icon) {
this.icon = icon;
}
/**
* 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);
public PwDate getCreationTime() {
return creation;
}
public void setCreationTime(PwDate date) {
creation = date;
}
public PwDate getLastModificationTime() {
return lastMod;
}
public void setLastModificationTime(PwDate date) {
lastMod = date;
}
public PwDate getLastAccessTime() {
return lastAccess;
}
public void setLastAccessTime(PwDate date) {
lastAccess = date;
}
public PwDate getExpiryTime() {
return expireDate;
}
public void setExpiryTime(PwDate date) {
expireDate = date;
}
public void setExpires(boolean expires) {
if (!expires) {
expireDate = PW_NEVER_EXPIRE;
}
}
public boolean expires() {
return ! PwDate.IsSameDate(NEVER_EXPIRE, expireDate.getDate());
}
/**
* 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,266 @@
/*
* 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(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(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 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 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(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 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 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().getDate()
.compareTo(object2.getCreationTime().getDate());
// If same creation, can be different
if (nodeCreationComp == 0) {
return object1.hashCode() - object2.hashCode();
}
return nodeCreationComp;
}
}
/**
* Group comparator by name
*/
public static class GroupNameComparator implements Comparator<PwGroup> {
private boolean ascending;
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(boolean ascending) {
this.ascending = ascending;
}
public int compare(PwGroup object1, PwGroup object2) {
if (object1.equals(object2))
return 0;
int groupCreationComp = object1.getCreationTime().getDate()
.compareTo(object2.getCreationTime().getDate());
// If same creation, can be different
if (groupCreationComp == 0) {
return object1.hashCode() - object2.hashCode();
}
// If descending
if (!ascending)
groupCreationComp = -groupCreationComp;
return groupCreationComp;
}
}
/**
* Comparator of Entry by Name
*/
public static class EntryNameComparator implements Comparator<PwEntry> {
private boolean ascending;
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(boolean ascending) {
this.ascending = ascending;
}
public int compare(PwEntry object1, PwEntry object2) {
if (object1.equals(object2))
return 0;
int entryCreationComp = object1.getCreationTime().getDate()
.compareTo(object2.getCreationTime().getDate());
// If same creation, can be different
if (entryCreationComp == 0) {
return object1.hashCode() - object2.hashCode();
}
// If descending
if (!ascending)
entryCreationComp = -entryCreationComp;
return entryCreationComp;
}
}
}

View File

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

View File

@@ -21,7 +21,7 @@ package com.keepassdroid.database.edit;
import android.content.Context; import android.content.Context;
import com.keepassdroid.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase; import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwGroup; import com.keepassdroid.database.PwGroup;
@@ -32,24 +32,20 @@ public class AddGroup extends RunnableOnFinish {
private PwGroup mGroup; private PwGroup mGroup;
private PwGroup mParent; private PwGroup mParent;
private Context ctx; private Context ctx;
protected boolean mDontSave; private boolean mDontSave;
public AddGroup(Context ctx, Database db, String name, int iconid,
public static AddGroup getInstance(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, boolean dontSave) { PwGroup parent, AfterAddNodeOnFinish afterAddNode,
return new AddGroup(ctx, db, name, iconid, parent, finish, dontSave); boolean dontSave) {
} super(afterAddNode);
private AddGroup(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, boolean dontSave) {
super(finish);
mDb = db; mDb = db;
mName = name; mName = name;
mIconID = iconid; mIconID = iconid;
mParent = parent; mParent = parent;
mDontSave = dontSave; mDontSave = dontSave;
this.ctx = ctx; this.ctx = ctx;
mFinish = new AfterAdd(mFinish); mFinish = new AfterAdd(mFinish);
} }
@@ -60,11 +56,9 @@ public class AddGroup extends RunnableOnFinish {
// Generate new group // Generate new group
mGroup = pm.createGroup(); mGroup = pm.createGroup();
mGroup.initNewGroup(mName, pm.newGroupId()); mGroup.initNewGroup(mName, pm.newGroupId());
mGroup.icon = mDb.pm.iconFactory.getIcon(mIconID); mGroup.setIcon(mDb.pm.iconFactory.getIcon(mIconID));
pm.addGroupTo(mGroup, mParent); pm.addGroupTo(mGroup, mParent);
//mParent.sortGroupsByName();
// Commit to disk // Commit to disk
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave); SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
save.run(); save.run();
@@ -72,24 +66,23 @@ public class AddGroup extends RunnableOnFinish {
private class AfterAdd extends OnFinish { private class AfterAdd extends OnFinish {
public AfterAdd(OnFinish finish) { AfterAdd(OnFinish finish) {
super(finish); super(finish);
} }
@Override @Override
public void run() { public void run() {
PwDatabase pm = mDb.pm; PwDatabase pm = mDb.pm;
if ( mSuccess ) { if ( !mSuccess ) {
// Mark parent group dirty pm.removeGroupFrom(mGroup, mParent);
mDb.dirty.add(mParent);
} else {
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; package com.keepassdroid.database.edit;
import android.content.Context; 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.app.App;
import com.keepassdroid.database.PwDatabase; import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwEncryptionAlgorithm;
import com.keepassdroid.utils.UriUtil; import com.keepassdroid.utils.UriUtil;
public class CreateDB extends RunnableOnFinish { public class CreateDB extends RunnableOnFinish {
private final int DEFAULT_ENCRYPTION_ROUNDS = 300;
private String mFilename; private String mFilename;
private boolean mDontSave; private boolean mDontSave;
@@ -57,7 +51,6 @@ public class CreateDB extends RunnableOnFinish {
// Set Database state // Set Database state
db.pm = pm; db.pm = pm;
Uri.Builder b = new Uri.Builder();
db.mUri = UriUtil.parseDefaultFile(mFilename); db.mUri = UriUtil.parseDefaultFile(mFilename);
db.setLoaded(); db.setLoaded();
App.clearShutdown(); App.clearShutdown();

View File

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

View File

@@ -19,109 +19,115 @@
*/ */
package com.keepassdroid.database.edit; package com.keepassdroid.database.edit;
import java.util.ArrayList; import android.content.Context;
import java.util.List;
import com.keepassdroid.Database;
import com.keepassdroid.GroupBaseActivity;
import com.keepassdroid.app.App; import com.keepassdroid.app.App;
import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwEntry; import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup; import com.keepassdroid.database.PwGroup;
import java.util.ArrayList;
import java.util.List;
public class DeleteGroup extends RunnableOnFinish { public class DeleteGroup extends RunnableOnFinish {
private Context mContext;
private Database mDb; private Database mDb;
private PwGroup mGroup; private PwGroup mGroup;
private GroupBaseActivity mAct;
private boolean mDontSave; 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); super(finish);
setMembers(db, group, act, false); setMembers(ctx, db, group, false);
}
public DeleteGroup(Database db, PwGroup group, GroupBaseActivity act, OnFinish finish, boolean dontSave) {
super(finish);
setMembers(db, group, act, dontSave);
} }
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) { public DeleteGroup(Database db, PwGroup group, OnFinish finish, boolean dontSave) {
super(finish); 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; mDb = db;
mGroup = group; mGroup = group;
mAct = act; mContext = ctx;
mDontSave = dontSave; mDontSave = dontSave;
mFinish = new AfterDelete(mFinish);
} }
@Override @Override
public void run() { public void run() {
PwDatabase pm = mDb.pm;
// Remove child entries PwGroup parent = mGroup.getParent();
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();
// 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.getChildEntries());
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.getChildGroups());
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 { 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); super(finish);
this.mParent = parent;
this.mGroup = mGroup;
this.recycled = recycle;
} }
public void run() { public void run() {
if ( mSuccess ) { PwDatabase pm = mDb.pm;
// Remove from tree global if ( !mSuccess ) {
mDb.pm.groups.remove(mGroup.getId()); if (recycled) {
pm.undoRecycle(mGroup, mParent);
// Remove tree from the dirty global (if it is present), not a big deal if this fails }
mDb.dirty.remove(mGroup); else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
// Mark parent dirty App.setShutdown();
PwGroup parent = mGroup.getParent(); // TODO TEST pm.undoDeleteGroup(mGroup, mParent);
if ( parent != null ) { }
mDb.dirty.add(parent); }
} // TODO Callback after delete group
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();
}
super.run(); super.run();
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,9 +21,8 @@ package com.keepassdroid.database.edit;
import android.content.Context; import android.content.Context;
import com.keepassdroid.Database; import com.keepassdroid.database.Database;
import com.keepassdroid.database.PwEntry; import com.keepassdroid.database.PwEntry;
import com.keepassdroid.database.PwGroup;
public class UpdateEntry extends RunnableOnFinish { public class UpdateEntry extends RunnableOnFinish {
private Database mDb; private Database mDb;
@@ -41,7 +40,7 @@ public class UpdateEntry extends RunnableOnFinish {
// Keep backup of original values in case save fails // Keep backup of original values in case save fails
PwEntry backup; PwEntry backup;
backup = (PwEntry) mOldE.clone(); backup = mOldE.clone();
mFinish = new AfterUpdate(backup, finish); mFinish = new AfterUpdate(backup, finish);
} }
@@ -61,36 +60,19 @@ public class UpdateEntry extends RunnableOnFinish {
private class AfterUpdate extends OnFinish { private class AfterUpdate extends OnFinish {
private PwEntry mBackup; private PwEntry mBackup;
public AfterUpdate(PwEntry backup, OnFinish finish) { AfterUpdate(PwEntry backup, OnFinish finish) {
super(finish); super(finish);
mBackup = backup; mBackup = backup;
} }
@Override @Override
public void run() { public void run() {
if ( mSuccess ) { 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 we fail to save, back out changes to global structure // If we fail to save, back out changes to global structure
mOldE.assign(mBackup); mOldE.assign(mBackup);
} }
// TODO Callback for update entry
super.run(); super.run();
} }
} }
} }

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ import javax.crypto.spec.SecretKeySpec;
import android.util.Log; import android.util.Log;
import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.R;
import com.keepassdroid.UpdateStatus; import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.crypto.CipherFactory; import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.database.PwDatabaseV3; import com.keepassdroid.database.PwDatabaseV3;
import com.keepassdroid.database.PwDate; import com.keepassdroid.database.PwDate;
@@ -125,10 +125,10 @@ public class ImporterV3 extends Importer {
public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream kfIs) public PwDatabaseV3 openDatabase( InputStream inStream, String password, InputStream kfIs)
throws IOException, InvalidDBException 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 throws IOException, InvalidDBException
{ {
PwDatabaseV3 newManager; PwDatabaseV3 newManager;
@@ -340,31 +340,31 @@ public class ImporterV3 extends Importer {
// Ignore field // Ignore field
break; break;
case 0x0001 : case 0x0001 :
grp.groupId = LEDataInputStream.readInt(buf, offset); grp.setGroupId(LEDataInputStream.readInt(buf, offset));
break; break;
case 0x0002 : case 0x0002 :
grp.name = Types.readCString(buf, offset); grp.setName(Types.readCString(buf, offset));
break; break;
case 0x0003 : case 0x0003 :
grp.tCreation = new PwDate(buf, offset); grp.setCreationTime(new PwDate(buf, offset));
break; break;
case 0x0004 : case 0x0004 :
grp.tLastMod = new PwDate(buf, offset); grp.setLastModificationTime(new PwDate(buf, offset));
break; break;
case 0x0005 : case 0x0005 :
grp.tLastAccess = new PwDate(buf, offset); grp.setLastAccessTime(new PwDate(buf, offset));
break; break;
case 0x0006 : case 0x0006 :
grp.tExpire = new PwDate(buf, offset); grp.setExpiryTime(new PwDate(buf, offset));
break; break;
case 0x0007 : case 0x0007 :
grp.icon = db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset)); grp.setIcon(db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset)));
break; break;
case 0x0008 : case 0x0008 :
grp.level = LEDataInputStream.readUShort(buf, offset); grp.setLevel(LEDataInputStream.readUShort(buf, offset));
break; break;
case 0x0009 : case 0x0009 :
grp.flags = LEDataInputStream.readInt(buf, offset); grp.setFlags(LEDataInputStream.readInt(buf, offset));
break; break;
} }
} }
@@ -387,7 +387,7 @@ public class ImporterV3 extends Importer {
ent.setUUID(Types.bytestoUUID(buf, offset)); ent.setUUID(Types.bytestoUUID(buf, offset));
break; break;
case 0x0002 : case 0x0002 :
ent.groupId = LEDataInputStream.readInt(buf, offset); ent.setGroupId(LEDataInputStream.readInt(buf, offset));
break; break;
case 0x0003 : case 0x0003 :
int iconId = LEDataInputStream.readInt(buf, offset); int iconId = LEDataInputStream.readInt(buf, offset);
@@ -397,37 +397,37 @@ public class ImporterV3 extends Importer {
iconId = 0; iconId = 0;
} }
ent.icon = db.iconFactory.getIcon(iconId); ent.setIcon(db.iconFactory.getIcon(iconId));
break; break;
case 0x0004 : case 0x0004 :
ent.title = Types.readCString(buf, offset); ent.setTitle(Types.readCString(buf, offset));
break; break;
case 0x0005 : case 0x0005 :
ent.url = Types.readCString(buf, offset); ent.setUrl(Types.readCString(buf, offset));
break; break;
case 0x0006 : case 0x0006 :
ent.username = Types.readCString(buf, offset); ent.setUsername(Types.readCString(buf, offset));
break; break;
case 0x0007 : case 0x0007 :
ent.setPassword(buf, offset, Types.strlen(buf, offset)); ent.setPassword(buf, offset, Types.strlen(buf, offset));
break; break;
case 0x0008 : case 0x0008 :
ent.additional = Types.readCString(buf, offset); ent.setNotes(Types.readCString(buf, offset));
break; break;
case 0x0009 : case 0x0009 :
ent.tCreation = new PwDate(buf, offset); ent.setCreationTime(new PwDate(buf, offset));
break; break;
case 0x000A : case 0x000A :
ent.tLastMod = new PwDate(buf, offset); ent.setLastModificationTime(new PwDate(buf, offset));
break; break;
case 0x000B : case 0x000B :
ent.tLastAccess = new PwDate(buf, offset); ent.setLastAccessTime(new PwDate(buf, offset));
break; break;
case 0x000C : case 0x000C :
ent.tExpire = new PwDate(buf, offset); ent.setExpiryTime(new PwDate(buf, offset));
break; break;
case 0x000D : case 0x000D :
ent.binaryDesc = Types.readCString(buf, offset); ent.setBinaryDesc(Types.readCString(buf, offset));
break; break;
case 0x000E : case 0x000E :
ent.setBinaryData(buf, offset, fieldSize); ent.setBinaryData(buf, offset, fieldSize);

View File

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

View File

@@ -46,11 +46,12 @@ import org.xmlpull.v1.XmlPullParserFactory;
import biz.source_code.base64Coder.Base64Coder; import biz.source_code.base64Coder.Base64Coder;
import com.keepassdroid.UpdateStatus; import com.keepassdroid.database.PwDatabase;
import com.keepassdroid.database.PwDate;
import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.crypto.CipherFactory; import com.keepassdroid.crypto.CipherFactory;
import com.keepassdroid.crypto.PwStreamCipherFactory; import com.keepassdroid.crypto.PwStreamCipherFactory;
import com.keepassdroid.crypto.engine.CipherEngine; import com.keepassdroid.crypto.engine.CipherEngine;
import com.keepassdroid.database.BinaryPool;
import com.keepassdroid.database.ITimeLogger; import com.keepassdroid.database.ITimeLogger;
import com.keepassdroid.database.PwCompressionAlgorithm; import com.keepassdroid.database.PwCompressionAlgorithm;
import com.keepassdroid.database.PwDatabaseV4; import com.keepassdroid.database.PwDatabaseV4;
@@ -78,11 +79,11 @@ public class ImporterV4 extends Importer {
private StreamCipher randomStream; private StreamCipher randomStream;
private PwDatabaseV4 db; private PwDatabaseV4 db;
private BinaryPool binPool = new BinaryPool();
private byte[] hashOfHeader = null; private byte[] hashOfHeader = null;
private byte[] pbHeader = null; private byte[] pbHeader = null;
private long version; private long version;
private int binNum = 0;
Calendar utcCal; Calendar utcCal;
public ImporterV4() { public ImporterV4() {
@@ -98,18 +99,17 @@ public class ImporterV4 extends Importer {
public PwDatabaseV4 openDatabase(InputStream inStream, String password, public PwDatabaseV4 openDatabase(InputStream inStream, String password,
InputStream keyInputStream) throws IOException, InvalidDBException { InputStream keyInputStream) throws IOException, InvalidDBException {
return openDatabase(inStream, password, keyInputStream, new UpdateStatus()); return openDatabase(inStream, password, keyInputStream, new UpdateStatus(), 0);
} }
@Override @Override
public PwDatabaseV4 openDatabase(InputStream inStream, String password, public PwDatabaseV4 openDatabase(InputStream inStream, String password,
InputStream keyInputStream, UpdateStatus status) throws IOException, InputStream keyInputStream, UpdateStatus status, long roundsFix) throws IOException,
InvalidDBException { InvalidDBException {
db = createDB(); db = createDB();
PwDbHeaderV4 header = new PwDbHeaderV4(db); PwDbHeaderV4 header = new PwDbHeaderV4(db);
header.binaries.clear(); db.binPool.clear();
PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream); PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream);
version = header.version; version = header.version;
@@ -118,7 +118,7 @@ public class ImporterV4 extends Importer {
pbHeader = hh.header; pbHeader = hh.header;
db.setMasterKey(password, keyInputStream); db.setMasterKey(password, keyInputStream);
db.makeFinalKey(header.masterSeed, db.kdfParameters); db.makeFinalKey(header.masterSeed, db.kdfParameters, roundsFix);
CipherEngine engine; CipherEngine engine;
Cipher cipher; Cipher cipher;
@@ -253,6 +253,7 @@ public class ImporterV4 extends Importer {
byte[] bin = new byte[data.length - 1]; byte[] bin = new byte[data.length - 1];
System.arraycopy(data, 1, bin, 0, data.length-1); System.arraycopy(data, 1, bin, 0, data.length-1);
ProtectedBinary pb = new ProtectedBinary(prot, bin); ProtectedBinary pb = new ProtectedBinary(prot, bin);
db.binPool.poolAdd(pb);
if (prot) { if (prot) {
Arrays.fill(data, (byte)0); Arrays.fill(data, (byte)0);
@@ -309,7 +310,7 @@ public class ImporterV4 extends Importer {
private boolean entryInHistory = false; private boolean entryInHistory = false;
private PwEntryV4 ctxHistoryBase = null; private PwEntryV4 ctxHistoryBase = null;
private PwDeletedObject ctxDeletedObject = null; private PwDeletedObject ctxDeletedObject = null;
private UUID customIconID = PwDatabaseV4.UUID_ZERO; private UUID customIconID = PwDatabase.UUID_ZERO;
private byte[] customIconData; private byte[] customIconData;
private String customDataKey = null; private String customDataKey = null;
private String customDataValue = null; private String customDataValue = null;
@@ -511,7 +512,7 @@ public class ImporterV4 extends Importer {
if ( key != null ) { if ( key != null ) {
ProtectedBinary pbData = ReadProtectedBinary(xpp); ProtectedBinary pbData = ReadProtectedBinary(xpp);
int id = Integer.parseInt(key); int id = Integer.parseInt(key);
binPool.put(id, pbData); db.binPool.put(id, pbData);
} else { } else {
ReadUnknown(xpp); ReadUnknown(xpp);
} }
@@ -558,38 +559,38 @@ public class ImporterV4 extends Importer {
case Group: case Group:
if ( name.equalsIgnoreCase(ElemUuid) ) { if ( name.equalsIgnoreCase(ElemUuid) ) {
ctxGroup.uuid = ReadUuid(xpp); ctxGroup.setUUID(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(ElemName) ) { } else if ( name.equalsIgnoreCase(ElemName) ) {
ctxGroup.name = ReadString(xpp); ctxGroup.setName(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemNotes) ) { } else if ( name.equalsIgnoreCase(ElemNotes) ) {
ctxGroup.notes = ReadString(xpp); ctxGroup.setNotes(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemIcon) ) { } else if ( name.equalsIgnoreCase(ElemIcon) ) {
ctxGroup.icon = db.iconFactory.getIcon((int)ReadUInt(xpp, 0)); ctxGroup.setIcon(db.iconFactory.getIcon((int)ReadUInt(xpp, 0)));
} else if ( name.equalsIgnoreCase(ElemCustomIconID) ) { } else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
ctxGroup.customIcon = db.iconFactory.getIcon(ReadUuid(xpp)); ctxGroup.setCustomIcon(db.iconFactory.getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(ElemTimes) ) { } else if ( name.equalsIgnoreCase(ElemTimes) ) {
return SwitchContext(ctx, KdbContext.GroupTimes, xpp); return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
} else if ( name.equalsIgnoreCase(ElemIsExpanded) ) { } else if ( name.equalsIgnoreCase(ElemIsExpanded) ) {
ctxGroup.isExpanded = ReadBool(xpp, true); ctxGroup.setExpanded(ReadBool(xpp, true));
} else if ( name.equalsIgnoreCase(ElemGroupDefaultAutoTypeSeq) ) { } else if ( name.equalsIgnoreCase(ElemGroupDefaultAutoTypeSeq) ) {
ctxGroup.defaultAutoTypeSequence = ReadString(xpp); ctxGroup.setDefaultAutoTypeSequence(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemEnableAutoType) ) { } else if ( name.equalsIgnoreCase(ElemEnableAutoType) ) {
ctxGroup.enableAutoType = StringToBoolean(ReadString(xpp)); ctxGroup.setEnableAutoType(StringToBoolean(ReadString(xpp)));
} else if ( name.equalsIgnoreCase(ElemEnableSearching) ) { } else if ( name.equalsIgnoreCase(ElemEnableSearching) ) {
ctxGroup.enableSearching = StringToBoolean(ReadString(xpp)); ctxGroup.setEnableSearching(StringToBoolean(ReadString(xpp)));
} else if ( name.equalsIgnoreCase(ElemLastTopVisibleEntry) ) { } else if ( name.equalsIgnoreCase(ElemLastTopVisibleEntry) ) {
ctxGroup.lastTopVisibleEntry = ReadUuid(xpp); ctxGroup.setLastTopVisibleEntry(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(ElemCustomData) ) { } else if ( name.equalsIgnoreCase(ElemCustomData) ) {
return SwitchContext(ctx, KdbContext.GroupCustomData, xpp); return SwitchContext(ctx, KdbContext.GroupCustomData, xpp);
} else if ( name.equalsIgnoreCase(ElemGroup) ) { } else if ( name.equalsIgnoreCase(ElemGroup) ) {
ctxGroup = new PwGroupV4(); ctxGroup = new PwGroupV4();
ctxGroups.peek().AddGroup(ctxGroup, true); ctxGroups.peek().AddGroup(ctxGroup);
ctxGroups.push(ctxGroup); ctxGroups.push(ctxGroup);
return SwitchContext(ctx, KdbContext.Group, xpp); return SwitchContext(ctx, KdbContext.Group, xpp);
} else if ( name.equalsIgnoreCase(ElemEntry) ) { } else if ( name.equalsIgnoreCase(ElemEntry) ) {
ctxEntry = new PwEntryV4(); ctxEntry = new PwEntryV4();
ctxGroup.AddEntry(ctxEntry, true); ctxGroup.AddEntry(ctxEntry);
entryInHistory = false; entryInHistory = false;
return SwitchContext(ctx, KdbContext.Entry, xpp); return SwitchContext(ctx, KdbContext.Entry, xpp);
@@ -619,17 +620,17 @@ public class ImporterV4 extends Importer {
if ( name.equalsIgnoreCase(ElemUuid) ) { if ( name.equalsIgnoreCase(ElemUuid) ) {
ctxEntry.setUUID(ReadUuid(xpp)); ctxEntry.setUUID(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(ElemIcon) ) { } else if ( name.equalsIgnoreCase(ElemIcon) ) {
ctxEntry.icon = db.iconFactory.getIcon((int)ReadUInt(xpp, 0)); ctxEntry.setIcon(db.iconFactory.getIcon((int)ReadUInt(xpp, 0)));
} else if ( name.equalsIgnoreCase(ElemCustomIconID) ) { } else if ( name.equalsIgnoreCase(ElemCustomIconID) ) {
ctxEntry.customIcon = db.iconFactory.getIcon(ReadUuid(xpp)); ctxEntry.setCustomIcon(db.iconFactory.getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(ElemFgColor) ) { } else if ( name.equalsIgnoreCase(ElemFgColor) ) {
ctxEntry.foregroundColor = ReadString(xpp); ctxEntry.setForegroundColor(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemBgColor) ) { } else if ( name.equalsIgnoreCase(ElemBgColor) ) {
ctxEntry.backgroupColor = ReadString(xpp); ctxEntry.setBackgroupColor(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemOverrideUrl) ) { } else if ( name.equalsIgnoreCase(ElemOverrideUrl) ) {
ctxEntry.overrideURL = ReadString(xpp); ctxEntry.setOverrideURL(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemTags) ) { } else if ( name.equalsIgnoreCase(ElemTags) ) {
ctxEntry.tags = ReadString(xpp); ctxEntry.setTags(ReadString(xpp));
} else if ( name.equalsIgnoreCase(ElemTimes) ) { } else if ( name.equalsIgnoreCase(ElemTimes) ) {
return SwitchContext(ctx, KdbContext.EntryTimes, xpp); return SwitchContext(ctx, KdbContext.EntryTimes, xpp);
} else if ( name.equalsIgnoreCase(ElemString) ) { } else if ( name.equalsIgnoreCase(ElemString) ) {
@@ -680,19 +681,19 @@ public class ImporterV4 extends Importer {
} }
if ( name.equalsIgnoreCase(ElemLastModTime) ) { if ( name.equalsIgnoreCase(ElemLastModTime) ) {
tl.setLastModificationTime(ReadTime(xpp)); tl.setLastModificationTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemCreationTime) ) { } else if ( name.equalsIgnoreCase(ElemCreationTime) ) {
tl.setCreationTime(ReadTime(xpp)); tl.setCreationTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemLastAccessTime) ) { } else if ( name.equalsIgnoreCase(ElemLastAccessTime) ) {
tl.setLastAccessTime(ReadTime(xpp)); tl.setLastAccessTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemExpiryTime) ) { } else if ( name.equalsIgnoreCase(ElemExpiryTime) ) {
tl.setExpiryTime(ReadTime(xpp)); tl.setExpiryTime(ReadPwTime(xpp));
} else if ( name.equalsIgnoreCase(ElemExpires) ) { } else if ( name.equalsIgnoreCase(ElemExpires) ) {
tl.setExpires(ReadBool(xpp, false)); tl.setExpires(ReadBool(xpp, false));
} else if ( name.equalsIgnoreCase(ElemUsageCount) ) { } else if ( name.equalsIgnoreCase(ElemUsageCount) ) {
tl.setUsageCount(ReadULong(xpp, 0)); tl.setUsageCount(ReadULong(xpp, 0));
} else if ( name.equalsIgnoreCase(ElemLocationChanged) ) { } else if ( name.equalsIgnoreCase(ElemLocationChanged) ) {
tl.setLocationChanged(ReadTime(xpp)); tl.setLocationChanged(ReadPwTime(xpp));
} else { } else {
ReadUnknown(xpp); ReadUnknown(xpp);
} }
@@ -718,11 +719,11 @@ public class ImporterV4 extends Importer {
case EntryAutoType: case EntryAutoType:
if ( name.equalsIgnoreCase(ElemAutoTypeEnabled) ) { if ( name.equalsIgnoreCase(ElemAutoTypeEnabled) ) {
ctxEntry.autoType.enabled = ReadBool(xpp, true); ctxEntry.getAutoType().enabled = ReadBool(xpp, true);
} else if ( name.equalsIgnoreCase(ElemAutoTypeObfuscation) ) { } else if ( name.equalsIgnoreCase(ElemAutoTypeObfuscation) ) {
ctxEntry.autoType.obfuscationOptions = ReadUInt(xpp, 0); ctxEntry.getAutoType().obfuscationOptions = ReadUInt(xpp, 0);
} else if ( name.equalsIgnoreCase(ElemAutoTypeDefaultSeq) ) { } else if ( name.equalsIgnoreCase(ElemAutoTypeDefaultSeq) ) {
ctxEntry.autoType.defaultSequence = ReadString(xpp); ctxEntry.getAutoType().defaultSequence = ReadString(xpp);
} else if ( name.equalsIgnoreCase(ElemAutoTypeItem) ) { } else if ( name.equalsIgnoreCase(ElemAutoTypeItem) ) {
return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xpp); return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xpp);
} else { } else {
@@ -743,7 +744,7 @@ public class ImporterV4 extends Importer {
case EntryHistory: case EntryHistory:
if ( name.equalsIgnoreCase(ElemEntry) ) { if ( name.equalsIgnoreCase(ElemEntry) ) {
ctxEntry = new PwEntryV4(); ctxEntry = new PwEntryV4();
ctxHistoryBase.history.add(ctxEntry); ctxHistoryBase.addToHistory(ctxEntry);
entryInHistory = true; entryInHistory = true;
return SwitchContext(ctx, KdbContext.Entry, xpp); return SwitchContext(ctx, KdbContext.Entry, xpp);
@@ -796,13 +797,13 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.CustomIcons && name.equalsIgnoreCase(ElemCustomIcons) ) { } else if ( ctx == KdbContext.CustomIcons && name.equalsIgnoreCase(ElemCustomIcons) ) {
return KdbContext.Meta; return KdbContext.Meta;
} else if ( ctx == KdbContext.CustomIcon && name.equalsIgnoreCase(ElemCustomIconItem) ) { } else if ( ctx == KdbContext.CustomIcon && name.equalsIgnoreCase(ElemCustomIconItem) ) {
if ( ! customIconID.equals(PwDatabaseV4.UUID_ZERO) ) { if ( ! customIconID.equals(PwDatabase.UUID_ZERO) ) {
PwIconCustom icon = new PwIconCustom(customIconID, customIconData); PwIconCustom icon = new PwIconCustom(customIconID, customIconData);
db.customIcons.add(icon); db.customIcons.add(icon);
db.iconFactory.put(icon); db.iconFactory.put(icon);
} else assert(false); } else assert(false);
customIconID = PwDatabaseV4.UUID_ZERO; customIconID = PwDatabase.UUID_ZERO;
customIconData = null; customIconData = null;
return KdbContext.CustomIcons; return KdbContext.CustomIcons;
@@ -820,8 +821,8 @@ public class ImporterV4 extends Importer {
return KdbContext.CustomData; return KdbContext.CustomData;
} else if ( ctx == KdbContext.Group && name.equalsIgnoreCase(ElemGroup) ) { } else if ( ctx == KdbContext.Group && name.equalsIgnoreCase(ElemGroup) ) {
if ( ctxGroup.uuid == null || ctxGroup.uuid.equals(PwDatabaseV4.UUID_ZERO) ) { if ( ctxGroup.getUUID() == null || ctxGroup.getUUID().equals(PwDatabase.UUID_ZERO) ) {
ctxGroup.uuid = UUID.randomUUID(); ctxGroup.setUUID(UUID.randomUUID());
} }
ctxGroups.pop(); ctxGroups.pop();
@@ -839,7 +840,7 @@ public class ImporterV4 extends Importer {
return KdbContext.Group; return KdbContext.Group;
} else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) { } else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) {
if (groupCustomDataKey != null && groupCustomDataValue != null) { if (groupCustomDataKey != null && groupCustomDataValue != null) {
ctxGroup.customData.put(groupCustomDataKey, groupCustomDataKey); ctxGroup.putCustomData(groupCustomDataKey, groupCustomDataKey);
} else { } else {
assert(false); assert(false);
} }
@@ -850,8 +851,8 @@ public class ImporterV4 extends Importer {
return KdbContext.GroupCustomData; return KdbContext.GroupCustomData;
} else if ( ctx == KdbContext.Entry && name.equalsIgnoreCase(ElemEntry) ) { } else if ( ctx == KdbContext.Entry && name.equalsIgnoreCase(ElemEntry) ) {
if ( ctxEntry.uuid == null || ctxEntry.uuid.equals(PwDatabaseV4.UUID_ZERO) ) { if ( ctxEntry.getUUID() == null || ctxEntry.getUUID().equals(PwDatabase.UUID_ZERO) ) {
ctxEntry.uuid = UUID.randomUUID(); ctxEntry.setUUID(UUID.randomUUID());
} }
if ( entryInHistory ) { if ( entryInHistory ) {
@@ -863,13 +864,13 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.EntryTimes && name.equalsIgnoreCase(ElemTimes) ) { } else if ( ctx == KdbContext.EntryTimes && name.equalsIgnoreCase(ElemTimes) ) {
return KdbContext.Entry; return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryString && name.equalsIgnoreCase(ElemString) ) { } else if ( ctx == KdbContext.EntryString && name.equalsIgnoreCase(ElemString) ) {
ctxEntry.strings.put(ctxStringName, ctxStringValue); ctxEntry.addField(ctxStringName, ctxStringValue);
ctxStringName = null; ctxStringName = null;
ctxStringValue = null; ctxStringValue = null;
return KdbContext.Entry; return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryBinary && name.equalsIgnoreCase(ElemBinary) ) { } else if ( ctx == KdbContext.EntryBinary && name.equalsIgnoreCase(ElemBinary) ) {
ctxEntry.binaries.put(ctxBinaryName, ctxBinaryValue); ctxEntry.putProtectedBinary(ctxBinaryName, ctxBinaryValue);
ctxBinaryName = null; ctxBinaryName = null;
ctxBinaryValue = null; ctxBinaryValue = null;
@@ -877,7 +878,7 @@ public class ImporterV4 extends Importer {
} else if ( ctx == KdbContext.EntryAutoType && name.equalsIgnoreCase(ElemAutoType) ) { } else if ( ctx == KdbContext.EntryAutoType && name.equalsIgnoreCase(ElemAutoType) ) {
return KdbContext.Entry; return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryAutoTypeItem && name.equalsIgnoreCase(ElemAutoTypeItem) ) { } else if ( ctx == KdbContext.EntryAutoTypeItem && name.equalsIgnoreCase(ElemAutoTypeItem) ) {
ctxEntry.autoType.put(ctxATName, ctxATSeq); ctxEntry.getAutoType().put(ctxATName, ctxATSeq);
ctxATName = null; ctxATName = null;
ctxATSeq = null; ctxATSeq = null;
@@ -886,7 +887,7 @@ public class ImporterV4 extends Importer {
return KdbContext.Entry; return KdbContext.Entry;
} else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) { } else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(ElemStringDictExItem)) {
if (entryCustomDataKey != null && entryCustomDataValue != null) { if (entryCustomDataKey != null && entryCustomDataValue != null) {
ctxEntry.customData.put(entryCustomDataKey, entryCustomDataValue); ctxEntry.putCustomData(entryCustomDataKey, entryCustomDataValue);
} else { } else {
assert(false); assert(false);
} }
@@ -913,6 +914,10 @@ public class ImporterV4 extends Importer {
throw new RuntimeException("Invalid end element: Context " + contextName + "End element: " + name); throw new RuntimeException("Invalid end element: Context " + contextName + "End element: " + name);
} }
} }
private PwDate ReadPwTime(XmlPullParser xpp) throws IOException, XmlPullParserException {
return new PwDate(ReadTime(xpp));
}
private Date ReadTime(XmlPullParser xpp) throws IOException, XmlPullParserException { private Date ReadTime(XmlPullParser xpp) throws IOException, XmlPullParserException {
String sDate = ReadString(xpp); String sDate = ReadString(xpp);
@@ -922,7 +927,7 @@ public class ImporterV4 extends Importer {
byte[] buf = Base64Coder.decode(sDate); byte[] buf = Base64Coder.decode(sDate);
if (buf.length != 8) { if (buf.length != 8) {
byte[] buf8 = new byte[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; buf = buf8;
} }
@@ -932,7 +937,7 @@ public class ImporterV4 extends Importer {
} else { } else {
try { try {
utcDate = PwDatabaseV4XML.dateFormat.parse(sDate); utcDate = PwDatabaseV4XML.dateFormatter.get().parse(sDate);
} catch (ParseException e) { } catch (ParseException e) {
// Catch with null test below // Catch with null test below
} }
@@ -981,7 +986,7 @@ public class ImporterV4 extends Importer {
String encoded = ReadString(xpp); String encoded = ReadString(xpp);
if (encoded == null || encoded.length() == 0 ) { if (encoded == null || encoded.length() == 0 ) {
return PwDatabaseV4.UUID_ZERO; return PwDatabase.UUID_ZERO;
} }
// TODO: Switch to framework Base64 once API level 8 is the minimum // TODO: Switch to framework Base64 once API level 8 is the minimum
@@ -1061,7 +1066,7 @@ public class ImporterV4 extends Importer {
xpp.next(); // Consume end tag xpp.next(); // Consume end tag
int id = Integer.parseInt(ref); int id = Integer.parseInt(ref);
return binPool.get(id); return db.binPool.get(id);
} }
boolean compressed = false; boolean compressed = false;

View File

@@ -19,7 +19,7 @@
*/ */
package com.keepassdroid.database.load; package com.keepassdroid.database.load;
import com.keepassdroid.UpdateStatus; import com.keepassdroid.tasks.UpdateStatus;
import com.keepassdroid.database.PwDatabaseV4Debug; import com.keepassdroid.database.PwDatabaseV4Debug;
import com.keepassdroid.database.exception.InvalidDBException; import com.keepassdroid.database.exception.InvalidDBException;
@@ -35,9 +35,10 @@ public class ImporterV4Debug extends ImporterV4 {
@Override @Override
public PwDatabaseV4Debug openDatabase(InputStream inStream, String password, public PwDatabaseV4Debug openDatabase(InputStream inStream, String password,
InputStream keyInputFile, UpdateStatus status) throws IOException, InputStream keyInputFile, UpdateStatus status, long roundsFix) throws IOException,
InvalidDBException { 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.writeInt(streamKeySize);
los.write(header.innerRandomStreamKey); los.write(header.innerRandomStreamKey);
for (ProtectedBinary bin : header.binaries) { for (ProtectedBinary bin : db.binPool.binaries()) {
byte flag = KdbxBinaryFlags.None; byte flag = KdbxBinaryFlags.None;
if (bin.isProtected()) { if (bin.isProtected()) {
flag |= KdbxBinaryFlags.Protected; flag |= KdbxBinaryFlags.Protected;

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