mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
210 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49bcc877ef | ||
|
|
4b81dd552e | ||
|
|
ff63aaf3f3 | ||
|
|
39cbd1477b | ||
|
|
3741cc558a | ||
|
|
2a314ca3c1 | ||
|
|
10f9564825 | ||
|
|
1ae87c9b18 | ||
|
|
24301ba462 | ||
|
|
ff6481274f | ||
|
|
866df585a2 | ||
|
|
416bec04a1 | ||
|
|
f747d6725e | ||
|
|
8846918e55 | ||
|
|
e347aefcd9 | ||
|
|
0a0676af3a | ||
|
|
7a69b63b4f | ||
|
|
fab5626741 | ||
|
|
c6832d6478 | ||
|
|
f451597746 | ||
|
|
a92e5b5156 | ||
|
|
786b3b26ea | ||
|
|
2e9fd2fd79 | ||
|
|
3be2b9893b | ||
|
|
7f6bed4f5f | ||
|
|
37322f284a | ||
|
|
4106bb1792 | ||
|
|
229db1242a | ||
|
|
8201b42135 | ||
|
|
24818575dc | ||
|
|
213050e7f2 | ||
|
|
1efaf4e3ea | ||
|
|
5532147992 | ||
|
|
550b43094d | ||
|
|
5e831837c8 | ||
|
|
480e88a088 | ||
|
|
81cbcbe8af | ||
|
|
5649634809 | ||
|
|
69a253f738 | ||
|
|
fbc8cfddb8 | ||
|
|
3e1024804c | ||
|
|
cf0b51be00 | ||
|
|
5b295a2a8f | ||
|
|
ebb3d7a149 | ||
|
|
73ab348d11 | ||
|
|
8ce17086f8 | ||
|
|
45cffc93b1 | ||
|
|
71cf4d5a34 | ||
|
|
b79a4154af | ||
|
|
ff818f8472 | ||
|
|
b540d32ca3 | ||
|
|
895a11d45f | ||
|
|
bdafae6132 | ||
|
|
f45d9ce6da | ||
|
|
7456e2c8f5 | ||
|
|
fc51b50fe6 | ||
|
|
b31aa26975 | ||
|
|
49801e1b14 | ||
|
|
1453464cbd | ||
|
|
b9be8ff13d | ||
|
|
9663c3cadd | ||
|
|
da5490bc0a | ||
|
|
df9b1b9fbb | ||
|
|
6a0a3ded13 | ||
|
|
ac7f4a08c3 | ||
|
|
e5ff5e3364 | ||
|
|
f0ac19fcc1 | ||
|
|
ce23d34923 | ||
|
|
bdbba87f5a | ||
|
|
fa8ef0477b | ||
|
|
3e9ec4cfb6 | ||
|
|
ad1c9d8129 | ||
|
|
7aaac4c13c | ||
|
|
a6c71c3d54 | ||
|
|
05714f38dc | ||
|
|
4ef5e4d8ae | ||
|
|
5fd0e1d6fe | ||
|
|
97faf5bef3 | ||
|
|
bd77128733 | ||
|
|
935debc6bf | ||
|
|
699ed19b6a | ||
|
|
126e5a94c2 | ||
|
|
792bd36ea7 | ||
|
|
04dfee1b43 | ||
|
|
6a08cfeea9 | ||
|
|
29a0f6c9f6 | ||
|
|
e2bdf26d82 | ||
|
|
1643949110 | ||
|
|
577dabd727 | ||
|
|
ed4bd0693f | ||
|
|
0f2982b34d | ||
|
|
0954094800 | ||
|
|
22dcfafc62 | ||
|
|
dd34051426 | ||
|
|
6feaf2cb8a | ||
|
|
603f64ea92 | ||
|
|
568f9ab0d0 | ||
|
|
a2ff1c33e6 | ||
|
|
84452e9fc0 | ||
|
|
5a6ae453cf | ||
|
|
cde3e3361d | ||
|
|
25f01192a4 | ||
|
|
bacb08ec65 | ||
|
|
36c905ee1c | ||
|
|
6b9280534a | ||
|
|
f94c6e850f | ||
|
|
f4cecf6906 | ||
|
|
a81d3766f8 | ||
|
|
7182c2e66d | ||
|
|
503316bc70 | ||
|
|
d5af59f2c7 | ||
|
|
cb3ac1ad3a | ||
|
|
e161080e4c | ||
|
|
ceab7f917b | ||
|
|
41d8066c4c | ||
|
|
e373cbd776 | ||
|
|
05ea6b6b10 | ||
|
|
28eed3ae71 | ||
|
|
535c67ac9b | ||
|
|
68027a6e15 | ||
|
|
12dea6b499 | ||
|
|
d8bd078a02 | ||
|
|
439bc109b0 | ||
|
|
b1ec93ceb5 | ||
|
|
44b9aa0e48 | ||
|
|
0a59063027 | ||
|
|
5db4608abd | ||
|
|
6ece2aa6cb | ||
|
|
95ee45f666 | ||
|
|
556e90b8d8 | ||
|
|
42841e6247 | ||
|
|
324c82248a | ||
|
|
94f5a47918 | ||
|
|
3f70990956 | ||
|
|
d77635e572 | ||
|
|
0f26f1b751 | ||
|
|
456bc22138 | ||
|
|
b6fe91e396 | ||
|
|
0fb4d26949 | ||
|
|
3b3583a416 | ||
|
|
cac1d576c8 | ||
|
|
9cba0d0a48 | ||
|
|
3bf11e9dc0 | ||
|
|
edbb160ac6 | ||
|
|
ee111dc63c | ||
|
|
b06edb756a | ||
|
|
fd745494e0 | ||
|
|
702bf3f479 | ||
|
|
6adc02a91f | ||
|
|
55bae5a130 | ||
|
|
1aae817b17 | ||
|
|
8afc8c23fb | ||
|
|
36e473b139 | ||
|
|
e529723f86 | ||
|
|
6c5112c142 | ||
|
|
77ac68a603 | ||
|
|
e816f40872 | ||
|
|
65313f114b | ||
|
|
ef1f1342f5 | ||
|
|
9205fe6c08 | ||
|
|
75df3e81fe | ||
|
|
47fffbadb5 | ||
|
|
78354473fa | ||
|
|
8d7efb44b5 | ||
|
|
cab00b3d8c | ||
|
|
8efab01336 | ||
|
|
66feb8beb4 | ||
|
|
ca4cccffeb | ||
|
|
51b1760c50 | ||
|
|
42e1bda365 | ||
|
|
194b6b557a | ||
|
|
e7bc439997 | ||
|
|
ef76cce0ac | ||
|
|
63eec6d969 | ||
|
|
719ae74c06 | ||
|
|
37cf424eb8 | ||
|
|
3a87f7ba9d | ||
|
|
275428d825 | ||
|
|
7b9dac86ca | ||
|
|
46496ee2cc | ||
|
|
110cc402cc | ||
|
|
587f006259 | ||
|
|
ec45c0df81 | ||
|
|
dc575aeca4 | ||
|
|
a3f790f000 | ||
|
|
4e9e188d02 | ||
|
|
abb17efae4 | ||
|
|
16be990502 | ||
|
|
d1a496f9a3 | ||
|
|
cd730fcfef | ||
|
|
1b65bf665b | ||
|
|
6f4735790c | ||
|
|
040666f89d | ||
|
|
227cb800c3 | ||
|
|
0052569a14 | ||
|
|
f7561c4888 | ||
|
|
92200f19e7 | ||
|
|
4c01f18a62 | ||
|
|
a6ce3e49fe | ||
|
|
1b3a5d1bf6 | ||
|
|
db7bdc63c8 | ||
|
|
7aca550f02 | ||
|
|
b60980b3fd | ||
|
|
b578c2c584 | ||
|
|
fc45bd624e | ||
|
|
17be3d9d2c | ||
|
|
5b3a38a7bc | ||
|
|
4403835d50 | ||
|
|
b7a3d3eb46 | ||
|
|
29846b22fe |
20
CHANGELOG
20
CHANGELOG
@@ -1,3 +1,23 @@
|
|||||||
|
KeePassDX(2.5RC2)
|
||||||
|
* Replacement of Spongy Castle by Bouncy Castle
|
||||||
|
* Update Autofill compatibility
|
||||||
|
* Fix Magikeyboard "Go" action
|
||||||
|
* Fix KeeWeb database opening
|
||||||
|
* Fix default username
|
||||||
|
* Fix themes
|
||||||
|
* Fix small issues
|
||||||
|
|
||||||
|
KeePassDX(2.5RC1)
|
||||||
|
* Add write permission to keep compatibility with old file managers
|
||||||
|
* Fix autofill for apps
|
||||||
|
* Auto search for autofill
|
||||||
|
* New keyfile input
|
||||||
|
* Icon to hide keyfile input
|
||||||
|
* New lock button
|
||||||
|
* Setting to hide lock button in user interface
|
||||||
|
* Clickable links in notes
|
||||||
|
* Fix autofill for key-value pairs
|
||||||
|
|
||||||
KeePassDX(2.5beta30)
|
KeePassDX(2.5beta30)
|
||||||
* Fix Lock after screen off (wait 1.5 seconds)
|
* Fix Lock after screen off (wait 1.5 seconds)
|
||||||
* Upgrade autofill algorithm
|
* Upgrade autofill algorithm
|
||||||
|
|||||||
177
LICENSES/LICENSE_ANDROID_BACKUP_SERVICE
Normal file
177
LICENSES/LICENSE_ANDROID_BACKUP_SERVICE
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
Terms of Service
|
||||||
|
|
||||||
|
This is the terms of service for the Android Backup Service.
|
||||||
|
|
||||||
|
1. Your relationship with Google
|
||||||
|
|
||||||
|
1.1 Your use of the Android Backup Service (referred to as the "Service" in this document) is subject to the terms of a legal agreement between you and Google. "Google" means Google LLC, whose principal place of business is at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. This document explains how the agreement is made up, and sets out some of the terms of that agreement.
|
||||||
|
|
||||||
|
1.2 Unless otherwise agreed in writing with Google, your agreement with Google will always include, at a minimum, the terms and conditions set out in this document. These are referred to below as the "Terms".
|
||||||
|
|
||||||
|
1.3 The Terms form a legally binding agreement between you and Google in relation to your use of the Service. It is important that you take the time to read them carefully.
|
||||||
|
|
||||||
|
2. Accepting the Terms
|
||||||
|
|
||||||
|
2.1 In order to use the Service, you must first agree to the Terms. You may not use the Service if you do not accept the Terms.
|
||||||
|
|
||||||
|
2.2 You can accept the Terms by clicking to accept or agree to the Terms, where this option is made available to you by Google.
|
||||||
|
|
||||||
|
2.3 You may not use the Service and may not accept the Terms if you are not of legal age to form a binding contract with Google.
|
||||||
|
|
||||||
|
2.4 You represent that you have full power, capacity and authority to accept these Terms. If you are accepting on behalf of your employer or another entity, you represent that you have full legal authority to bind your employer or such entity to these Terms. If you don't have the legal authority to bind, please ensure that an authorized person from your entity consents to and accepts these Terms.
|
||||||
|
|
||||||
|
3. Provision of the Service by Google
|
||||||
|
|
||||||
|
3.1 Google has subsidiaries and affiliated legal entities around the world ("Subsidiaries and Affiliates"). Sometimes, these companies will be providing the Service to you on behalf of Google itself. You acknowledge and agree that Subsidiaries and Affiliates will be entitled to provide the Service to you.
|
||||||
|
|
||||||
|
3.2 Google is constantly innovating in order to provide the best possible experience for its users. You acknowledge and agree that the form and nature of the Service which Google provides may change from time to time without prior notice to you.
|
||||||
|
|
||||||
|
3.3 As part of this continuing innovation, you acknowledge and agree that Google may stop (permanently or temporarily) providing the Service (or any features within the Service) to you or to users generally at Google's sole discretion, without prior notice to you. You may stop using the Service at any time. You do not need to specifically inform Google when you stop using the Service.
|
||||||
|
|
||||||
|
3.4 You acknowledge and agree that if Google disables your Backup Service Key, you and the Android application(s) you developed ("Application(s)") may be prevented from accessing the Service and any content that is stored with the Service.
|
||||||
|
|
||||||
|
3.5 You acknowledge and agree that Google may set a fixed upper limit on the number of backup transmissions you may send or receive through the Service or on the amount of storage space used for the provision of the Service at any time, at Google's discretion. You agree to abide by any such fixed upper limits.
|
||||||
|
|
||||||
|
4. Use of the Service by you
|
||||||
|
|
||||||
|
4.1 In order to access the Service, you must have a unique application identifier ("Package Name") for your Application as described in the documentation for the Service.
|
||||||
|
|
||||||
|
4.2 After supplying Google with the Package Name and accepting the Terms, you will be issued an alphanumeric key ("Backup Service Key") assigned to you by Google that is uniquely associated with your Application. Your Application must include this Backup Service Key as described in the documentation for the Service.
|
||||||
|
|
||||||
|
4.3 There is currently no limit to the number of Backup Service Keys you may obtain in this manner provided that you use a different Package Name for each Backup Service Key you obtain. You agree that each Backup Service Key is only valid for Applications with the corresponding Package Name. You agree that Google may, in its sole discretion, impose a limit on the number of Backup Service Keys that may be obtained in the future. You agree that your continued use of any of the Backup Service Keys assigned by Google, or distribution of any Applications using such Backup Service Keys, constitutes your continued agreement to these Terms.
|
||||||
|
|
||||||
|
4.4 You agree to use the Service only for purposes that are permitted by (a) the Terms and (b) any applicable law, regulation, third-party terms of service, or generally accepted practices or guidelines in the relevant jurisdictions (including any laws regarding the export of data or software to and from the United States or other relevant countries).
|
||||||
|
|
||||||
|
4.5 You agree not to access (or attempt to access) any of the Service by any means other than through the interfaces, methods, and APIs that are provided by Google, unless you have been specifically allowed to do so in a separate agreement with Google.
|
||||||
|
|
||||||
|
4.6 You agree that you will not engage in any activity that interferes with or disrupts the Service (or the servers and networks which are connected to the Service), or the servers or networks of any third-party.
|
||||||
|
|
||||||
|
4.7 You agree that your use of the Service will be in compliance with any documentation guidelines provided by Google and that failure to comply with the documentation guidelines may result in the disabling of the Backup Service Key(s) for your Application(s).
|
||||||
|
|
||||||
|
4.8 Unless you have been specifically permitted to do so in a separate agreement with Google, you agree that you will not reproduce, duplicate, copy, sell, trade or resell (a) use of the Service, or (b) access to the Service.
|
||||||
|
|
||||||
|
4.9 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) your and your Application's use of the Service, any breach of your obligations under the Terms, and for the consequences (including any loss or damage which Google may suffer) of any such breach.
|
||||||
|
|
||||||
|
4.10 You agree that in your use of the Service, you and your Applications will protect the privacy and legal rights of users. You must provide legally adequate privacy notice and protection for users whose data your Applications back up to the Service. Further, your Application may only use that information for the limited purpose of backing up the data to the Service unless the user has given you permission for further use. If the user has not given you permission to back up information to the Service, you may not transmit such information to the Service.
|
||||||
|
|
||||||
|
4.11 You agree that you and your Applications will not transmit or store sensitive user information, such as user names, passwords, or credit card numbers, through the Service.
|
||||||
|
|
||||||
|
5. Security
|
||||||
|
|
||||||
|
5.1 You agree and understand that you are responsible for maintaining the security associated with any information you provide to access the Service as well as of the Backup Service Key(s) assigned to you by Google. You agree that only you are authorized to use the Backup Service Key(s) assigned to you.
|
||||||
|
|
||||||
|
5.2 Accordingly, you agree that you will be solely responsible to Google for all activities that occur in connection with your access to the Service, as well as the Backup Service Key.
|
||||||
|
|
||||||
|
5.3 If you become aware of any unauthorized use of your Backup Service Key(s) you agree to notify Google immediately.
|
||||||
|
|
||||||
|
6. Privacy and your personal information
|
||||||
|
|
||||||
|
6.1 For information about Google's data protection practices, please read Google's privacy policy at http://www.google.com/privacy.html. This policy explains how Google treats your personal information when you use the Service.
|
||||||
|
|
||||||
|
6.2 You agree to the use of your data in accordance with Google's privacy policies.
|
||||||
|
|
||||||
|
7. Content in the Service
|
||||||
|
|
||||||
|
7.1 You agree that you are solely responsible for (and that Google has no responsibility to you or to any third party for) any Content that you or your Applications transmit or store through the Service and for the consequences of your actions (including any loss or damage which Google may suffer) by doing so. You agree that you are solely responsible for (A) any Content that is transmitted through the Service by your Applications, and (B) any Content that Devices retrieve from the Service by virtue of your Applications. For purposes of the Terms, "Content" means information such as data, messages, settings information, written text, computer software, music, audio files or other sounds, photographs, videos or other images. "Device(s)" means device(s) powered by the Android operating system.
|
||||||
|
|
||||||
|
7.2 You agree that you will not transmit any Content through the Service that is copyrighted, protected by trade secret or otherwise subject to third party proprietary rights, including patent, privacy and publicity rights, unless you are the owner of such rights or have permission from their rightful owner to transmit the Content through the Service.
|
||||||
|
|
||||||
|
8. Proprietary rights
|
||||||
|
|
||||||
|
8.1 You acknowledge and agree that Google (or Google's licensors) own all legal right, title and interest in and to the Service, including any intellectual property rights which subsist in the Service (whether those rights happen to be registered or not, and wherever in the world those rights may exist).
|
||||||
|
|
||||||
|
8.2 Unless you have agreed otherwise in writing with Google, nothing in the Terms gives you a right to use any of Google's trade names, trademarks, service marks, logos, domain names, and other distinctive brand features.
|
||||||
|
|
||||||
|
8.3 If you have been given an explicit right to use any of these brand features in a separate written agreement with Google, then you agree that your use of such features shall be in compliance with that agreement, any applicable provisions of the Terms, and Google's brand feature use guidelines as updated from time to time. These guidelines can be viewed online at http://www.google.com/permissions/ guidelines.html (or such other URL as Google may provide for this purpose from time to time).
|
||||||
|
|
||||||
|
8.4 You agree that you shall not remove, obscure, or alter any proprietary rights notices (including copyright, trade mark notices) which may be affixed to or contained within the Service.
|
||||||
|
|
||||||
|
9. License from Google
|
||||||
|
|
||||||
|
9.1 Subject to terms and conditions of these Terms, Google gives you a personal, worldwide, royalty-free, non-assignable and non-exclusive license to use the Service as provided to you by Google. This license is for the sole purpose of enabling you to use and enjoy the benefit of the Service as provided by Google, in the manner permitted by the Terms.
|
||||||
|
|
||||||
|
9.2 You may not (and you may not permit anyone else to) copy, modify, create a derivative work of, reverse engineer, decompile or otherwise attempt to extract the source code from the Service or any part thereof, unless this is expressly permitted or required by law, or unless you have been specifically told that you may do so by Google, in writing.
|
||||||
|
|
||||||
|
9.3 Unless Google has given you specific written permission to do so, you may not assign (or grant a sub-license of) your rights to use the Service, grant a security interest in or over your rights to use the Service, or otherwise transfer any part of your rights to use the Service.
|
||||||
|
|
||||||
|
10. Your code
|
||||||
|
|
||||||
|
10.1 Google claims no ownership or control over any source code written by you to be used with the Service. You retain copyright and any other rights you already hold in this code, and you are responsible for protecting those rights, as appropriate.
|
||||||
|
|
||||||
|
11. Ending your relationship with Google
|
||||||
|
|
||||||
|
11.1 The Terms will continue to apply until terminated by either you or Google as set out below.
|
||||||
|
|
||||||
|
11.2 You may terminate your legal agreement with Google by discontinuing your use of the Service at any time.
|
||||||
|
|
||||||
|
11.3 Google may, at any time, terminate its legal agreement with you if:
|
||||||
|
|
||||||
|
(A) you have breached any provision of the Terms (or have acted in manner which clearly shows that you do not intend to, or are unable to comply with the provisions of the Terms); or
|
||||||
|
|
||||||
|
(B) Google is required to do so by law (for example, where the provision of the Service to you is, or becomes, unlawful); or
|
||||||
|
|
||||||
|
(C) Google is transitioning to no longer providing the Service; or
|
||||||
|
|
||||||
|
(D) your Application fails to meet the documentation guidelines provided by Google.
|
||||||
|
|
||||||
|
11.4 Nothing in this Section shall affect Google's rights regarding provision of the Service under Section 3 of the Terms.
|
||||||
|
|
||||||
|
11.5 When these Terms come to an end, all of the legal rights, obligations and liabilities that you and Google have benefited from, been subject to (or which have accrued over time whilst the Terms have been in force) or which are expressed to continue indefinitely, shall be unaffected by this cessation, and the provisions of Sections 12, 13 and Paragraph 16 shall continue to apply to such rights, obligations and liabilities indefinitely.
|
||||||
|
|
||||||
|
12. EXCLUSION OF WARRANTIES
|
||||||
|
|
||||||
|
12.1 NOTHING IN THESE TERMS, INCLUDING SECTIONS 12 AND 13, SHALL EXCLUDE OR LIMIT GOOGLE'S WARRANTY OR LIABILITY FOR LOSSES WHICH MAY NOT BE LAWFULLY EXCLUDED OR LIMITED BY APPLICABLE LAW. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR CONDITIONS OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR LOSS OR DAMAGE CAUSED BY NEGLIGENCE, BREACH OF CONTRACT OR BREACH OF IMPLIED TERMS, OR INCIDENTAL OR CONSEQUENTIAL DAMAGES. ACCORDINGLY, ONLY THE LIMITATIONS WHICH ARE LAWFUL IN YOUR JURISDICTION WILL APPLY TO YOU AND OUR LIABILITY WILL BE LIMITED TO THE MAXIMUM EXTENT PERMITTED BY LAW.
|
||||||
|
|
||||||
|
12.2 YOU EXPRESSLY UNDERSTAND AND AGREE THAT YOUR USE OF THE SERVICE IS AT YOUR SOLE RISK AND THAT THE SERVICE AND CONTENT ARE PROVIDED "AS IS" AND "AS AVAILABLE".
|
||||||
|
|
||||||
|
12.3 IN PARTICULAR, GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS DO NOT REPRESENT OR WARRANT TO YOU THAT:
|
||||||
|
|
||||||
|
(A) YOUR USE OF THE SERVICE WILL MEET YOUR REQUIREMENTS,
|
||||||
|
|
||||||
|
(B) YOUR USE OF THE SERVICE WILL BE UNINTERRUPTED, TIMELY, SECURE OR FREE FROM ERROR, AND
|
||||||
|
|
||||||
|
(C) THAT DEFECTS IN THE OPERATION OR FUNCTIONALITY OF ANY SOFTWARE PROVIDED TO YOU AS PART OF THE SERVICE WILL BE CORRECTED.
|
||||||
|
|
||||||
|
12.4 NO ADVICE OR INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM GOOGLE OR THROUGH OR FROM THE SERVICE SHALL CREATE ANY WARRANTY NOT EXPRESSLY STATED IN THE TERMS.
|
||||||
|
|
||||||
|
12.5 GOOGLE FURTHER EXPRESSLY DISCLAIMS ALL WARRANTIES AND CONDITIONS OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
||||||
|
|
||||||
|
13. LIMITATION OF LIABILITY
|
||||||
|
|
||||||
|
13.1 SUBJECT TO OVERALL PROVISION IN PARAGRAPH 12.1 ABOVE, YOU EXPRESSLY UNDERSTAND AND AGREE THAT GOOGLE, ITS SUBSIDIARIES AND AFFILIATES, AND ITS LICENSORS SHALL NOT BE LIABLE TO YOU FOR:
|
||||||
|
|
||||||
|
(A) ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL CONSEQUENTIAL OR EXEMPLARY DAMAGES WHICH MAY BE INCURRED BY YOU, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY. THIS SHALL INCLUDE, BUT NOT BE LIMITED TO, ANY LOSS OF PROFIT (WHETHER INCURRED DIRECTLY OR INDIRECTLY), ANY LOSS OF GOODWILL OR BUSINESS REPUTATION, ANY LOSS OF DATA SUFFERED, COST OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICE, OR OTHER INTANGIBLE LOSS;
|
||||||
|
|
||||||
|
(B) ANY LOSS OR DAMAGE WHICH MAY BE INCURRED BY YOU, INCLUDING BUT NOT LIMITED TO LOSS OR DAMAGE AS A RESULT OF:
|
||||||
|
|
||||||
|
(I) ANY CHANGES WHICH GOOGLE MAY MAKE TO THE SERVICE, OR FOR ANY PERMANENT OR TEMPORARY CESSATION IN THE PROVISION OF THE SERVICE (OR ANY FEATURES WITHIN THE SERVICE);
|
||||||
|
|
||||||
|
(II) THE DELETION OF, CORRUPTION OF, OR FAILURE TO STORE, ANY CONTENT AND OTHER COMMUNICATIONS DATA MAINTAINED OR TRANSMITTED BY OR THROUGH YOUR USE OF THE SERVICE;
|
||||||
|
|
||||||
|
(III) YOUR FAILURE TO PROVIDE GOOGLE WITH ACCURATE ACCOUNT INFORMATION; OR
|
||||||
|
|
||||||
|
(IV) YOUR FAILURE TO KEEP YOUR PASSWORD OR ACCOUNT DETAILS SECURE AND CONFIDENTIAL.
|
||||||
|
|
||||||
|
13.2 THE LIMITATIONS ON GOOGLE'S LIABILITY TO YOU IN PARAGRAPH 13.1 ABOVE SHALL APPLY WHETHER OR NOT GOOGLE HAS BEEN ADVISED OF OR SHOULD HAVE BEEN AWARE OF THE POSSIBILITY OF ANY SUCH LOSSES ARISING.
|
||||||
|
|
||||||
|
14. Indemnification
|
||||||
|
|
||||||
|
14.1 You agree to hold harmless and indemnify Google, and its subsidiaries, affiliates, officers, agents, employees, or licensors from and against any third party claim arising from or in any way related to (a) your breach of the Terms, (b) your use of the Service, or (c) your violation of applicable laws, rules or regulations in connection with the Service, including any liability or expense arising from all claims, losses, damages (actual and consequential), suits, judgments, litigation costs and attorneys' fees, of every kind and nature. In such a case, Google will provide you with written notice of such claim, suit or action.
|
||||||
|
|
||||||
|
15. Changes to the Terms
|
||||||
|
|
||||||
|
15.1 Due to things like changes to the law or changes to functionality offered through the Service, Google may need to change these Terms from time to time. You should look at the Terms regularly. We'll post notice of the modified Terms within, or through, the Service. Once the modified Terms are posted, the changes will become effective immediately, and you are deemed to have accepted the modified Terms if you continue to use the Service. If you do not agree to the modified Terms for the Service, please stop using the Service.
|
||||||
|
|
||||||
|
16. General legal terms
|
||||||
|
|
||||||
|
16.1 The Terms constitute the whole legal agreement between you and Google and govern your use of the Service (but excluding any service which Google may provide to you under a separate written agreement), and completely replace any prior agreements between you and Google in relation to the Service.
|
||||||
|
|
||||||
|
16.2 You agree that Google may provide you with notices, including those regarding changes to the Terms, by email, regular mail, or postings on the Service.
|
||||||
|
|
||||||
|
16.3 You agree that if Google does not exercise or enforce any legal right or remedy which is contained in the Terms (or which Google has the benefit of under any applicable law), this will not be taken to be a formal waiver of Google's rights and that those rights or remedies will still be available to Google.
|
||||||
|
|
||||||
|
16.4 If any court of law, having the jurisdiction to decide on this matter, rules that any provision of these Terms is invalid, then that provision will be removed from the Terms without affecting the rest of the Terms. The remaining provisions of the Terms will continue to be valid and enforceable.
|
||||||
|
|
||||||
|
16.5 You acknowledge and agree that each member of the group of companies of which Google is the parent shall be third party beneficiaries to the Terms and that such other companies shall be entitled to directly enforce, and rely upon, any provision of the Terms which confers a benefit on (or rights in favor of) them. Other than this, no other person or company shall be third party beneficiaries to the Terms.
|
||||||
|
|
||||||
|
16.6 The Terms, and your relationship with Google under the Terms, shall be governed by the laws of the State of California without regard to its conflict of laws provisions. You and Google agree to submit to the exclusive jurisdiction of the courts located within the county of Santa Clara, California to resolve any legal matter arising from the Terms. Notwithstanding this, you agree that Google shall still be allowed to apply for injunctive remedies (or an equivalent type of urgent legal relief) in any jurisdiction.
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
* Material design with **themes**
|
* Material design with **themes**
|
||||||
* **AutoFill** and Integration
|
* **AutoFill** and Integration
|
||||||
* Field filling **keyboard**
|
* Field filling **keyboard**
|
||||||
|
* **History** of each entry
|
||||||
* Precise management of **settings**
|
* Precise management of **settings**
|
||||||
* Code written in **native language** *(Kotlin / Java / JNI / C)*
|
* Code written in **native language** *(Kotlin / Java / JNI / C)*
|
||||||
|
|
||||||
@@ -53,9 +54,9 @@ You can contribute in different ways to help us on our work.
|
|||||||
alt="Get it on Google Play"
|
alt="Get it on Google Play"
|
||||||
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
|
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
|
||||||
|
|
||||||
## F.A.Q.
|
## Frequently Asked Questions
|
||||||
|
|
||||||
Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassDX/wiki/F.A.Q.)
|
Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/wiki/FAQ)
|
||||||
|
|
||||||
## Other devices
|
## Other devices
|
||||||
|
|
||||||
|
|||||||
@@ -11,14 +11,15 @@ android {
|
|||||||
applicationId "com.kunzisoft.keepass"
|
applicationId "com.kunzisoft.keepass"
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode = 30
|
versionCode = 32
|
||||||
versionName = "2.5beta30"
|
versionName = "2.5RC2"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||||
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
||||||
|
|
||||||
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
|
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
|
||||||
|
manifestPlaceholders = [ googleAndroidBackupAPIKey:"" ]
|
||||||
|
|
||||||
kapt {
|
kapt {
|
||||||
arguments {
|
arguments {
|
||||||
@@ -44,32 +45,36 @@ android {
|
|||||||
dexOptions {
|
dexOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "tier"
|
flavorDimensions "version"
|
||||||
productFlavors {
|
productFlavors {
|
||||||
libre {
|
libre {
|
||||||
|
dimension "version"
|
||||||
applicationIdSuffix = ".libre"
|
applicationIdSuffix = ".libre"
|
||||||
buildConfigField "String", "BUILD_VERSION", "\"libre\""
|
buildConfigField "String", "BUILD_VERSION", "\"libre\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "false"
|
buildConfigField "boolean", "CLOSED_STORE", "false"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
||||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||||
|
|
||||||
}
|
}
|
||||||
pro {
|
pro {
|
||||||
|
dimension "version"
|
||||||
applicationIdSuffix = ".pro"
|
applicationIdSuffix = ".pro"
|
||||||
buildConfigField "String", "BUILD_VERSION", "\"pro\""
|
buildConfigField "String", "BUILD_VERSION", "\"pro\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{}"
|
||||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||||
|
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIZiXvrQCzSV9LNI6-p7cjTKENZLHIrz_zaqZuQQ" ]
|
||||||
}
|
}
|
||||||
free {
|
free {
|
||||||
|
dimension "version"
|
||||||
applicationIdSuffix = ".free"
|
applicationIdSuffix = ".free"
|
||||||
buildConfigField "String", "BUILD_VERSION", "\"free\""
|
buildConfigField "String", "BUILD_VERSION", "\"free\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "false"
|
buildConfigField "boolean", "FULL_VERSION", "false"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
||||||
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
buildConfigField "String[]", "ICON_PACKS_DISABLED", "{}"
|
||||||
|
manifestPlaceholders = [ googleAndroidBackupAPIKey:"AEdPqrEAAAAIbRfbV8fHLItXo8OcHwrO0sSNblqhPwkc0DPTqg" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,32 +90,31 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def spongycastleVersion = "1.58.0.0"
|
|
||||||
def room_version = "2.2.5"
|
def room_version = "2.2.5"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'androidx.preference:preference:1.1.0'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
implementation 'androidx.legacy:legacy-preference-v14:1.0.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'androidx.biometric:biometric:1.0.1'
|
implementation 'androidx.biometric:biometric:1.0.1'
|
||||||
|
implementation "androidx.core:core-ktx:1.2.0"
|
||||||
// To upgrade with style
|
// To upgrade with style
|
||||||
implementation 'com.google.android.material:material:1.0.0'
|
implementation 'com.google.android.material:material:1.0.0'
|
||||||
|
// Database
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
kapt "androidx.room:room-compiler:$room_version"
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
|
// Crypto
|
||||||
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.65'
|
||||||
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
|
||||||
// Time
|
// Time
|
||||||
implementation 'joda-time:joda-time:2.9.9'
|
implementation 'joda-time:joda-time:2.9.9'
|
||||||
// Color
|
// Color
|
||||||
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
|
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.3'
|
||||||
// Education
|
// Education
|
||||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
|
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.0'
|
||||||
// Apache Commons Collections
|
// Apache Commons Collections
|
||||||
implementation 'commons-collections:commons-collections:3.2.1'
|
implementation 'commons-collections:commons-collections:3.2.1'
|
||||||
implementation 'org.apache.commons:commons-io:1.3.2'
|
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||||
@@ -119,4 +123,7 @@ dependencies {
|
|||||||
// Icon pack
|
// Icon pack
|
||||||
implementation project(path: ':icon-pack-classic')
|
implementation project(path: ':icon-pack-classic')
|
||||||
implementation project(path: ':icon-pack-material')
|
implementation project(path: ':icon-pack-material')
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
androidTestImplementation 'junit:junit:4.12'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ import java.util.Random
|
|||||||
|
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.AndroidFinalKey
|
import com.kunzisoft.keepass.crypto.finalkey.AndroidAESKeyTransformer
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.NativeFinalKey
|
import com.kunzisoft.keepass.crypto.finalkey.NativeAESKeyTransformer
|
||||||
|
|
||||||
class FinalKeyTest : TestCase() {
|
class AESKeyTest : TestCase() {
|
||||||
private var mRand: Random? = null
|
private lateinit var mRand: Random
|
||||||
|
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
@@ -40,29 +40,28 @@ class FinalKeyTest : TestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun testNativeAndroid() {
|
fun testAES() {
|
||||||
// Test both an old and an even number to test my flip variable
|
// Test both an old and an even number to test my flip variable
|
||||||
testNativeFinalKey(5)
|
testAESFinalKey(5)
|
||||||
testNativeFinalKey(6)
|
testAESFinalKey(6)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun testNativeFinalKey(rounds: Int) {
|
private fun testAESFinalKey(rounds: Long) {
|
||||||
val seed = ByteArray(32)
|
val seed = ByteArray(32)
|
||||||
val key = ByteArray(32)
|
val key = ByteArray(32)
|
||||||
val nativeKey: ByteArray
|
val nativeKey: ByteArray?
|
||||||
val androidKey: ByteArray
|
val androidKey: ByteArray?
|
||||||
|
|
||||||
mRand!!.nextBytes(seed)
|
mRand.nextBytes(seed)
|
||||||
mRand!!.nextBytes(key)
|
mRand.nextBytes(key)
|
||||||
|
|
||||||
val aKey = AndroidFinalKey()
|
val androidAESKey = AndroidAESKeyTransformer()
|
||||||
androidKey = aKey.transformMasterKey(seed, key, rounds.toLong())
|
androidKey = androidAESKey.transformMasterKey(seed, key, rounds)
|
||||||
|
|
||||||
val nKey = NativeFinalKey()
|
val nativeAESKey = NativeAESKeyTransformer()
|
||||||
nativeKey = nKey.transformMasterKey(seed, key, rounds.toLong())
|
nativeKey = nativeAESKey.transformMasterKey(seed, key, rounds)
|
||||||
|
|
||||||
assertArrayEquals("Does not match", androidKey, nativeKey)
|
assertArrayEquals("Does not match", androidKey, nativeKey)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,6 +80,4 @@ class AESTest : TestCase() {
|
|||||||
|
|
||||||
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
|
assertArrayEquals("Arrays differ on size: $dataSize", outAndroid, outNative)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import junit.framework.TestCase
|
|||||||
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
||||||
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
|
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
|
||||||
|
|
||||||
class HashedBlock : TestCase() {
|
class HashedBlockTest : TestCase() {
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun testBlockAligned() {
|
fun testBlockAligned() {
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePassDX.
|
|
||||||
*
|
|
||||||
* KeePassDX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePassDX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.tests.utils
|
|
||||||
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.utils.StringUtil
|
|
||||||
|
|
||||||
import junit.framework.TestCase
|
|
||||||
|
|
||||||
class StringUtilTest : TestCase() {
|
|
||||||
private val text = "AbCdEfGhIj"
|
|
||||||
private val search = "BcDe"
|
|
||||||
private val badSearch = "Ed"
|
|
||||||
|
|
||||||
private val repText = "AbCtestingaBc"
|
|
||||||
private val repSearch = "ABc"
|
|
||||||
private val repSearchBad = "CCCCCC"
|
|
||||||
private val repNew = "12345"
|
|
||||||
private val repResult = "12345testing12345"
|
|
||||||
|
|
||||||
fun testIndexOfIgnoreCase1() {
|
|
||||||
assertEquals(1, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testIndexOfIgnoreCase2() {
|
|
||||||
assertEquals(-1f, StringUtil.indexOfIgnoreCase(text, search, Locale.ENGLISH).toFloat(), 2f)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testIndexOfIgnoreCase3() {
|
|
||||||
assertEquals(-1, StringUtil.indexOfIgnoreCase(text, badSearch, Locale.ENGLISH))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testReplaceAllIgnoresCase1() {
|
|
||||||
assertEquals(repResult, StringUtil.replaceAllIgnoresCase(repText, repSearch, repNew, Locale.ENGLISH))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testReplaceAllIgnoresCase2() {
|
|
||||||
assertEquals(repText, StringUtil.replaceAllIgnoresCase(repText, repSearchBad, repNew, Locale.ENGLISH))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.kunzisoft.keepass.tests.utils
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
import junit.framework.TestCase
|
||||||
|
|
||||||
|
class UnsignedIntTest: TestCase() {
|
||||||
|
|
||||||
|
fun testUInt() {
|
||||||
|
val standardInt = UnsignedInt(15).toInt()
|
||||||
|
assertEquals(15, standardInt)
|
||||||
|
val unsignedInt = UnsignedInt(-1).toLong()
|
||||||
|
assertEquals(4294967295L, unsignedInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testMaxValue() {
|
||||||
|
val maxValue = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
assertEquals(4294967295L, maxValue)
|
||||||
|
val longValue = UnsignedInt.fromLong(4294967295L).toLong()
|
||||||
|
assertEquals(longValue, maxValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testLong() {
|
||||||
|
val longValue = UnsignedInt.fromLong(50L).toInt()
|
||||||
|
assertEquals(50, longValue)
|
||||||
|
val uIntLongValue = UnsignedInt.fromLong(4294967290).toLong()
|
||||||
|
assertEquals(4294967290, uIntLongValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,17 +17,18 @@
|
|||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests
|
package com.kunzisoft.keepass.tests.utils
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
|
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class StringDatabaseKDBUtilsTest : TestCase() {
|
class ValuesTest : TestCase() {
|
||||||
|
|
||||||
fun testReadWriteLongZero() {
|
fun testReadWriteLongZero() {
|
||||||
testReadWriteLong(0.toByte())
|
testReadWriteLong(0.toByte())
|
||||||
@@ -77,11 +78,10 @@ class StringDatabaseKDBUtilsTest : TestCase() {
|
|||||||
|
|
||||||
setArray(orig, value, 4)
|
setArray(orig, value, 4)
|
||||||
|
|
||||||
val one = bytes4ToInt(orig)
|
val one = bytes4ToUInt(orig)
|
||||||
val dest = intTo4Bytes(one)
|
val dest = uIntTo4Bytes(one)
|
||||||
|
|
||||||
assertArrayEquals(orig, dest)
|
assertArrayEquals(orig, dest)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
|
||||||
@@ -133,7 +133,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun testReadWriteByte(value: Byte) {
|
private fun testReadWriteByte(value: Byte) {
|
||||||
val dest: Byte = uIntToByte(byteToUInt(value))
|
val dest: Byte = UnsignedInt(UnsignedInt.fromByte(value)).toByte()
|
||||||
assert(value == dest)
|
assert(value == dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ class StringDatabaseKDBUtilsTest : TestCase() {
|
|||||||
|
|
||||||
val bos = ByteArrayOutputStream()
|
val bos = ByteArrayOutputStream()
|
||||||
val leos = LittleEndianDataOutputStream(bos)
|
val leos = LittleEndianDataOutputStream(bos)
|
||||||
leos.writeLong(ULONG_MAX_VALUE)
|
leos.writeLong(UnsignedLong.MAX_VALUE)
|
||||||
leos.close()
|
leos.close()
|
||||||
|
|
||||||
val uLongMax = bos.toByteArray()
|
val uLongMax = bos.toByteArray()
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.VIBRATE"/>
|
android:name="android.permission.VIBRATE"/>
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:maxSdkVersion="18"
|
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@@ -24,17 +23,15 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:name="com.kunzisoft.keepass.app.App"
|
android:name="com.kunzisoft.keepass.app.App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:theme="@style/KeepassDXStyle.Night"
|
android:theme="@style/KeepassDXStyle.Night"
|
||||||
tools:targetApi="n">
|
tools:targetApi="n">
|
||||||
<!-- TODO backup API Key -->
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
android:value="" />
|
android:value="${googleAndroidBackupAPIKey}" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
|
android:name="com.kunzisoft.keepass.activities.FileDatabaseSelectActivity"
|
||||||
android:theme="@style/KeepassDXStyle.SplashScreen"
|
android:theme="@style/KeepassDXStyle.SplashScreen"
|
||||||
@@ -138,12 +135,12 @@
|
|||||||
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
|
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
|
||||||
android:configChanges="keyboardHidden" />
|
android:configChanges="keyboardHidden" />
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
<activity android:name="com.kunzisoft.keepass.settings.SettingsAdvancedUnlockActivity" />
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
|
<activity android:name="com.kunzisoft.keepass.settings.AutofillSettingsActivity" />
|
||||||
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
<activity android:name="com.kunzisoft.keepass.magikeyboard.KeyboardLauncherActivity"
|
||||||
android:label="@string/keyboard_name"
|
android:label="@string/keyboard_name"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
|
<activity android:name="com.kunzisoft.keepass.settings.MagikeyboardSettingsActivity"
|
||||||
android:label="@string/keyboard_setting_label">
|
android:label="@string/keyboard_setting_label">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
|
|||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
|
|
||||||
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
|
||||||
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
import com.kunzisoft.keepass.timeout.ClipboardHelper
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
@@ -73,6 +72,7 @@ class EntryActivity : LockingActivity() {
|
|||||||
private var historyView: View? = null
|
private var historyView: View? = null
|
||||||
private var entryContentsView: EntryContentsView? = null
|
private var entryContentsView: EntryContentsView? = null
|
||||||
private var entryProgress: ProgressBar? = null
|
private var entryProgress: ProgressBar? = null
|
||||||
|
private var lockView: View? = null
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
|
|
||||||
private var mDatabase: Database? = null
|
private var mDatabase: Database? = null
|
||||||
@@ -124,6 +124,11 @@ class EntryActivity : LockingActivity() {
|
|||||||
entryContentsView = findViewById(R.id.entry_contents)
|
entryContentsView = findViewById(R.id.entry_contents)
|
||||||
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
||||||
entryProgress = findViewById(R.id.entry_progress)
|
entryProgress = findViewById(R.id.entry_progress)
|
||||||
|
lockView = findViewById(R.id.lock_button)
|
||||||
|
|
||||||
|
lockView?.setOnClickListener {
|
||||||
|
lockAndExit()
|
||||||
|
}
|
||||||
|
|
||||||
// Init the clipboard helper
|
// Init the clipboard helper
|
||||||
clipboardHelper = ClipboardHelper(this)
|
clipboardHelper = ClipboardHelper(this)
|
||||||
@@ -148,6 +153,13 @@ class EntryActivity : LockingActivity() {
|
|||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
// Show the lock button
|
||||||
|
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
// Get Entry from UUID
|
// Get Entry from UUID
|
||||||
try {
|
try {
|
||||||
val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY)
|
val keyEntry: NodeId<UUID>? = intent.getParcelableExtra(KEY_ENTRY)
|
||||||
@@ -359,7 +371,6 @@ class EntryActivity : LockingActivity() {
|
|||||||
taColorAccent.recycle()
|
taColorAccent.recycle()
|
||||||
}
|
}
|
||||||
val entryHistory = entry.getHistory()
|
val entryHistory = entry.getHistory()
|
||||||
// TODO isMainEntry = not an history
|
|
||||||
val showHistoryView = entryHistory.isNotEmpty()
|
val showHistoryView = entryHistory.isNotEmpty()
|
||||||
entryContentsView?.showHistory(showHistoryView)
|
entryContentsView?.showHistory(showHistoryView)
|
||||||
if (showHistoryView) {
|
if (showHistoryView) {
|
||||||
@@ -462,8 +473,7 @@ class EntryActivity : LockingActivity() {
|
|||||||
getString(R.string.entry_user_name)))
|
getString(R.string.entry_user_name)))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Launch autofill settings
|
performedNextEducation(entryActivityEducation, menu)
|
||||||
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!entryCopyEducationPerformed) {
|
if (!entryCopyEducationPerformed) {
|
||||||
@@ -474,9 +484,7 @@ class EntryActivity : LockingActivity() {
|
|||||||
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Open Keepass doc to create field references
|
performedNextEducation(entryActivityEducation, menu)
|
||||||
startActivity(Intent(Intent.ACTION_VIEW,
|
|
||||||
UriUtil.parse(getString(R.string.field_references_url))))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,10 +534,6 @@ class EntryActivity : LockingActivity() {
|
|||||||
!mReadOnly && mAutoSaveEnable)
|
!mReadOnly && mAutoSaveEnable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.menu_lock -> {
|
|
||||||
lockAndExit()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
R.id.menu_save_database -> {
|
R.id.menu_save_database -> {
|
||||||
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
private var entryEditContentsView: EntryEditContentsView? = null
|
private var entryEditContentsView: EntryEditContentsView? = null
|
||||||
private var entryEditAddToolBar: ActionMenuView? = null
|
private var entryEditAddToolBar: ActionMenuView? = null
|
||||||
private var saveView: View? = null
|
private var saveView: View? = null
|
||||||
|
private var lockView: View? = null
|
||||||
|
|
||||||
// Education
|
// Education
|
||||||
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
private var entryEditActivityEducation: EntryEditActivityEducation? = null
|
||||||
@@ -112,6 +113,12 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
.show(supportFragmentManager, "DatePickerFragment")
|
.show(supportFragmentManager, "DatePickerFragment")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lockView = findViewById(R.id.lock_button)
|
||||||
|
lockView?.setOnClickListener {
|
||||||
|
lockAndExit()
|
||||||
|
}
|
||||||
|
|
||||||
// Focus view to reinitialize timeout
|
// Focus view to reinitialize timeout
|
||||||
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
|
resetAppTimeoutWhenViewFocusedOrChanged(entryEditContentsView)
|
||||||
|
|
||||||
@@ -241,6 +248,16 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun populateViewsWithEntry(newEntry: Entry) {
|
private fun populateViewsWithEntry(newEntry: Entry) {
|
||||||
// Don't start the field reference manager, we want to see the raw ref
|
// Don't start the field reference manager, we want to see the raw ref
|
||||||
mDatabase?.stopManageEntry(newEntry)
|
mDatabase?.stopManageEntry(newEntry)
|
||||||
@@ -251,7 +268,10 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
// Set info in view
|
// Set info in view
|
||||||
entryEditContentsView?.apply {
|
entryEditContentsView?.apply {
|
||||||
title = newEntry.title
|
title = newEntry.title
|
||||||
username = if (newEntry.username.isEmpty()) mDatabase?.defaultUsername ?:"" else newEntry.username
|
username = if (mIsNew && newEntry.username.isEmpty())
|
||||||
|
mDatabase?.defaultUsername ?: ""
|
||||||
|
else
|
||||||
|
newEntry.username
|
||||||
url = newEntry.url
|
url = newEntry.url
|
||||||
password = newEntry.password
|
password = newEntry.password
|
||||||
expires = newEntry.expires
|
expires = newEntry.expires
|
||||||
@@ -416,10 +436,6 @@ class EntryEditActivity : LockingActivity(),
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.menu_lock -> {
|
|
||||||
lockAndExit()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
R.id.menu_save_database -> {
|
R.id.menu_save_database -> {
|
||||||
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,13 +26,11 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
@@ -48,9 +46,11 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
|||||||
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
import com.kunzisoft.keepass.adapters.FileDatabaseHistoryAdapter
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
|
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
|
||||||
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.*
|
import com.kunzisoft.keepass.utils.*
|
||||||
@@ -63,7 +63,7 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
|
|
||||||
// Views
|
// Views
|
||||||
private var coordinatorLayout: CoordinatorLayout? = null
|
private var coordinatorLayout: CoordinatorLayout? = null
|
||||||
private var fileListContainer: View? = null
|
private var fileManagerExplanationButton: View? = null
|
||||||
private var createButtonView: View? = null
|
private var createButtonView: View? = null
|
||||||
private var openDatabaseButtonView: View? = null
|
private var openDatabaseButtonView: View? = null
|
||||||
|
|
||||||
@@ -85,12 +85,16 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
|
|
||||||
setContentView(R.layout.activity_file_selection)
|
setContentView(R.layout.activity_file_selection)
|
||||||
coordinatorLayout = findViewById(R.id.activity_file_selection_coordinator_layout)
|
coordinatorLayout = findViewById(R.id.activity_file_selection_coordinator_layout)
|
||||||
fileListContainer = findViewById(R.id.container_file_list)
|
|
||||||
|
|
||||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||||
toolbar.title = ""
|
toolbar.title = ""
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
|
fileManagerExplanationButton = findViewById(R.id.file_manager_explanation_button)
|
||||||
|
fileManagerExplanationButton?.setOnClickListener {
|
||||||
|
UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
|
||||||
|
}
|
||||||
|
|
||||||
// Create button
|
// Create button
|
||||||
createButtonView = findViewById(R.id.create_database_button)
|
createButtonView = findViewById(R.id.create_database_button)
|
||||||
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
|
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
|
||||||
@@ -105,8 +109,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
createButtonView?.setOnClickListener { createNewFile() }
|
createButtonView?.setOnClickListener { createNewFile() }
|
||||||
|
|
||||||
mOpenFileHelper = OpenFileHelper(this)
|
mOpenFileHelper = OpenFileHelper(this)
|
||||||
openDatabaseButtonView = findViewById(R.id.open_database_button)
|
openDatabaseButtonView = findViewById(R.id.open_keyfile_button)
|
||||||
openDatabaseButtonView?.setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
openDatabaseButtonView?.apply {
|
||||||
|
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
||||||
|
setOnClickListener(it)
|
||||||
|
setOnLongClickListener(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// History list
|
// History list
|
||||||
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
|
val fileDatabaseHistoryRecyclerView = findViewById<RecyclerView>(R.id.file_list)
|
||||||
@@ -121,7 +130,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
databaseFileUri,
|
databaseFileUri,
|
||||||
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
|
UriUtil.parse(fileDatabaseHistoryEntityToOpen.keyFileUri))
|
||||||
}
|
}
|
||||||
updateFileListVisibility()
|
|
||||||
}
|
}
|
||||||
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
mAdapterDatabaseHistory?.setOnFileDatabaseHistoryDeleteListener { fileDatabaseHistoryToDelete ->
|
||||||
// Remove from app database
|
// Remove from app database
|
||||||
@@ -130,7 +138,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
|
fileHistoryDeleted?.let { databaseFileHistoryDeleted ->
|
||||||
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
|
mAdapterDatabaseHistory?.deleteDatabaseFileHistory(databaseFileHistoryDeleted)
|
||||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
updateFileListVisibility()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -212,7 +219,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
try {
|
try {
|
||||||
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
PasswordActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
||||||
databaseUri, keyFile,
|
databaseUri, keyFile,
|
||||||
assistStructure)
|
assistStructure,
|
||||||
|
intent.getParcelableExtra(KEY_SEARCH_INFO))
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
fileNoFoundAction(e)
|
fileNoFoundAction(e)
|
||||||
}
|
}
|
||||||
@@ -224,16 +232,21 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
private fun launchGroupActivity(readOnly: Boolean) {
|
private fun launchGroupActivity(readOnly: Boolean) {
|
||||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||||
{
|
{
|
||||||
GroupActivity.launch(this@FileDatabaseSelectActivity, readOnly)
|
GroupActivity.launch(this@FileDatabaseSelectActivity,
|
||||||
|
readOnly)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity, readOnly)
|
GroupActivity.launchForKeyboardSelection(this@FileDatabaseSelectActivity,
|
||||||
|
readOnly)
|
||||||
// Do not keep history
|
// Do not keep history
|
||||||
finish()
|
finish()
|
||||||
},
|
},
|
||||||
{ assistStructure ->
|
{ assistStructure ->
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity, assistStructure, readOnly)
|
GroupActivity.launchForAutofillResult(this@FileDatabaseSelectActivity,
|
||||||
|
assistStructure,
|
||||||
|
intent.getParcelableExtra(KEY_SEARCH_INFO),
|
||||||
|
readOnly)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -245,25 +258,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
overridePendingTransition(0, 0)
|
overridePendingTransition(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateExternalStorageWarning() {
|
|
||||||
// To show errors
|
|
||||||
var warning = -1
|
|
||||||
val state = Environment.getExternalStorageState()
|
|
||||||
if (state == Environment.MEDIA_MOUNTED_READ_ONLY) {
|
|
||||||
warning = R.string.read_only_warning
|
|
||||||
} else if (state != Environment.MEDIA_MOUNTED) {
|
|
||||||
warning = R.string.warning_unmounted
|
|
||||||
}
|
|
||||||
|
|
||||||
val labelWarningView = findViewById<TextView>(R.id.label_warning)
|
|
||||||
if (warning != -1) {
|
|
||||||
labelWarningView.setText(warning)
|
|
||||||
labelWarningView.visibility = View.VISIBLE
|
|
||||||
} else {
|
|
||||||
labelWarningView.visibility = View.INVISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
val database = Database.getInstance()
|
val database = Database.getInstance()
|
||||||
if (database.loaded) {
|
if (database.loaded) {
|
||||||
@@ -272,8 +266,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
|
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
updateExternalStorageWarning()
|
|
||||||
|
|
||||||
// Construct adapter with listeners
|
// Construct adapter with listeners
|
||||||
if (PreferencesUtil.showRecentFiles(this)) {
|
if (PreferencesUtil.showRecentFiles(this)) {
|
||||||
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
|
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
|
||||||
@@ -289,13 +281,11 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
true
|
true
|
||||||
})
|
})
|
||||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
updateFileListVisibility()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
|
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
|
||||||
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
mAdapterDatabaseHistory?.notifyDataSetChanged()
|
||||||
updateFileListVisibility()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register progress task
|
// Register progress task
|
||||||
@@ -317,13 +307,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
|
outState.putParcelable(EXTRA_DATABASE_URI, mDatabaseFileUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFileListVisibility() {
|
|
||||||
if (mAdapterDatabaseHistory?.itemCount == 0)
|
|
||||||
fileListContainer?.visibility = View.INVISIBLE
|
|
||||||
else
|
|
||||||
fileListContainer?.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAssignKeyDialogPositiveClick(
|
override fun onAssignKeyDialogPositiveClick(
|
||||||
masterPasswordChecked: Boolean, masterPassword: String?,
|
masterPasswordChecked: Boolean, masterPassword: String?,
|
||||||
keyFileChecked: Boolean, keyFile: Uri?) {
|
keyFileChecked: Boolean, keyFile: Uri?) {
|
||||||
@@ -454,10 +437,13 @@ class FileDatabaseSelectActivity : StylishActivity(),
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
fun launchForAutofillResult(activity: Activity, assistStructure: AssistStructure) {
|
fun launchForAutofillResult(activity: Activity,
|
||||||
|
assistStructure: AssistStructure,
|
||||||
|
searchInfo: SearchInfo?) {
|
||||||
AutofillHelper.startActivityForAutofillResult(activity,
|
AutofillHelper.startActivityForAutofillResult(activity,
|
||||||
Intent(activity, FileDatabaseSelectActivity::class.java),
|
Intent(activity, FileDatabaseSelectActivity::class.java),
|
||||||
assistStructure)
|
assistStructure,
|
||||||
|
searchInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import com.kunzisoft.keepass.database.element.node.Type
|
|||||||
import com.kunzisoft.keepass.education.GroupActivityEducation
|
import com.kunzisoft.keepass.education.GroupActivityEducation
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||||
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
|
||||||
@@ -89,6 +90,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
|
|
||||||
// Views
|
// Views
|
||||||
private var coordinatorLayout: CoordinatorLayout? = null
|
private var coordinatorLayout: CoordinatorLayout? = null
|
||||||
|
private var lockView: View? = null
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
private var searchTitleView: View? = null
|
private var searchTitleView: View? = null
|
||||||
private var toolbarAction: ToolbarAction? = null
|
private var toolbarAction: ToolbarAction? = null
|
||||||
@@ -134,6 +136,11 @@ class GroupActivity : LockingActivity(),
|
|||||||
groupNameView = findViewById(R.id.group_name)
|
groupNameView = findViewById(R.id.group_name)
|
||||||
toolbarAction = findViewById(R.id.toolbar_action)
|
toolbarAction = findViewById(R.id.toolbar_action)
|
||||||
modeTitleView = findViewById(R.id.mode_title_view)
|
modeTitleView = findViewById(R.id.mode_title_view)
|
||||||
|
lockView = findViewById(R.id.lock_button)
|
||||||
|
|
||||||
|
lockView?.setOnClickListener {
|
||||||
|
lockAndExit()
|
||||||
|
}
|
||||||
|
|
||||||
toolbar?.title = ""
|
toolbar?.title = ""
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
@@ -347,7 +354,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
// If it's a search
|
// If it's a search
|
||||||
if (Intent.ACTION_SEARCH == intent.action) {
|
if (Intent.ACTION_SEARCH == intent.action) {
|
||||||
val searchString = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
|
val searchString = intent.getStringExtra(SearchManager.QUERY)?.trim { it <= ' ' } ?: ""
|
||||||
return mDatabase?.search(searchString)
|
return mDatabase?.createVirtualGroupFromSearch(searchString)
|
||||||
}
|
}
|
||||||
// else a real group
|
// else a real group
|
||||||
else {
|
else {
|
||||||
@@ -486,7 +493,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
// Build response with the entry selected
|
// Build response with the entry selected
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && mDatabase != null) {
|
||||||
mDatabase?.let { database ->
|
mDatabase?.let { database ->
|
||||||
AutofillHelper.buildResponseWhenEntrySelected(this@GroupActivity,
|
AutofillHelper.buildResponse(this@GroupActivity,
|
||||||
entryVersioned.getEntryInfo(database))
|
entryVersioned.getEntryInfo(database))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -632,6 +639,13 @@ class GroupActivity : LockingActivity(),
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
// Show the lock button
|
||||||
|
lockView?.visibility = if (PreferencesUtil.showLockDatabaseButton(this)) {
|
||||||
|
View.VISIBLE
|
||||||
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
// Refresh the elements
|
// Refresh the elements
|
||||||
assignGroupViewElements()
|
assignGroupViewElements()
|
||||||
// Refresh suggestions to change preferences
|
// Refresh suggestions to change preferences
|
||||||
@@ -753,12 +767,11 @@ class GroupActivity : LockingActivity(),
|
|||||||
|
|
||||||
if (!sortMenuEducationPerformed) {
|
if (!sortMenuEducationPerformed) {
|
||||||
// lockMenuEducationPerformed
|
// lockMenuEducationPerformed
|
||||||
toolbar != null
|
val lockButtonView = findViewById<View>(R.id.lock_button_icon)
|
||||||
&& toolbar!!.findViewById<View>(R.id.menu_lock) != null
|
lockButtonView != null
|
||||||
&& groupActivityEducation.checkAndPerformedLockMenuEducation(
|
&& groupActivityEducation.checkAndPerformedLockMenuEducation(lockButtonView,
|
||||||
toolbar!!.findViewById(R.id.menu_lock),
|
|
||||||
{
|
{
|
||||||
onOptionsItemSelected(menu.findItem(R.id.menu_lock))
|
lockAndExit()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
performedNextEducation(groupActivityEducation, menu)
|
performedNextEducation(groupActivityEducation, menu)
|
||||||
@@ -777,10 +790,6 @@ class GroupActivity : LockingActivity(),
|
|||||||
R.id.menu_search ->
|
R.id.menu_search ->
|
||||||
//onSearchRequested();
|
//onSearchRequested();
|
||||||
return true
|
return true
|
||||||
R.id.menu_lock -> {
|
|
||||||
lockAndExit()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
R.id.menu_save_database -> {
|
R.id.menu_save_database -> {
|
||||||
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
mProgressDialogThread?.startDatabaseSave(!mReadOnly)
|
||||||
return true
|
return true
|
||||||
@@ -834,7 +843,7 @@ class GroupActivity : LockingActivity(),
|
|||||||
removeChildren()
|
removeChildren()
|
||||||
|
|
||||||
title = name
|
title = name
|
||||||
this.icon = icon // TODO custom icon
|
this.icon = icon // TODO custom icon #96
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If group updated save it in the database
|
// If group updated save it in the database
|
||||||
@@ -956,27 +965,41 @@ class GroupActivity : LockingActivity(),
|
|||||||
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
|
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"
|
||||||
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
|
private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY"
|
||||||
|
|
||||||
private fun buildIntent(context: Context, group: Group?, readOnly: Boolean,
|
private fun buildIntent(context: Context,
|
||||||
|
group: Group?,
|
||||||
|
searchInfo: SearchInfo?,
|
||||||
|
readOnly: Boolean,
|
||||||
intentBuildLauncher: (Intent) -> Unit) {
|
intentBuildLauncher: (Intent) -> Unit) {
|
||||||
val intent = Intent(context, GroupActivity::class.java)
|
val intent = Intent(context, GroupActivity::class.java)
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
intent.putExtra(GROUP_ID_KEY, group.nodeId)
|
intent.putExtra(GROUP_ID_KEY, group.nodeId)
|
||||||
}
|
}
|
||||||
|
if (searchInfo != null) {
|
||||||
|
intent.action = Intent.ACTION_SEARCH
|
||||||
|
val searchQuery = searchInfo.webDomain ?: searchInfo.applicationId
|
||||||
|
intent.putExtra(SearchManager.QUERY, searchQuery)
|
||||||
|
}
|
||||||
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
|
||||||
intentBuildLauncher.invoke(intent)
|
intentBuildLauncher.invoke(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkTimeAndBuildIntent(activity: Activity, group: Group?, readOnly: Boolean,
|
private fun checkTimeAndBuildIntent(activity: Activity,
|
||||||
|
group: Group?,
|
||||||
|
searchInfo: SearchInfo?,
|
||||||
|
readOnly: Boolean,
|
||||||
intentBuildLauncher: (Intent) -> Unit) {
|
intentBuildLauncher: (Intent) -> Unit) {
|
||||||
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
|
||||||
buildIntent(activity, group, readOnly, intentBuildLauncher)
|
buildIntent(activity, group, searchInfo, readOnly, intentBuildLauncher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkTimeAndBuildIntent(context: Context, group: Group?, readOnly: Boolean,
|
private fun checkTimeAndBuildIntent(context: Context,
|
||||||
|
group: Group?,
|
||||||
|
searchInfo: SearchInfo?,
|
||||||
|
readOnly: Boolean,
|
||||||
intentBuildLauncher: (Intent) -> Unit) {
|
intentBuildLauncher: (Intent) -> Unit) {
|
||||||
if (TimeoutHelper.checkTime(context)) {
|
if (TimeoutHelper.checkTime(context)) {
|
||||||
buildIntent(context, group, readOnly, intentBuildLauncher)
|
buildIntent(context, group, searchInfo, readOnly, intentBuildLauncher)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,11 +1008,9 @@ class GroupActivity : LockingActivity(),
|
|||||||
* Standard Launch
|
* Standard Launch
|
||||||
* -------------------------
|
* -------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
fun launch(context: Context,
|
fun launch(context: Context,
|
||||||
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
||||||
checkTimeAndBuildIntent(context, null, readOnly) { intent ->
|
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -999,11 +1020,10 @@ class GroupActivity : LockingActivity(),
|
|||||||
* Keyboard Launch
|
* Keyboard Launch
|
||||||
* -------------------------
|
* -------------------------
|
||||||
*/
|
*/
|
||||||
// TODO implement pre search to directly open the direct group
|
// TODO implement pre search to directly open the direct group #280
|
||||||
|
|
||||||
fun launchForKeyboardSelection(context: Context,
|
fun launchForKeyboardSelection(context: Context,
|
||||||
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(context)) {
|
||||||
checkTimeAndBuildIntent(context, null, readOnly) { intent ->
|
checkTimeAndBuildIntent(context, null, null, readOnly) { intent ->
|
||||||
EntrySelectionHelper.startActivityForEntrySelection(context, intent)
|
EntrySelectionHelper.startActivityForEntrySelection(context, intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1013,14 +1033,13 @@ class GroupActivity : LockingActivity(),
|
|||||||
* Autofill Launch
|
* Autofill Launch
|
||||||
* -------------------------
|
* -------------------------
|
||||||
*/
|
*/
|
||||||
// TODO implement pre search to directly open the direct group
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
fun launchForAutofillResult(activity: Activity,
|
fun launchForAutofillResult(activity: Activity,
|
||||||
assistStructure: AssistStructure,
|
assistStructure: AssistStructure,
|
||||||
|
searchInfo: SearchInfo? = null,
|
||||||
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
|
readOnly: Boolean = PreferencesUtil.enableReadOnlyDatabase(activity)) {
|
||||||
checkTimeAndBuildIntent(activity, null, readOnly) { intent ->
|
checkTimeAndBuildIntent(activity, null, searchInfo, readOnly) { intent ->
|
||||||
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure)
|
AutofillHelper.startActivityForAutofillResult(activity, intent, assistStructure, searchInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
|||||||
if (readOnly
|
if (readOnly
|
||||||
|| isASearchResult
|
|| isASearchResult
|
||||||
|| nodes.any { it.type == Type.GROUP }) {
|
|| nodes.any { it.type == Type.GROUP }) {
|
||||||
// TODO COPY For Group
|
// TODO Copy For Group
|
||||||
menu?.removeItem(R.id.menu_copy)
|
menu?.removeItem(R.id.menu_copy)
|
||||||
menu?.removeItem(R.id.menu_move)
|
menu?.removeItem(R.id.menu_move)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.app.Activity
|
|||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
import android.app.backup.BackupManager
|
import android.app.backup.BackupManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -32,13 +33,11 @@ import android.text.TextWatcher
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
||||||
import android.widget.Button
|
import android.widget.*
|
||||||
import android.widget.CompoundButton
|
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.biometric.BiometricManager
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||||
@@ -50,11 +49,13 @@ import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
|||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||||
|
import com.kunzisoft.keepass.autofill.AutofillHelper.KEY_SEARCH_INFO
|
||||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
||||||
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
import com.kunzisoft.keepass.database.action.ProgressDialogThread
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||||
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.DATABASE_URI_KEY
|
||||||
@@ -66,6 +67,7 @@ import com.kunzisoft.keepass.utils.FileDatabaseInfo
|
|||||||
import com.kunzisoft.keepass.utils.MenuUtil
|
import com.kunzisoft.keepass.utils.MenuUtil
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||||
|
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||||
import com.kunzisoft.keepass.view.asError
|
import com.kunzisoft.keepass.view.asError
|
||||||
import kotlinx.android.synthetic.main.activity_password.*
|
import kotlinx.android.synthetic.main.activity_password.*
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
@@ -77,7 +79,7 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
private var containerView: View? = null
|
private var containerView: View? = null
|
||||||
private var filenameView: TextView? = null
|
private var filenameView: TextView? = null
|
||||||
private var passwordView: EditText? = null
|
private var passwordView: EditText? = null
|
||||||
private var keyFileView: EditText? = null
|
private var keyFileSelectionView: KeyFileSelectionView? = null
|
||||||
private var confirmButtonView: Button? = null
|
private var confirmButtonView: Button? = null
|
||||||
private var checkboxPasswordView: CompoundButton? = null
|
private var checkboxPasswordView: CompoundButton? = null
|
||||||
private var checkboxKeyFileView: CompoundButton? = null
|
private var checkboxKeyFileView: CompoundButton? = null
|
||||||
@@ -92,6 +94,7 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
private var mRememberKeyFile: Boolean = false
|
private var mRememberKeyFile: Boolean = false
|
||||||
private var mOpenFileHelper: OpenFileHelper? = null
|
private var mOpenFileHelper: OpenFileHelper? = null
|
||||||
|
|
||||||
|
private var mPermissionAsked = false
|
||||||
private var readOnly: Boolean = false
|
private var readOnly: Boolean = false
|
||||||
private var mForceReadOnly: Boolean = false
|
private var mForceReadOnly: Boolean = false
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -123,18 +126,23 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
confirmButtonView = findViewById(R.id.activity_password_open_button)
|
confirmButtonView = findViewById(R.id.activity_password_open_button)
|
||||||
filenameView = findViewById(R.id.filename)
|
filenameView = findViewById(R.id.filename)
|
||||||
passwordView = findViewById(R.id.password)
|
passwordView = findViewById(R.id.password)
|
||||||
keyFileView = findViewById(R.id.pass_keyfile)
|
keyFileSelectionView = findViewById(R.id.keyfile_selection)
|
||||||
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
||||||
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
||||||
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
|
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
|
||||||
advancedUnlockInfoView = findViewById(R.id.biometric_info)
|
advancedUnlockInfoView = findViewById(R.id.biometric_info)
|
||||||
infoContainerView = findViewById(R.id.activity_password_info_container)
|
infoContainerView = findViewById(R.id.activity_password_info_container)
|
||||||
|
|
||||||
|
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
|
||||||
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
|
||||||
|
|
||||||
val browseView = findViewById<View>(R.id.open_database_button)
|
|
||||||
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
mOpenFileHelper = OpenFileHelper(this@PasswordActivity)
|
||||||
browseView.setOnClickListener(mOpenFileHelper!!.openFileOnClickViewListener)
|
keyFileSelectionView?.apply {
|
||||||
|
mOpenFileHelper?.openFileOnClickViewListener?.let {
|
||||||
|
setOnClickListener(it)
|
||||||
|
setOnLongClickListener(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
passwordView?.setOnEditorActionListener(onEditorActionListener)
|
passwordView?.setOnEditorActionListener(onEditorActionListener)
|
||||||
passwordView?.addTextChangedListener(object : TextWatcher {
|
passwordView?.addTextChangedListener(object : TextWatcher {
|
||||||
@@ -147,17 +155,6 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
checkboxPasswordView?.isChecked = true
|
checkboxPasswordView?.isChecked = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
keyFileView?.setOnEditorActionListener(onEditorActionListener)
|
|
||||||
keyFileView?.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
|
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
|
||||||
if (editable.toString().isNotEmpty() && checkboxKeyFileView?.isChecked != true)
|
|
||||||
checkboxKeyFileView?.isChecked = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||||
enableOrNotTheConfirmationButton()
|
enableOrNotTheConfirmationButton()
|
||||||
@@ -260,16 +257,38 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
private fun launchGroupActivity() {
|
private fun launchGroupActivity() {
|
||||||
EntrySelectionHelper.doEntrySelectionAction(intent,
|
EntrySelectionHelper.doEntrySelectionAction(intent,
|
||||||
{
|
{
|
||||||
GroupActivity.launch(this@PasswordActivity, readOnly)
|
GroupActivity.launch(this@PasswordActivity,
|
||||||
|
readOnly)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GroupActivity.launchForKeyboardSelection(this@PasswordActivity, readOnly)
|
GroupActivity.launchForKeyboardSelection(this@PasswordActivity,
|
||||||
|
readOnly)
|
||||||
// Do not keep history
|
// Do not keep history
|
||||||
finish()
|
finish()
|
||||||
},
|
},
|
||||||
{ assistStructure ->
|
{ assistStructure ->
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
GroupActivity.launchForAutofillResult(this@PasswordActivity, assistStructure, readOnly)
|
val searchInfo: SearchInfo? = intent.getParcelableExtra(KEY_SEARCH_INFO)
|
||||||
|
AutofillHelper.checkAutoSearchInfo(this,
|
||||||
|
Database.getInstance(),
|
||||||
|
searchInfo,
|
||||||
|
{ items ->
|
||||||
|
// Response is build
|
||||||
|
AutofillHelper.buildResponse(this, items)
|
||||||
|
finish()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Here no search info found
|
||||||
|
GroupActivity.launchForAutofillResult(this@PasswordActivity,
|
||||||
|
assistStructure,
|
||||||
|
null,
|
||||||
|
readOnly)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Simply close if database not opened, normally not happened
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -285,11 +304,12 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
|
|
||||||
|
|
||||||
if (Database.getInstance().loaded)
|
if (Database.getInstance().loaded)
|
||||||
launchGroupActivity()
|
launchGroupActivity()
|
||||||
|
|
||||||
|
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
|
||||||
|
|
||||||
// If the database isn't accessible make sure to clear the password field, if it
|
// If the database isn't accessible make sure to clear the password field, if it
|
||||||
// was saved in the instance state
|
// was saved in the instance state
|
||||||
if (Database.getInstance().loaded) {
|
if (Database.getInstance().loaded) {
|
||||||
@@ -302,9 +322,12 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
mProgressDialogThread?.registerProgressTask()
|
mProgressDialogThread?.registerProgressTask()
|
||||||
|
|
||||||
initUriFromIntent()
|
initUriFromIntent()
|
||||||
|
|
||||||
|
checkPermission()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
|
||||||
mDatabaseKeyFileUri?.let {
|
mDatabaseKeyFileUri?.let {
|
||||||
outState.putString(KEY_KEYFILE, it.toString())
|
outState.putString(KEY_KEYFILE, it.toString())
|
||||||
}
|
}
|
||||||
@@ -319,7 +342,9 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
!FileDatabaseInfo(this, it).canWrite
|
!FileDatabaseInfo(this, it).canWrite
|
||||||
} ?: false
|
} ?: false
|
||||||
*/
|
*/
|
||||||
mForceReadOnly = false
|
mForceReadOnly = mDatabaseFileUri?.let {
|
||||||
|
!FileDatabaseInfo(this, it).exists
|
||||||
|
} ?: true
|
||||||
|
|
||||||
// Post init uri with KeyFile if needed
|
// Post init uri with KeyFile if needed
|
||||||
if (mRememberKeyFile && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
|
if (mRememberKeyFile && (mDatabaseKeyFileUri == null || mDatabaseKeyFileUri.toString().isEmpty())) {
|
||||||
@@ -345,7 +370,7 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
|
|
||||||
// Define Key File text
|
// Define Key File text
|
||||||
if (mRememberKeyFile) {
|
if (mRememberKeyFile) {
|
||||||
populateKeyFileTextView(keyFileUri?.toString())
|
populateKeyFileTextView(keyFileUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define listeners for default database checkbox and validate button
|
// Define listeners for default database checkbox and validate button
|
||||||
@@ -459,13 +484,13 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populateKeyFileTextView(text: String?) {
|
private fun populateKeyFileTextView(uri: Uri?) {
|
||||||
if (text == null || text.isEmpty()) {
|
if (uri == null || uri.toString().isEmpty()) {
|
||||||
keyFileView?.setText("")
|
keyFileSelectionView?.uri = null
|
||||||
if (checkboxKeyFileView?.isChecked == true)
|
if (checkboxKeyFileView?.isChecked == true)
|
||||||
checkboxKeyFileView?.isChecked = false
|
checkboxKeyFileView?.isChecked = false
|
||||||
} else {
|
} else {
|
||||||
keyFileView?.setText(text)
|
keyFileSelectionView?.uri = uri
|
||||||
if (checkboxKeyFileView?.isChecked != true)
|
if (checkboxKeyFileView?.isChecked != true)
|
||||||
checkboxKeyFileView?.isChecked = true
|
checkboxKeyFileView?.isChecked = true
|
||||||
}
|
}
|
||||||
@@ -486,7 +511,7 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
|
|
||||||
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
private fun verifyCheckboxesAndLoadDatabase(cipherDatabaseEntity: CipherDatabaseEntity? = null) {
|
||||||
val password: String? = passwordView?.text?.toString()
|
val password: String? = passwordView?.text?.toString()
|
||||||
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
val keyFile: Uri? = keyFileSelectionView?.uri
|
||||||
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
|
verifyCheckboxesAndLoadDatabase(password, keyFile, cipherDatabaseEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,7 +524,7 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
|
private fun verifyKeyFileCheckboxAndLoadDatabase(password: String?) {
|
||||||
val keyFile: Uri? = UriUtil.parse(keyFileView?.text?.toString())
|
val keyFile: Uri? = keyFileSelectionView?.uri
|
||||||
verifyKeyFileCheckbox(keyFile)
|
verifyKeyFileCheckbox(keyFile)
|
||||||
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
|
loadDatabase(mDatabaseFileUri, password, mDatabaseKeyFileUri)
|
||||||
}
|
}
|
||||||
@@ -576,28 +601,56 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
private fun checkPermission() {
|
||||||
|
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
val permissions = arrayOf(writePermission)
|
||||||
|
if (Build.VERSION.SDK_INT >= 23
|
||||||
|
&& !readOnly
|
||||||
|
&& !mPermissionAsked) {
|
||||||
|
mPermissionAsked = true
|
||||||
|
// Check self permission to show or not the dialog
|
||||||
|
if (toolbar != null
|
||||||
|
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
|
||||||
|
when (requestCode) {
|
||||||
|
WRITE_EXTERNAL_STORAGE_REQUEST -> {
|
||||||
|
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
|
||||||
|
Toast.makeText(this, R.string.read_only_warning, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// To fix multiple view education
|
// To fix multiple view education
|
||||||
private var performedEductionInProgress = false
|
private var performedEductionInProgress = false
|
||||||
private fun launchEducation(menu: Menu, onEducationFinished: (()-> Unit)? = null) {
|
private fun launchEducation(menu: Menu) {
|
||||||
if (!performedEductionInProgress) {
|
if (!performedEductionInProgress) {
|
||||||
performedEductionInProgress = true
|
performedEductionInProgress = true
|
||||||
// Show education views
|
// Show education views
|
||||||
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
|
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
|
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
|
||||||
menu: Menu,
|
menu: Menu) {
|
||||||
onEducationFinished: (()-> Unit)? = null) {
|
|
||||||
val educationToolbar = toolbar
|
val educationToolbar = toolbar
|
||||||
val unlockEducationPerformed = educationToolbar != null
|
val unlockEducationPerformed = educationToolbar != null
|
||||||
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
|
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
|
||||||
educationToolbar,
|
educationToolbar,
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
performedNextEducation(passwordActivityEducation, menu)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
performedNextEducation(passwordActivityEducation, menu)
|
||||||
})
|
})
|
||||||
if (!unlockEducationPerformed) {
|
if (!unlockEducationPerformed) {
|
||||||
val readOnlyEducationPerformed =
|
val readOnlyEducationPerformed =
|
||||||
@@ -606,30 +659,25 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
|
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
|
||||||
{
|
{
|
||||||
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
|
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
|
||||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
performedNextEducation(passwordActivityEducation, menu)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
performedNextEducation(passwordActivityEducation, menu)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!readOnlyEducationPerformed) {
|
if (!readOnlyEducationPerformed) {
|
||||||
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
|
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
|
||||||
val biometricEducationPerformed =
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||||
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
|
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
|
||||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||||
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
|
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
|
||||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
|
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
performedNextEducation(passwordActivityEducation, menu)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
|
performedNextEducation(passwordActivityEducation, menu)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!biometricEducationPerformed) {
|
|
||||||
onEducationFinished?.invoke()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -678,7 +726,7 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
) { uri ->
|
) { uri ->
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
mDatabaseKeyFileUri = uri
|
mDatabaseKeyFileUri = uri
|
||||||
populateKeyFileTextView(uri.toString())
|
populateKeyFileTextView(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -703,6 +751,8 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
|
|
||||||
private const val KEY_PASSWORD = "password"
|
private const val KEY_PASSWORD = "password"
|
||||||
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
|
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
|
||||||
|
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
|
||||||
|
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
|
||||||
|
|
||||||
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
|
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
|
||||||
intentBuildLauncher: (Intent) -> Unit) {
|
intentBuildLauncher: (Intent) -> Unit) {
|
||||||
@@ -757,13 +807,15 @@ open class PasswordActivity : StylishActivity() {
|
|||||||
activity: Activity,
|
activity: Activity,
|
||||||
databaseFile: Uri,
|
databaseFile: Uri,
|
||||||
keyFile: Uri?,
|
keyFile: Uri?,
|
||||||
assistStructure: AssistStructure?) {
|
assistStructure: AssistStructure?,
|
||||||
|
searchInfo: SearchInfo?) {
|
||||||
if (assistStructure != null) {
|
if (assistStructure != null) {
|
||||||
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||||
AutofillHelper.startActivityForAutofillResult(
|
AutofillHelper.startActivityForAutofillResult(
|
||||||
activity,
|
activity,
|
||||||
intent,
|
intent,
|
||||||
assistStructure)
|
assistStructure,
|
||||||
|
searchInfo)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
launch(activity, databaseFile, keyFile)
|
launch(activity, databaseFile, keyFile)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import android.widget.CompoundButton
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||||
|
|
||||||
class AssignMasterKeyDialogFragment : DialogFragment() {
|
class AssignMasterKeyDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
@@ -51,9 +51,8 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
private var passwordRepeatTextInputLayout: TextInputLayout? = null
|
||||||
private var passwordRepeatView: TextView? = null
|
private var passwordRepeatView: TextView? = null
|
||||||
|
|
||||||
private var keyFileTextInputLayout: TextInputLayout? = null
|
|
||||||
private var keyFileCheckBox: CompoundButton? = null
|
private var keyFileCheckBox: CompoundButton? = null
|
||||||
private var keyFileView: TextView? = null
|
private var keyFileSelectionView: KeyFileSelectionView? = null
|
||||||
|
|
||||||
private var mListener: AssignPasswordDialogListener? = null
|
private var mListener: AssignPasswordDialogListener? = null
|
||||||
|
|
||||||
@@ -69,16 +68,6 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val keyFileTextWatcher = object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
|
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
|
||||||
|
|
||||||
override fun afterTextChanged(editable: Editable) {
|
|
||||||
keyFileCheckBox?.isChecked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AssignPasswordDialogListener {
|
interface AssignPasswordDialogListener {
|
||||||
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
|
fun onAssignKeyDialogPositiveClick(masterPasswordChecked: Boolean, masterPassword: String?,
|
||||||
keyFileChecked: Boolean, keyFile: Uri?)
|
keyFileChecked: Boolean, keyFile: Uri?)
|
||||||
@@ -121,13 +110,14 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
passwordRepeatTextInputLayout = rootView?.findViewById(R.id.password_repeat_input_layout)
|
||||||
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
passwordRepeatView = rootView?.findViewById(R.id.pass_conf_password)
|
||||||
|
|
||||||
keyFileTextInputLayout = rootView?.findViewById(R.id.keyfile_input_layout)
|
|
||||||
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
keyFileCheckBox = rootView?.findViewById(R.id.keyfile_checkox)
|
||||||
keyFileView = rootView?.findViewById(R.id.pass_keyfile)
|
keyFileSelectionView = rootView?.findViewById(R.id.keyfile_selection)
|
||||||
|
|
||||||
mOpenFileHelper = OpenFileHelper(this)
|
mOpenFileHelper = OpenFileHelper(this)
|
||||||
rootView?.findViewById<View>(R.id.open_database_button)?.setOnClickListener { view ->
|
keyFileSelectionView?.apply {
|
||||||
mOpenFileHelper?.openFileOnClickViewListener?.onClick(view) }
|
setOnClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||||
|
setOnLongClickListener(mOpenFileHelper?.openFileOnClickViewListener)
|
||||||
|
}
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
|
|
||||||
@@ -176,14 +166,12 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
|
|
||||||
// To check checkboxes if a text is present
|
// To check checkboxes if a text is present
|
||||||
passwordView?.addTextChangedListener(passwordTextWatcher)
|
passwordView?.addTextChangedListener(passwordTextWatcher)
|
||||||
keyFileView?.addTextChangedListener(keyFileTextWatcher)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
|
||||||
passwordView?.removeTextChangedListener(passwordTextWatcher)
|
passwordView?.removeTextChangedListener(passwordTextWatcher)
|
||||||
keyFileView?.removeTextChangedListener(keyFileTextWatcher)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun verifyPassword(): Boolean {
|
private fun verifyPassword(): Boolean {
|
||||||
@@ -216,11 +204,11 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
if (keyFileCheckBox != null
|
if (keyFileCheckBox != null
|
||||||
&& keyFileCheckBox!!.isChecked) {
|
&& keyFileCheckBox!!.isChecked) {
|
||||||
|
|
||||||
UriUtil.parse(keyFileView?.text?.toString())?.let { uri ->
|
keyFileSelectionView?.uri?.let { uri ->
|
||||||
mKeyFile = uri
|
mKeyFile = uri
|
||||||
} ?: run {
|
} ?: run {
|
||||||
error = true
|
error = true
|
||||||
keyFileTextInputLayout?.error = getString(R.string.error_nokeyfile)
|
keyFileSelectionView?.error = getString(R.string.error_nokeyfile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return error
|
return error
|
||||||
@@ -265,8 +253,7 @@ class AssignMasterKeyDialogFragment : DialogFragment() {
|
|||||||
) { uri ->
|
) { uri ->
|
||||||
uri?.let { pathUri ->
|
uri?.let { pathUri ->
|
||||||
keyFileCheckBox?.isChecked = true
|
keyFileCheckBox?.isChecked = true
|
||||||
keyFileView?.text = pathUri.toString()
|
keyFileSelectionView?.uri = pathUri
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import android.widget.TextView
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
class BrowserDialogFragment : DialogFragment() {
|
class FileManagerDialogFragment : DialogFragment() {
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
@@ -41,15 +41,8 @@ class BrowserDialogFragment : DialogFragment() {
|
|||||||
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
|
val textDescription = root.findViewById<TextView>(R.id.file_manager_install_description)
|
||||||
textDescription.text = getString(R.string.file_manager_install_description)
|
textDescription.text = getString(R.string.file_manager_install_description)
|
||||||
|
|
||||||
val market = root.findViewById<Button>(R.id.file_manager_install_play_store)
|
root.findViewById<Button>(R.id.file_manager_button).setOnClickListener {
|
||||||
market.setOnClickListener {
|
UriUtil.gotoUrl(requireContext(), R.string.file_manager_explanation_url)
|
||||||
UriUtil.gotoUrl(context!!, R.string.file_manager_play_store)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
val web = root.findViewById<Button>(R.id.file_manager_install_f_droid)
|
|
||||||
web.setOnClickListener {
|
|
||||||
UriUtil.gotoUrl(context!!, R.string.file_manager_f_droid)
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,6 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
var password = ""
|
var password = ""
|
||||||
try {
|
try {
|
||||||
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
|
val length = Integer.valueOf(root?.findViewById<EditText>(R.id.length)?.text.toString())
|
||||||
|
|
||||||
password = PasswordGenerator(resources).generatePassword(length,
|
password = PasswordGenerator(resources).generatePassword(length,
|
||||||
uppercaseBox?.isChecked == true,
|
uppercaseBox?.isChecked == true,
|
||||||
lowercaseBox?.isChecked == true,
|
lowercaseBox?.isChecked == true,
|
||||||
@@ -178,6 +177,7 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
specialsBox?.isChecked == true,
|
specialsBox?.isChecked == true,
|
||||||
bracketsBox?.isChecked == true,
|
bracketsBox?.isChecked == true,
|
||||||
extendedBox?.isChecked == true)
|
extendedBox?.isChecked == true)
|
||||||
|
passwordInputLayoutView?.error = null
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
|
passwordInputLayoutView?.error = getString(R.string.error_wrong_length)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
@@ -193,7 +193,6 @@ class GeneratePasswordDialogFragment : DialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID"
|
const val KEY_PASSWORD_ID = "KEY_PASSWORD_ID"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,9 +130,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP
|
|||||||
}
|
}
|
||||||
|
|
||||||
iconButtonView?.setOnClickListener { _ ->
|
iconButtonView?.setOnClickListener { _ ->
|
||||||
fragmentManager?.let {
|
IconPickerDialogFragment().show(parentFragmentManager, "IconPickerDialogFragment")
|
||||||
IconPickerDialogFragment().show(it, "IconPickerDialogFragment")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.create()
|
return builder.create()
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ class ProFeatureDialogFragment : DialogFragment() {
|
|||||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_ad_free), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_buy_pro), FROM_HTML_MODE_LEGACY))
|
||||||
builder.setPositiveButton(R.string.download) { _, _ ->
|
builder.setPositiveButton(R.string.download) { _, _ ->
|
||||||
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
|
UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_feature_generosity), FROM_HTML_MODE_LEGACY)).append("\n\n")
|
||||||
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
|
stringBuilder.append(HtmlCompat.fromHtml(getString(R.string.html_text_donation), FROM_HTML_MODE_LEGACY))
|
||||||
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
||||||
UriUtil.gotoUrl(context!!, R.string.contribution_url)
|
UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.setMessage(stringBuilder)
|
builder.setMessage(stringBuilder)
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
|
|||||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_buy_pro), HtmlCompat.FROM_HTML_MODE_LEGACY)).append("\n")
|
||||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||||
builder.setPositiveButton(R.string.download) { _, _ ->
|
builder.setPositiveButton(R.string.download) { _, _ ->
|
||||||
UriUtil.gotoUrl(context!!, R.string.app_pro_url)
|
UriUtil.gotoUrl(requireContext(), R.string.app_pro_url)
|
||||||
}
|
}
|
||||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ class UnderDevelopmentFeatureDialogFragment : DialogFragment() {
|
|||||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_contibute), HtmlCompat.FROM_HTML_MODE_LEGACY)).append(" ")
|
||||||
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
.append(HtmlCompat.fromHtml(getString(R.string.html_text_dev_feature_encourage), HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||||
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
builder.setPositiveButton(R.string.contribute) { _, _ ->
|
||||||
UriUtil.gotoUrl(context!!, R.string.contribution_url)
|
UriUtil.gotoUrl(requireContext(), R.string.contribution_url)
|
||||||
}
|
}
|
||||||
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
builder.setNegativeButton(android.R.string.cancel) { _, _ -> dismiss() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import android.util.Log
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.FileManagerDialogFragment
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
|
|
||||||
class OpenFileHelper {
|
class OpenFileHelper {
|
||||||
@@ -52,14 +52,22 @@ class OpenFileHelper {
|
|||||||
this.fragment = context
|
this.fragment = context
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class OpenFileOnClickViewListener : View.OnClickListener {
|
inner class OpenFileOnClickViewListener : View.OnClickListener, View.OnLongClickListener {
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
private fun onAbstractClick(longClick: Boolean = false) {
|
||||||
try {
|
try {
|
||||||
try {
|
if (longClick) {
|
||||||
openActivityWithActionOpenDocument()
|
try {
|
||||||
} catch(e: Exception) {
|
openActivityWithActionGetContent()
|
||||||
openActivityWithActionGetContent()
|
} catch (e: Exception) {
|
||||||
|
openActivityWithActionOpenDocument()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
openActivityWithActionOpenDocument()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
openActivityWithActionGetContent()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Enable to start the file picker activity", e)
|
Log.e(TAG, "Enable to start the file picker activity", e)
|
||||||
@@ -68,6 +76,15 @@ class OpenFileHelper {
|
|||||||
showBrowserDialog()
|
showBrowserDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
onAbstractClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongClick(v: View?): Boolean {
|
||||||
|
onAbstractClick(true)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
@@ -147,11 +164,10 @@ class OpenFileHelper {
|
|||||||
*/
|
*/
|
||||||
private fun showBrowserDialog() {
|
private fun showBrowserDialog() {
|
||||||
try {
|
try {
|
||||||
val browserDialogFragment = BrowserDialogFragment()
|
val fileManagerDialogFragment = FileManagerDialogFragment()
|
||||||
if (fragment != null && fragment!!.fragmentManager != null)
|
fragment?.let {
|
||||||
browserDialogFragment.show(fragment!!.fragmentManager!!, "browserDialog")
|
fileManagerDialogFragment.show(it.parentFragmentManager, "browserDialog")
|
||||||
else
|
} ?: fileManagerDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
|
||||||
browserDialogFragment.show((activity as FragmentActivity).supportFragmentManager, "browserDialog")
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Can't open BrowserDialog", e)
|
Log.e(TAG, "Can't open BrowserDialog", e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities.lock
|
package com.kunzisoft.keepass.activities.lock
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -75,7 +74,10 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
|
|
||||||
if (mTimeoutEnable) {
|
if (mTimeoutEnable) {
|
||||||
mLockReceiver = LockReceiver {
|
mLockReceiver = LockReceiver {
|
||||||
lockAndExit()
|
closeDatabase()
|
||||||
|
// Add onActivityForResult response
|
||||||
|
setResult(RESULT_EXIT_LOCK)
|
||||||
|
finish()
|
||||||
}
|
}
|
||||||
registerLockReceiver(mLockReceiver)
|
registerLockReceiver(mLockReceiver)
|
||||||
}
|
}
|
||||||
@@ -148,7 +150,6 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
|
|
||||||
protected fun lockAndExit() {
|
protected fun lockAndExit() {
|
||||||
sendBroadcast(Intent(LOCK_ACTION))
|
sendBroadcast(Intent(LOCK_ACTION))
|
||||||
lock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,11 +181,3 @@ abstract class LockingActivity : StylishActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.lock() {
|
|
||||||
closeDatabase()
|
|
||||||
|
|
||||||
// Add onActivityForResult response
|
|
||||||
setResult(LockingActivity.RESULT_EXIT_LOCK)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -41,8 +41,9 @@ abstract class StylishActivity : AppCompatActivity() {
|
|||||||
*/
|
*/
|
||||||
override fun startActivity(intent: Intent) {
|
override fun startActivity(intent: Intent) {
|
||||||
try {
|
try {
|
||||||
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
|
intent.component?.let {
|
||||||
intent.component = null
|
if (it.shortClassName == ".HtcLinkifyDispatcherActivity")
|
||||||
|
intent.component = null
|
||||||
}
|
}
|
||||||
super.startActivity(intent)
|
super.startActivity(intent)
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
@@ -52,19 +53,19 @@ abstract class StylishActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
this.themeId = Stylish.getThemeId(this)
|
this.themeId = Stylish.getThemeId(this)
|
||||||
setTheme(themeId)
|
setTheme(themeId)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
// Several gingerbread devices have problems with FLAG_SECURE
|
// Several gingerbread devices have problems with FLAG_SECURE
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
|
||||||
if (Stylish.getThemeId(this) != this.themeId) {
|
if (Stylish.getThemeId(this) != this.themeId) {
|
||||||
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
|
Log.d(this.javaClass.name, "Theme change detected, restarting activity")
|
||||||
this.recreate()
|
this.recreate()
|
||||||
}
|
}
|
||||||
|
super.onResume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ abstract class StylishFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
// To fix status bar color
|
// To fix status bar color
|
||||||
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
val window = activity!!.window
|
val window = requireActivity().window
|
||||||
|
|
||||||
val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark)
|
val attrColorPrimaryDark = intArrayOf(android.R.attr.colorPrimaryDark)
|
||||||
val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark)
|
val taColorPrimaryDark = contextThemed?.theme?.obtainStyledAttributes(attrColorPrimaryDark)
|
||||||
|
|||||||
@@ -28,8 +28,14 @@ import android.view.ViewGroup
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.database.cursor.EntryCursorKDB
|
||||||
|
import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
||||||
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||||
|
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||||
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.view.strikeOut
|
import com.kunzisoft.keepass.view.strikeOut
|
||||||
@@ -69,8 +75,7 @@ class SearchEntryCursorAdapter(private val context: Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun bindView(view: View, context: Context, cursor: Cursor) {
|
override fun bindView(view: View, context: Context, cursor: Cursor) {
|
||||||
|
getEntryFrom(cursor)?.let { currentEntry ->
|
||||||
database.getEntryFrom(cursor)?.let { currentEntry ->
|
|
||||||
val viewHolder = view.tag as ViewHolder
|
val viewHolder = view.tag as ViewHolder
|
||||||
|
|
||||||
// Assign image
|
// Assign image
|
||||||
@@ -98,14 +103,46 @@ class SearchEntryCursorAdapter(private val context: Context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ViewHolder {
|
private fun getEntryFrom(cursor: Cursor): Entry? {
|
||||||
internal var imageViewIcon: ImageView? = null
|
return database.createEntry()?.apply {
|
||||||
internal var textViewTitle: TextView? = null
|
database.startManageEntry(this)
|
||||||
internal var textViewSubTitle: TextView? = null
|
entryKDB?.let { entryKDB ->
|
||||||
|
(cursor as EntryCursorKDB).populateEntry(entryKDB, database.iconFactory)
|
||||||
|
}
|
||||||
|
entryKDBX?.let { entryKDBX ->
|
||||||
|
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, database.iconFactory)
|
||||||
|
}
|
||||||
|
database.stopManageEntry(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
|
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
|
||||||
return database.searchEntries(context, constraint.toString())
|
return searchEntries(context, constraint.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchEntries(context: Context, query: String): Cursor? {
|
||||||
|
var cursorKDB: EntryCursorKDB? = null
|
||||||
|
var cursorKDBX: EntryCursorKDBX? = null
|
||||||
|
|
||||||
|
if (database.type == DatabaseKDB.TYPE)
|
||||||
|
cursorKDB = EntryCursorKDB()
|
||||||
|
if (database.type == DatabaseKDBX.TYPE)
|
||||||
|
cursorKDBX = EntryCursorKDBX()
|
||||||
|
|
||||||
|
val searchGroup = database.createVirtualGroupFromSearch(query, SearchHelper.MAX_SEARCH_ENTRY)
|
||||||
|
if (searchGroup != null) {
|
||||||
|
// Search in hide entries but not meta-stream
|
||||||
|
for (entry in searchGroup.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) {
|
||||||
|
entry.entryKDB?.let {
|
||||||
|
cursorKDB?.addEntry(it)
|
||||||
|
}
|
||||||
|
entry.entryKDBX?.let {
|
||||||
|
cursorKDBX?.addEntry(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursorKDB ?: cursorKDBX
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getEntryFromPosition(position: Int): Entry? {
|
fun getEntryFromPosition(position: Int): Entry? {
|
||||||
@@ -113,9 +150,14 @@ class SearchEntryCursorAdapter(private val context: Context,
|
|||||||
|
|
||||||
val cursor = this.cursor
|
val cursor = this.cursor
|
||||||
if (cursor.moveToFirst() && cursor.move(position)) {
|
if (cursor.moveToFirst() && cursor.move(position)) {
|
||||||
pwEntry = database.getEntryFrom(cursor)
|
pwEntry = getEntryFrom(cursor)
|
||||||
}
|
}
|
||||||
return pwEntry
|
return pwEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ViewHolder {
|
||||||
|
internal var imageViewIcon: ImageView? = null
|
||||||
|
internal var textViewTitle: TextView? = null
|
||||||
|
internal var textViewSubTitle: TextView? = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ package com.kunzisoft.keepass.app;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.utils.StringUtil;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
@@ -66,7 +64,7 @@ public final class PRNGFixes {
|
|||||||
|
|
||||||
private static boolean supportedOnThisDevice() {
|
private static boolean supportedOnThisDevice() {
|
||||||
// Blacklist on samsung devices
|
// Blacklist on samsung devices
|
||||||
if (StringUtil.INSTANCE.indexOfIgnoreCase(Build.MANUFACTURER, "samsung", Locale.ENGLISH) >= 0) {
|
if (Build.MANUFACTURER.toLowerCase(Locale.ENGLISH).contains("samsung")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,9 +31,17 @@ import android.view.autofill.AutofillManager
|
|||||||
import android.view.autofill.AutofillValue
|
import android.view.autofill.AutofillValue
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
|
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||||
|
import com.kunzisoft.keepass.icons.assignDatabaseIcon
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@@ -42,6 +50,7 @@ object AutofillHelper {
|
|||||||
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
|
private const val AUTOFILL_RESPONSE_REQUEST_CODE = 8165
|
||||||
|
|
||||||
private const val ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
|
private const val ASSIST_STRUCTURE = AutofillManager.EXTRA_ASSIST_STRUCTURE
|
||||||
|
const val KEY_SEARCH_INFO = "KEY_SEARCH_INFO"
|
||||||
|
|
||||||
fun retrieveAssistStructure(intent: Intent?): AssistStructure? {
|
fun retrieveAssistStructure(intent: Intent?): AssistStructure? {
|
||||||
intent?.let {
|
intent?.let {
|
||||||
@@ -62,11 +71,28 @@ object AutofillHelper {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildDataset(context: Context,
|
internal fun addHeader(responseBuilder: FillResponse.Builder,
|
||||||
entryInfo: EntryInfo,
|
packageName: String,
|
||||||
struct: StructureParser.Result): Dataset? {
|
webDomain: String?,
|
||||||
|
applicationId: String?) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
if (webDomain != null) {
|
||||||
|
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_web_domain).apply {
|
||||||
|
setTextViewText(R.id.autofill_web_domain_text, webDomain)
|
||||||
|
})
|
||||||
|
} else if (applicationId != null) {
|
||||||
|
responseBuilder.setHeader(RemoteViews(packageName, R.layout.item_autofill_app_id).apply {
|
||||||
|
setTextViewText(R.id.autofill_app_id_text, applicationId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun buildDataset(context: Context,
|
||||||
|
entryInfo: EntryInfo,
|
||||||
|
struct: StructureParser.Result): Dataset? {
|
||||||
val title = makeEntryTitle(entryInfo)
|
val title = makeEntryTitle(entryInfo)
|
||||||
val views = newRemoteViews(context.packageName, title)
|
val views = newRemoteViews(context, title, entryInfo.icon)
|
||||||
val builder = Dataset.Builder(views)
|
val builder = Dataset.Builder(views)
|
||||||
builder.setId(entryInfo.id)
|
builder.setId(entryInfo.id)
|
||||||
|
|
||||||
@@ -86,9 +112,16 @@ object AutofillHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to hit when right key is selected
|
* Build the Autofill response for one entry
|
||||||
*/
|
*/
|
||||||
fun buildResponseWhenEntrySelected(activity: Activity, entryInfo: EntryInfo) {
|
fun buildResponse(activity: Activity, entryInfo: EntryInfo) {
|
||||||
|
buildResponse(activity, ArrayList<EntryInfo>().apply { add(entryInfo) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the Autofill response for many entry
|
||||||
|
*/
|
||||||
|
fun buildResponse(activity: Activity, entriesInfo: List<EntryInfo>) {
|
||||||
var setResultOk = false
|
var setResultOk = false
|
||||||
activity.intent?.extras?.let { extras ->
|
activity.intent?.extras?.let { extras ->
|
||||||
if (extras.containsKey(ASSIST_STRUCTURE)) {
|
if (extras.containsKey(ASSIST_STRUCTURE)) {
|
||||||
@@ -96,8 +129,9 @@ object AutofillHelper {
|
|||||||
StructureParser(structure).parse()?.let { result ->
|
StructureParser(structure).parse()?.let { result ->
|
||||||
// New Response
|
// New Response
|
||||||
val responseBuilder = FillResponse.Builder()
|
val responseBuilder = FillResponse.Builder()
|
||||||
val dataset = buildDataset(activity, entryInfo, result)
|
entriesInfo.forEach {
|
||||||
responseBuilder.addDataset(dataset)
|
responseBuilder.addDataset(buildDataset(activity, it, result))
|
||||||
|
}
|
||||||
val mReplyIntent = Intent()
|
val mReplyIntent = Intent()
|
||||||
Log.d(activity.javaClass.name, "Successed Autofill auth.")
|
Log.d(activity.javaClass.name, "Successed Autofill auth.")
|
||||||
mReplyIntent.putExtra(
|
mReplyIntent.putExtra(
|
||||||
@@ -115,12 +149,48 @@ object AutofillHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to perform actions if item is found or not after an auto search in [database]
|
||||||
|
*/
|
||||||
|
fun checkAutoSearchInfo(context: Context,
|
||||||
|
database: Database,
|
||||||
|
searchInfo: SearchInfo?,
|
||||||
|
onItemsFound: (items: List<EntryInfo>) -> Unit,
|
||||||
|
onItemNotFound: () -> Unit,
|
||||||
|
onDatabaseClosed: () -> Unit) {
|
||||||
|
if (database.loaded && TimeoutHelper.checkTime(context)) {
|
||||||
|
var searchWithoutUI = false
|
||||||
|
if (PreferencesUtil.isAutofillAutoSearchEnable(context)
|
||||||
|
&& searchInfo != null) {
|
||||||
|
// If search provide results
|
||||||
|
database.createVirtualGroupFromSearch(searchInfo, SearchHelper.MAX_SEARCH_ENTRY)?.let { searchGroup ->
|
||||||
|
if (searchGroup.getNumberOfChildEntries() > 0) {
|
||||||
|
searchWithoutUI = true
|
||||||
|
onItemsFound.invoke(
|
||||||
|
searchGroup.getChildEntriesInfo(database))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!searchWithoutUI) {
|
||||||
|
onItemNotFound.invoke()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onDatabaseClosed.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to start an activity with an Autofill for result
|
* Utility method to start an activity with an Autofill for result
|
||||||
*/
|
*/
|
||||||
fun startActivityForAutofillResult(activity: Activity, intent: Intent, assistStructure: AssistStructure) {
|
fun startActivityForAutofillResult(activity: Activity,
|
||||||
|
intent: Intent,
|
||||||
|
assistStructure: AssistStructure,
|
||||||
|
searchInfo: SearchInfo?) {
|
||||||
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent)
|
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent)
|
||||||
intent.putExtra(ASSIST_STRUCTURE, assistStructure)
|
intent.putExtra(ASSIST_STRUCTURE, assistStructure)
|
||||||
|
searchInfo?.let {
|
||||||
|
intent.putExtra(KEY_SEARCH_INFO, it)
|
||||||
|
}
|
||||||
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
|
activity.startActivityForResult(intent, AUTOFILL_RESPONSE_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +209,18 @@ object AutofillHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newRemoteViews(packageName: String, remoteViewsText: String): RemoteViews {
|
private fun newRemoteViews(context: Context,
|
||||||
val presentation = RemoteViews(packageName, R.layout.item_autofill_service)
|
remoteViewsText: String,
|
||||||
presentation.setTextViewText(R.id.text, remoteViewsText)
|
remoteViewsIcon: IconImage? = null): RemoteViews {
|
||||||
|
val presentation = RemoteViews(context.packageName, R.layout.item_autofill_entry)
|
||||||
|
presentation.setTextViewText(R.id.autofill_entry_text, remoteViewsText)
|
||||||
|
if (remoteViewsIcon != null) {
|
||||||
|
presentation.assignDatabaseIcon(context,
|
||||||
|
R.id.autofill_entry_icon,
|
||||||
|
Database.getInstance().drawFactory,
|
||||||
|
remoteViewsIcon,
|
||||||
|
ContextCompat.getColor(context, R.color.green))
|
||||||
|
}
|
||||||
return presentation
|
return presentation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||||
import com.kunzisoft.keepass.activities.GroupActivity
|
import com.kunzisoft.keepass.activities.GroupActivity
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
class AutofillLauncherActivity : AppCompatActivity() {
|
class AutofillLauncherActivity : AppCompatActivity() {
|
||||||
@@ -40,13 +40,31 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
|||||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
||||||
val assistStructure = AutofillHelper.retrieveAssistStructure(intent)
|
val assistStructure = AutofillHelper.retrieveAssistStructure(intent)
|
||||||
if (assistStructure != null) {
|
if (assistStructure != null) {
|
||||||
if (Database.getInstance().loaded && TimeoutHelper.checkTime(this))
|
// Build search param
|
||||||
GroupActivity.launchForAutofillResult(this,
|
val searchInfo = SearchInfo().apply {
|
||||||
assistStructure)
|
applicationId = intent.getStringExtra(KEY_SEARCH_APPLICATION_ID)
|
||||||
else {
|
webDomain = intent.getStringExtra(KEY_SEARCH_DOMAIN)
|
||||||
FileDatabaseSelectActivity.launchForAutofillResult(this,
|
|
||||||
assistStructure)
|
|
||||||
}
|
}
|
||||||
|
// If database is open
|
||||||
|
AutofillHelper.checkAutoSearchInfo(this,
|
||||||
|
Database.getInstance(),
|
||||||
|
searchInfo,
|
||||||
|
{ items ->
|
||||||
|
// Items found
|
||||||
|
AutofillHelper.buildResponse(this, items)
|
||||||
|
finish()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Show the database UI to select the entry
|
||||||
|
GroupActivity.launchForAutofillResult(this,
|
||||||
|
assistStructure)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// If database not open
|
||||||
|
FileDatabaseSelectActivity.launchForAutofillResult(this,
|
||||||
|
assistStructure, searchInfo)
|
||||||
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
setResult(Activity.RESULT_CANCELED)
|
setResult(Activity.RESULT_CANCELED)
|
||||||
finish()
|
finish()
|
||||||
@@ -62,10 +80,20 @@ class AutofillLauncherActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun getAuthIntentSenderForResponse(context: Context): IntentSender {
|
private const val KEY_SEARCH_APPLICATION_ID = "KEY_SEARCH_APPLICATION_ID"
|
||||||
val intent = Intent(context, AutofillLauncherActivity::class.java)
|
private const val KEY_SEARCH_DOMAIN = "KEY_SEARCH_DOMAIN"
|
||||||
|
|
||||||
|
fun getAuthIntentSenderForResponse(context: Context,
|
||||||
|
searchInfo: SearchInfo? = null): IntentSender {
|
||||||
return PendingIntent.getActivity(context, 0,
|
return PendingIntent.getActivity(context, 0,
|
||||||
intent, PendingIntent.FLAG_CANCEL_CURRENT).intentSender
|
// Doesn't work with Parcelable (don't know why?)
|
||||||
|
Intent(context, AutofillLauncherActivity::class.java).apply {
|
||||||
|
searchInfo?.let {
|
||||||
|
putExtra(KEY_SEARCH_APPLICATION_ID, it.applicationId)
|
||||||
|
putExtra(KEY_SEARCH_DOMAIN, it.webDomain)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PendingIntent.FLAG_CANCEL_CURRENT).intentSender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,31 +22,78 @@ package com.kunzisoft.keepass.autofill
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
import android.service.autofill.*
|
import android.service.autofill.*
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
class KeeAutofillService : AutofillService() {
|
class KeeAutofillService : AutofillService() {
|
||||||
|
|
||||||
override fun onFillRequest(request: FillRequest, cancellationSignal: CancellationSignal,
|
override fun onFillRequest(request: FillRequest,
|
||||||
|
cancellationSignal: CancellationSignal,
|
||||||
callback: FillCallback) {
|
callback: FillCallback) {
|
||||||
val fillContexts = request.fillContexts
|
val fillContexts = request.fillContexts
|
||||||
val latestStructure = fillContexts[fillContexts.size - 1].structure
|
val latestStructure = fillContexts[fillContexts.size - 1].structure
|
||||||
|
|
||||||
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
|
cancellationSignal.setOnCancelListener { Log.w(TAG, "Cancel autofill.") }
|
||||||
|
|
||||||
val responseBuilder = FillResponse.Builder()
|
|
||||||
// Check user's settings for authenticating Responses and Datasets.
|
// Check user's settings for authenticating Responses and Datasets.
|
||||||
val parseResult = StructureParser(latestStructure).parse()
|
StructureParser(latestStructure).parse()?.let { parseResult ->
|
||||||
parseResult?.allAutofillIds()?.let { autofillIds ->
|
|
||||||
|
val searchInfo = SearchInfo().apply {
|
||||||
|
applicationId = parseResult.applicationId
|
||||||
|
webDomain = parseResult.domain
|
||||||
|
}
|
||||||
|
|
||||||
|
AutofillHelper.checkAutoSearchInfo(this,
|
||||||
|
Database.getInstance(),
|
||||||
|
searchInfo,
|
||||||
|
{ items ->
|
||||||
|
val responseBuilder = FillResponse.Builder()
|
||||||
|
AutofillHelper.addHeader(responseBuilder, packageName,
|
||||||
|
parseResult.domain, parseResult.applicationId)
|
||||||
|
items.forEach {
|
||||||
|
responseBuilder.addDataset(AutofillHelper.buildDataset(this, it, parseResult))
|
||||||
|
}
|
||||||
|
callback.onSuccess(responseBuilder.build())
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Show UI if no search result
|
||||||
|
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Show UI if database not open
|
||||||
|
showUIForEntrySelection(parseResult, searchInfo, callback)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showUIForEntrySelection(parseResult: StructureParser.Result,
|
||||||
|
searchInfo: SearchInfo,
|
||||||
|
callback: FillCallback) {
|
||||||
|
parseResult.allAutofillIds().let { autofillIds ->
|
||||||
if (autofillIds.isNotEmpty()) {
|
if (autofillIds.isNotEmpty()) {
|
||||||
// If the entire Autofill Response is authenticated, AuthActivity is used
|
// If the entire Autofill Response is authenticated, AuthActivity is used
|
||||||
// to generate Response.
|
// to generate Response.
|
||||||
val sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this)
|
val sender = AutofillLauncherActivity.getAuthIntentSenderForResponse(this,
|
||||||
val presentation = RemoteViews(packageName, R.layout.item_autofill_service_unlock)
|
searchInfo)
|
||||||
responseBuilder.setAuthentication(autofillIds, sender, presentation)
|
val responseBuilder = FillResponse.Builder()
|
||||||
|
val remoteViewsUnlock: RemoteViews = if (!parseResult.domain.isNullOrEmpty()) {
|
||||||
|
RemoteViews(packageName, R.layout.item_autofill_unlock_web_domain).apply {
|
||||||
|
setTextViewText(R.id.autofill_web_domain_text, parseResult.domain)
|
||||||
|
}
|
||||||
|
} else if (!parseResult.applicationId.isNullOrEmpty()) {
|
||||||
|
RemoteViews(packageName, R.layout.item_autofill_unlock_app_id).apply {
|
||||||
|
setTextViewText(R.id.autofill_app_id_text, parseResult.applicationId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RemoteViews(packageName, R.layout.item_autofill_unlock)
|
||||||
|
}
|
||||||
|
responseBuilder.setAuthentication(autofillIds, sender, remoteViewsUnlock)
|
||||||
callback.onSuccess(responseBuilder.build())
|
callback.onSuccess(responseBuilder.build())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package com.kunzisoft.keepass.autofill
|
|||||||
|
|
||||||
import android.app.assist.AssistStructure
|
import android.app.assist.AssistStructure
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.text.InputType
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -34,49 +35,63 @@ import java.util.*
|
|||||||
internal class StructureParser(private val structure: AssistStructure) {
|
internal class StructureParser(private val structure: AssistStructure) {
|
||||||
private var result: Result? = null
|
private var result: Result? = null
|
||||||
private var usernameCandidate: AutofillId? = null
|
private var usernameCandidate: AutofillId? = null
|
||||||
|
private var usernameNeeded = true
|
||||||
|
|
||||||
fun parse(): Result? {
|
fun parse(): Result? {
|
||||||
result = Result()
|
try {
|
||||||
result?.apply {
|
result = Result()
|
||||||
usernameCandidate = null
|
result?.apply {
|
||||||
mainLoop@ for (i in 0 until structure.windowNodeCount) {
|
usernameCandidate = null
|
||||||
val windowNode = structure.getWindowNodeAt(i)
|
mainLoop@ for (i in 0 until structure.windowNodeCount) {
|
||||||
/*
|
val windowNode = structure.getWindowNodeAt(i)
|
||||||
title.add(windowNode.title)
|
applicationId = windowNode.title.toString().split("/")[0]
|
||||||
windowNode.rootViewNode.webDomain?.let {
|
Log.d(TAG, "Autofill applicationId: $applicationId")
|
||||||
webDomain.add(it)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if (parseViewNode(windowNode.rootViewNode))
|
|
||||||
break@mainLoop
|
|
||||||
}
|
|
||||||
// If not explicit username field found, add the field just before password field.
|
|
||||||
if (usernameId == null && passwordId != null && usernameCandidate != null)
|
|
||||||
usernameId = usernameCandidate
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the result only if password field is retrieved
|
if (parseViewNode(windowNode.rootViewNode))
|
||||||
return if (result?.passwordId != null)
|
break@mainLoop
|
||||||
result
|
}
|
||||||
else
|
// If not explicit username field found, add the field just before password field.
|
||||||
null
|
if (usernameId == null && passwordId != null && usernameCandidate != null)
|
||||||
|
usernameId = usernameCandidate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the result only if password field is retrieved
|
||||||
|
return if ((!usernameNeeded || result?.usernameId != null)
|
||||||
|
&& result?.passwordId != null)
|
||||||
|
result
|
||||||
|
else
|
||||||
|
null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseViewNode(node: AssistStructure.ViewNode): Boolean {
|
private fun parseViewNode(node: AssistStructure.ViewNode): Boolean {
|
||||||
if (node.autofillId != null) {
|
// Get the domain of a web app
|
||||||
val hints = node.autofillHints
|
node.webDomain?.let {
|
||||||
if (hints != null && hints.isNotEmpty()) {
|
result?.domain = it
|
||||||
if (parseNodeByAutofillHint(node))
|
Log.d(TAG, "Autofill domain: $it")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only parse visible nodes
|
||||||
|
if (node.visibility == View.VISIBLE) {
|
||||||
|
if (node.autofillId != null
|
||||||
|
&& node.autofillType == View.AUTOFILL_TYPE_TEXT) {
|
||||||
|
// Parse methods
|
||||||
|
val hints = node.autofillHints
|
||||||
|
if (hints != null && hints.isNotEmpty()) {
|
||||||
|
if (parseNodeByAutofillHint(node))
|
||||||
|
return true
|
||||||
|
} else if (parseNodeByHtmlAttributes(node))
|
||||||
return true
|
return true
|
||||||
} else {
|
else if (parseNodeByAndroidInput(node))
|
||||||
if (parseNodeByHtmlAttributes(node))
|
return true
|
||||||
|
}
|
||||||
|
// Recursive method to process each node
|
||||||
|
for (i in 0 until node.childCount) {
|
||||||
|
if (parseViewNode(node.getChildAt(i)))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Recursive method to process each node
|
|
||||||
for (i in 0 until node.childCount) {
|
|
||||||
if (parseViewNode(node.getChildAt(i)))
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -85,22 +100,26 @@ internal class StructureParser(private val structure: AssistStructure) {
|
|||||||
val autofillId = node.autofillId
|
val autofillId = node.autofillId
|
||||||
node.autofillHints?.forEach {
|
node.autofillHints?.forEach {
|
||||||
when {
|
when {
|
||||||
it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_USERNAME
|
it.equals(View.AUTOFILL_HINT_USERNAME, true)
|
||||||
|| it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_EMAIL_ADDRESS
|
|| it.equals(View.AUTOFILL_HINT_EMAIL_ADDRESS, true)
|
||||||
|| it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_PHONE -> {
|
|| it.equals(View.AUTOFILL_HINT_PHONE, true)
|
||||||
|
|| it.equals("email", true)
|
||||||
|
|| it.equals("usernameOrEmail", true)-> {
|
||||||
result?.usernameId = autofillId
|
result?.usernameId = autofillId
|
||||||
Log.d(TAG, "Autofill username hint")
|
Log.d(TAG, "Autofill username hint")
|
||||||
}
|
}
|
||||||
it.toLowerCase(Locale.ENGLISH) == View.AUTOFILL_HINT_PASSWORD
|
it.equals(View.AUTOFILL_HINT_PASSWORD, true)
|
||||||
|| it.toLowerCase(Locale.ENGLISH).contains("password") -> {
|
|| it.contains("password", true) -> {
|
||||||
result?.passwordId = autofillId
|
result?.passwordId = autofillId
|
||||||
Log.d(TAG, "Autofill password hint")
|
Log.d(TAG, "Autofill password hint")
|
||||||
|
// Username not needed in this specific case
|
||||||
|
usernameNeeded = false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Ignore autocomplete="off"
|
// Ignore autocomplete="off"
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
|
||||||
it.toLowerCase(Locale.ENGLISH) == "off" ||
|
it.equals("off", true) ||
|
||||||
it.toLowerCase(Locale.ENGLISH) == "on" -> {
|
it.equals("on", true) -> {
|
||||||
Log.d(TAG, "Autofill web hint")
|
Log.d(TAG, "Autofill web hint")
|
||||||
return parseNodeByHtmlAttributes(node)
|
return parseNodeByHtmlAttributes(node)
|
||||||
}
|
}
|
||||||
@@ -121,15 +140,15 @@ internal class StructureParser(private val structure: AssistStructure) {
|
|||||||
when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) {
|
when (pairAttribute.second.toLowerCase(Locale.ENGLISH)) {
|
||||||
"tel", "email" -> {
|
"tel", "email" -> {
|
||||||
result?.usernameId = autofillId
|
result?.usernameId = autofillId
|
||||||
Log.d(TAG, "Autofill username type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
Log.d(TAG, "Autofill username web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
||||||
}
|
}
|
||||||
"text" -> {
|
"text" -> {
|
||||||
usernameCandidate = autofillId
|
usernameCandidate = autofillId
|
||||||
Log.d(TAG, "Autofill type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
Log.d(TAG, "Autofill username candidate web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
||||||
}
|
}
|
||||||
"password" -> {
|
"password" -> {
|
||||||
result?.passwordId = autofillId
|
result?.passwordId = autofillId
|
||||||
Log.d(TAG, "Autofill password type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
Log.d(TAG, "Autofill password web type: ${node.htmlInfo?.tag} ${node.htmlInfo?.attributes}")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,8 +160,57 @@ internal class StructureParser(private val structure: AssistStructure) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseNodeByAndroidInput(node: AssistStructure.ViewNode): Boolean {
|
||||||
|
val autofillId = node.autofillId
|
||||||
|
val inputType = node.inputType
|
||||||
|
if (inputType and InputType.TYPE_CLASS_TEXT != 0) {
|
||||||
|
when {
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS != 0 -> {
|
||||||
|
result?.usernameId = autofillId
|
||||||
|
Log.d(TAG, "Autofill username android type: $inputType")
|
||||||
|
}
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_NORMAL != 0 ||
|
||||||
|
inputType and InputType.TYPE_NUMBER_VARIATION_NORMAL != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_PERSON_NAME != 0 -> {
|
||||||
|
usernameCandidate = autofillId
|
||||||
|
Log.d(TAG, "Autofill username candidate android type: $inputType")
|
||||||
|
}
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD != 0 ||
|
||||||
|
inputType and InputType.TYPE_NUMBER_VARIATION_PASSWORD != 0 -> {
|
||||||
|
result?.passwordId = autofillId
|
||||||
|
Log.d(TAG, "Autofill password android type: $inputType")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_FILTER != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_PHONETIC != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_URI != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS != 0 ||
|
||||||
|
inputType and InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD != 0 -> {
|
||||||
|
// Type not used
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.d(TAG, "Autofill unknown android type: $inputType")
|
||||||
|
usernameCandidate = autofillId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
internal class Result {
|
internal class Result {
|
||||||
|
var applicationId: String? = null
|
||||||
|
var domain: String? = null
|
||||||
|
set(value) {
|
||||||
|
if (field == null)
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
var usernameId: AutofillId? = null
|
var usernameId: AutofillId? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field == null)
|
if (field == null)
|
||||||
|
|||||||
@@ -19,15 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.backup
|
package com.kunzisoft.keepass.backup
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.backup.BackupAgentHelper
|
import android.app.backup.BackupAgentHelper
|
||||||
import android.app.backup.SharedPreferencesBackupHelper
|
import android.app.backup.SharedPreferencesBackupHelper
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
|
||||||
class SettingsBackupAgent : BackupAgentHelper() {
|
class SettingsBackupAgent : BackupAgentHelper() {
|
||||||
|
|
||||||
//TODO Backup
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
val defaultPrefs = this.packageName + "_preferences"
|
val defaultPrefs = this.packageName + "_preferences"
|
||||||
val prefHelper = SharedPreferencesBackupHelper(this, defaultPrefs)
|
val prefHelper = SharedPreferencesBackupHelper(this, defaultPrefs)
|
||||||
|
|||||||
@@ -300,7 +300,6 @@ class AdvancedUnlockedManager(var context: FragmentActivity,
|
|||||||
|
|
||||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
|
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
|
||||||
loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec)
|
loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec)
|
||||||
// TODO setAdvancedUnlockedMessageView(R.string.encrypted_value_stored)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleDecryptedResult(decryptedValue: String) {
|
override fun handleDecryptedResult(decryptedValue: String) {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
|
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
|
||||||
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
|
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
|
||||||
setConfirmationRequired(true)
|
setConfirmationRequired(true)
|
||||||
// TODO device credential
|
// TODO device credential #102 #152
|
||||||
/*
|
/*
|
||||||
if (keyguardManager?.isDeviceSecure == true)
|
if (keyguardManager?.isDeviceSecure == true)
|
||||||
setDeviceCredentialAllowed(true)
|
setDeviceCredentialAllowed(true)
|
||||||
@@ -73,7 +73,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
|||||||
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
|
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
|
||||||
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
||||||
setConfirmationRequired(false)
|
setConfirmationRequired(false)
|
||||||
// TODO device credential
|
// TODO device credential #102 #152
|
||||||
/*
|
/*
|
||||||
if (keyguardManager?.isDeviceSecure == true)
|
if (keyguardManager?.isDeviceSecure == true)
|
||||||
setDeviceCredentialAllowed(true)
|
setDeviceCredentialAllowed(true)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import com.kunzisoft.keepass.crypto.engine.AesEngine
|
|||||||
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
|
import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine
|
||||||
import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
||||||
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
|
import com.kunzisoft.keepass.crypto.engine.TwofishEngine
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -37,6 +37,7 @@ object CipherFactory {
|
|||||||
private var blacklisted: Boolean = false
|
private var blacklisted: Boolean = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||||
Security.addProvider(BouncyCastleProvider())
|
Security.addProvider(BouncyCastleProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,16 +19,18 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|
||||||
enum class CrsAlgorithm constructor(val id: Int) {
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
|
||||||
Null(0),
|
enum class CrsAlgorithm constructor(val id: UnsignedInt) {
|
||||||
ArcFourVariant(1),
|
|
||||||
Salsa20(2),
|
Null(UnsignedInt(0)),
|
||||||
ChaCha20(3);
|
ArcFourVariant(UnsignedInt(1)),
|
||||||
|
Salsa20(UnsignedInt(2)),
|
||||||
|
ChaCha20(UnsignedInt(3));
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun fromId(num: Int): CrsAlgorithm? {
|
fun fromId(num: UnsignedInt): CrsAlgorithm? {
|
||||||
for (e in values()) {
|
for (e in values()) {
|
||||||
if (e.id == num) {
|
if (e.id == num) {
|
||||||
return e
|
return e
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto
|
package com.kunzisoft.keepass.crypto
|
||||||
|
|
||||||
import org.spongycastle.crypto.StreamCipher
|
import org.bouncycastle.crypto.StreamCipher
|
||||||
import org.spongycastle.crypto.engines.ChaCha7539Engine
|
import org.bouncycastle.crypto.engines.ChaCha7539Engine
|
||||||
import org.spongycastle.crypto.engines.Salsa20Engine
|
import org.bouncycastle.crypto.engines.Salsa20Engine
|
||||||
import org.spongycastle.crypto.params.KeyParameter
|
import org.bouncycastle.crypto.params.KeyParameter
|
||||||
import org.spongycastle.crypto.params.ParametersWithIV
|
import org.bouncycastle.crypto.params.ParametersWithIV
|
||||||
|
|
||||||
object StreamCipherFactory {
|
object StreamCipherFactory {
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.engine
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
import org.spongycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import java.security.InvalidAlgorithmParameterException
|
import java.security.InvalidAlgorithmParameterException
|
||||||
import java.security.InvalidKeyException
|
import java.security.InvalidKeyException
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
|
|||||||
@@ -17,18 +17,20 @@
|
|||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.CipherFactory;
|
import com.kunzisoft.keepass.crypto.CipherFactory.deviceBlacklisted
|
||||||
|
|
||||||
public class FinalKeyFactory {
|
object AESKeyTransformerFactory : KeyTransformer() {
|
||||||
public static FinalKey createFinalKey() {
|
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
|
||||||
// Prefer the native final key implementation
|
// Prefer the native final key implementation
|
||||||
if ( !CipherFactory.INSTANCE.deviceBlacklisted() && NativeFinalKey.available() ) {
|
val keyTransformer = if (!deviceBlacklisted()
|
||||||
return new NativeFinalKey();
|
&& NativeAESKeyTransformer.available()) {
|
||||||
|
NativeAESKeyTransformer()
|
||||||
} else {
|
} else {
|
||||||
// Fall back on the android crypto implementation
|
// Fall back on the android crypto implementation
|
||||||
return new AndroidFinalKey();
|
AndroidAESKeyTransformer()
|
||||||
}
|
}
|
||||||
|
return keyTransformer.transformMasterKey(seed, key, rounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.crypto.finalkey
|
||||||
|
|
||||||
|
import java.io.IOException
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.security.InvalidKeyException
|
||||||
|
import java.security.MessageDigest
|
||||||
|
import java.security.NoSuchAlgorithmException
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.NoSuchPaddingException
|
||||||
|
import javax.crypto.ShortBufferException
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
class AndroidAESKeyTransformer : KeyTransformer() {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray? {
|
||||||
|
val cipher: Cipher = try {
|
||||||
|
Cipher.getInstance("AES/ECB/NoPadding")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IOException("Unable to get the cipher", e)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(seed, "AES"))
|
||||||
|
} catch (e: InvalidKeyException) {
|
||||||
|
throw IOException("Unable to init the cipher", e)
|
||||||
|
}
|
||||||
|
if (key == null) {
|
||||||
|
throw IOException("Invalid key")
|
||||||
|
}
|
||||||
|
if (rounds == null) {
|
||||||
|
throw IOException("Invalid rounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt key rounds times
|
||||||
|
val keyLength = key.size
|
||||||
|
val newKey = ByteArray(keyLength)
|
||||||
|
System.arraycopy(key, 0, newKey, 0, keyLength)
|
||||||
|
val destKey = ByteArray(keyLength)
|
||||||
|
for (i in 0 until rounds) {
|
||||||
|
try {
|
||||||
|
cipher.update(newKey, 0, newKey.size, destKey, 0)
|
||||||
|
System.arraycopy(destKey, 0, newKey, 0, newKey.size)
|
||||||
|
} catch (e: ShortBufferException) {
|
||||||
|
throw IOException("Short buffer", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash the key
|
||||||
|
val messageDigest: MessageDigest = try {
|
||||||
|
MessageDigest.getInstance("SHA-256")
|
||||||
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
|
throw IOException("SHA-256 not implemented here: " + e.message)
|
||||||
|
}
|
||||||
|
messageDigest.update(newKey)
|
||||||
|
return messageDigest.digest()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePassDX.
|
|
||||||
*
|
|
||||||
* KeePassDX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePassDX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.ShortBufferException;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
public class AndroidFinalKey extends FinalKey {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] transformMasterKey(byte[] pKeySeed, byte[] pKey, long rounds) throws IOException {
|
|
||||||
Cipher cipher;
|
|
||||||
try {
|
|
||||||
cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new IOException("NoSuchAlgorithm: " + e.getMessage());
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
throw new IOException("NoSuchPadding: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(pKeySeed, "AES"));
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
throw new IOException("InvalidPasswordException: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt key rounds times
|
|
||||||
byte[] newKey = new byte[pKey.length];
|
|
||||||
System.arraycopy(pKey, 0, newKey, 0, pKey.length);
|
|
||||||
byte[] destKey = new byte[pKey.length];
|
|
||||||
for (int i = 0; i < rounds; i++) {
|
|
||||||
try {
|
|
||||||
cipher.update(newKey, 0, newKey.length, destKey, 0);
|
|
||||||
System.arraycopy(destKey, 0, newKey, 0, newKey.length);
|
|
||||||
|
|
||||||
} catch (ShortBufferException e) {
|
|
||||||
throw new IOException("Short buffer: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash the key
|
|
||||||
MessageDigest md = null;
|
|
||||||
try {
|
|
||||||
md = MessageDigest.getInstance("SHA-256");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
assert true;
|
|
||||||
throw new IOException("SHA-256 not implemented here: " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
md.update(newKey);
|
|
||||||
return md.digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -17,10 +17,11 @@
|
|||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.crypto.finalkey;
|
package com.kunzisoft.keepass.crypto.finalkey
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException
|
||||||
|
|
||||||
public abstract class FinalKey {
|
abstract class KeyTransformer {
|
||||||
public abstract byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException;
|
@Throws(IOException::class)
|
||||||
}
|
abstract fun transformMasterKey(seed: ByteArray?, key: ByteArray?, rounds: Long?): ByteArray?
|
||||||
|
}
|
||||||
@@ -21,17 +21,20 @@ package com.kunzisoft.keepass.crypto.finalkey;
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.NativeLib;
|
import com.kunzisoft.keepass.crypto.NativeLib;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
public class NativeFinalKey extends FinalKey {
|
public class NativeAESKeyTransformer extends KeyTransformer {
|
||||||
|
|
||||||
public static boolean available() {
|
public static boolean available() {
|
||||||
return NativeLib.INSTANCE.init();
|
return NativeLib.INSTANCE.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public byte[] transformMasterKey(byte[] seed, byte[] key, long rounds) throws IOException {
|
public byte[] transformMasterKey(@Nullable byte[] seed, @Nullable byte[] key, @Nullable Long rounds) throws IOException {
|
||||||
NativeLib.INSTANCE.init();
|
NativeLib.INSTANCE.init();
|
||||||
|
|
||||||
return nTransformMasterKey(seed, key, rounds);
|
return nTransformMasterKey(seed, key, rounds);
|
||||||
@@ -22,72 +22,69 @@ package com.kunzisoft.keepass.crypto.keyDerivation
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.crypto.CryptoUtil
|
import com.kunzisoft.keepass.crypto.CryptoUtil
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
|
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
|
||||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class AesKdf internal constructor() : KdfEngine() {
|
class AesKdf : KdfEngine() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
uuid = CIPHER_UUID
|
||||||
|
}
|
||||||
|
|
||||||
override val defaultParameters: KdfParameters
|
override val defaultParameters: KdfParameters
|
||||||
get() {
|
get() {
|
||||||
return KdfParameters(uuid!!).apply {
|
return KdfParameters(uuid!!).apply {
|
||||||
setParamUUID()
|
setParamUUID()
|
||||||
setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong())
|
setUInt64(PARAM_ROUNDS, defaultKeyRounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultKeyRounds: Long
|
override val defaultKeyRounds: Long = 6000L
|
||||||
get() = DEFAULT_ROUNDS.toLong()
|
|
||||||
|
|
||||||
init {
|
|
||||||
uuid = CIPHER_UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getName(resources: Resources): String {
|
override fun getName(resources: Resources): String {
|
||||||
return resources.getString(R.string.kdf_AES)
|
return resources.getString(R.string.kdf_AES)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
|
||||||
var currentMasterKey = masterKey
|
|
||||||
val rounds = p.getUInt64(PARAM_ROUNDS)
|
|
||||||
var seed = p.getByteArray(PARAM_SEED)
|
|
||||||
|
|
||||||
|
var seed = kdfParameters.getByteArray(PARAM_SEED)
|
||||||
|
if (seed != null && seed.size != 32) {
|
||||||
|
seed = CryptoUtil.hashSha256(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentMasterKey = masterKey
|
||||||
if (currentMasterKey.size != 32) {
|
if (currentMasterKey.size != 32) {
|
||||||
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
|
currentMasterKey = CryptoUtil.hashSha256(currentMasterKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seed.size != 32) {
|
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)
|
||||||
seed = CryptoUtil.hashSha256(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
val key = FinalKeyFactory.createFinalKey()
|
return AESKeyTransformerFactory.transformMasterKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
|
||||||
return key.transformMasterKey(seed, currentMasterKey, rounds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun randomize(p: KdfParameters) {
|
override fun randomize(kdfParameters: KdfParameters) {
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
|
|
||||||
val seed = ByteArray(32)
|
val seed = ByteArray(32)
|
||||||
random.nextBytes(seed)
|
random.nextBytes(seed)
|
||||||
|
|
||||||
p.setByteArray(PARAM_SEED, seed)
|
kdfParameters.setByteArray(PARAM_SEED, seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeyRounds(p: KdfParameters): Long {
|
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
|
||||||
return p.getUInt64(PARAM_ROUNDS)
|
return kdfParameters.getUInt64(PARAM_ROUNDS) ?: defaultKeyRounds
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
|
||||||
p.setUInt64(PARAM_ROUNDS, keyRounds)
|
kdfParameters.setUInt64(PARAM_ROUNDS, keyRounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val DEFAULT_ROUNDS = 6000
|
|
||||||
|
|
||||||
val CIPHER_UUID: UUID = bytes16ToUuid(
|
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||||
byteArrayOf(0xC9.toByte(),
|
byteArrayOf(0xC9.toByte(),
|
||||||
0xD9.toByte(),
|
0xD9.toByte(),
|
||||||
@@ -106,7 +103,7 @@ class AesKdf internal constructor() : KdfEngine() {
|
|||||||
0x4F.toByte(),
|
0x4F.toByte(),
|
||||||
0xEA.toByte()))
|
0xEA.toByte()))
|
||||||
|
|
||||||
const val PARAM_ROUNDS = "R"
|
const val PARAM_ROUNDS = "R" // UInt64
|
||||||
const val PARAM_SEED = "S"
|
const val PARAM_SEED = "S" // Byte array
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -53,35 +54,49 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray {
|
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
|
||||||
|
|
||||||
val salt = p.getByteArray(PARAM_SALT)
|
val salt = kdfParameters.getByteArray(PARAM_SALT)
|
||||||
val parallelism = p.getUInt32(PARAM_PARALLELISM).toInt()
|
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
|
||||||
val memory = p.getUInt64(PARAM_MEMORY)
|
UnsignedInt(it)
|
||||||
val iterations = p.getUInt64(PARAM_ITERATIONS)
|
}
|
||||||
val version = p.getUInt32(PARAM_VERSION)
|
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.div(MEMORY_BLOCK_SIZE)?.let {
|
||||||
val secretKey = p.getByteArray(PARAM_SECRET_KEY)
|
UnsignedInt.fromLong(it)
|
||||||
val assocData = p.getByteArray(PARAM_ASSOC_DATA)
|
}
|
||||||
|
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.let {
|
||||||
|
UnsignedInt.fromLong(it)
|
||||||
|
}
|
||||||
|
val version = kdfParameters.getUInt32(PARAM_VERSION)?.let {
|
||||||
|
UnsignedInt(it)
|
||||||
|
}
|
||||||
|
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
|
||||||
|
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
|
||||||
|
|
||||||
return Argon2Native.transformKey(masterKey, salt, parallelism, memory, iterations,
|
return Argon2Native.transformKey(masterKey,
|
||||||
secretKey, assocData, version)
|
salt,
|
||||||
|
parallelism,
|
||||||
|
memory,
|
||||||
|
iterations,
|
||||||
|
secretKey,
|
||||||
|
assocData,
|
||||||
|
version)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun randomize(p: KdfParameters) {
|
override fun randomize(kdfParameters: KdfParameters) {
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
|
|
||||||
val salt = ByteArray(32)
|
val salt = ByteArray(32)
|
||||||
random.nextBytes(salt)
|
random.nextBytes(salt)
|
||||||
|
|
||||||
p.setByteArray(PARAM_SALT, salt)
|
kdfParameters.setByteArray(PARAM_SALT, salt)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKeyRounds(p: KdfParameters): Long {
|
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
|
||||||
return p.getUInt64(PARAM_ITERATIONS)
|
return kdfParameters.getUInt64(PARAM_ITERATIONS) ?: defaultKeyRounds
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setKeyRounds(p: KdfParameters, keyRounds: Long) {
|
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
|
||||||
p.setUInt64(PARAM_ITERATIONS, keyRounds)
|
kdfParameters.setUInt64(PARAM_ITERATIONS, keyRounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val minKeyRounds: Long
|
override val minKeyRounds: Long
|
||||||
@@ -90,12 +105,12 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
override val maxKeyRounds: Long
|
override val maxKeyRounds: Long
|
||||||
get() = MAX_ITERATIONS
|
get() = MAX_ITERATIONS
|
||||||
|
|
||||||
override fun getMemoryUsage(p: KdfParameters): Long {
|
override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
|
||||||
return p.getUInt64(PARAM_MEMORY)
|
return kdfParameters.getUInt64(PARAM_MEMORY) ?: defaultMemoryUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
|
||||||
p.setUInt64(PARAM_MEMORY, memory)
|
kdfParameters.setUInt64(PARAM_MEMORY, memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultMemoryUsage: Long
|
override val defaultMemoryUsage: Long
|
||||||
@@ -107,21 +122,23 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
override val maxMemoryUsage: Long
|
override val maxMemoryUsage: Long
|
||||||
get() = MAX_MEMORY
|
get() = MAX_MEMORY
|
||||||
|
|
||||||
override fun getParallelism(p: KdfParameters): Int {
|
override fun getParallelism(kdfParameters: KdfParameters): Long {
|
||||||
return p.getUInt32(PARAM_PARALLELISM).toInt() // TODO Verify
|
return kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
|
||||||
|
UnsignedInt(it).toLong()
|
||||||
|
} ?: defaultParallelism
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setParallelism(p: KdfParameters, parallelism: Int) {
|
override fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
|
||||||
p.setUInt32(PARAM_PARALLELISM, parallelism.toLong())
|
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromLong(parallelism))
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultParallelism: Int
|
override val defaultParallelism: Long
|
||||||
get() = DEFAULT_PARALLELISM.toInt()
|
get() = DEFAULT_PARALLELISM.toLong()
|
||||||
|
|
||||||
override val minParallelism: Int
|
override val minParallelism: Long
|
||||||
get() = MIN_PARALLELISM
|
get() = MIN_PARALLELISM
|
||||||
|
|
||||||
override val maxParallelism: Int
|
override val maxParallelism: Long
|
||||||
get() = MAX_PARALLELISM
|
get() = MAX_PARALLELISM
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -152,23 +169,24 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
|||||||
private const val PARAM_SECRET_KEY = "K" // byte[]
|
private const val PARAM_SECRET_KEY = "K" // byte[]
|
||||||
private const val PARAM_ASSOC_DATA = "A" // byte[]
|
private const val PARAM_ASSOC_DATA = "A" // byte[]
|
||||||
|
|
||||||
private const val MIN_VERSION: Long = 0x10
|
private val MIN_VERSION = UnsignedInt(0x10)
|
||||||
private const val MAX_VERSION: Long = 0x13
|
private val MAX_VERSION = UnsignedInt(0x13)
|
||||||
|
|
||||||
private const val MIN_SALT = 8
|
private const val MIN_SALT = 8
|
||||||
private const val MAX_SALT = Integer.MAX_VALUE
|
private val MAX_SALT = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
|
||||||
private const val MIN_ITERATIONS: Long = 1
|
private const val MIN_ITERATIONS: Long = 1L
|
||||||
private const val MAX_ITERATIONS = 4294967295L
|
private const val MAX_ITERATIONS = 4294967295L
|
||||||
|
|
||||||
private const val MIN_MEMORY = (1024 * 8).toLong()
|
private const val MIN_MEMORY = (1024 * 8).toLong()
|
||||||
private const val MAX_MEMORY = Integer.MAX_VALUE.toLong()
|
private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
private const val MEMORY_BLOCK_SIZE: Long = 1024L
|
||||||
|
|
||||||
private const val MIN_PARALLELISM = 1
|
private const val MIN_PARALLELISM: Long = 1L
|
||||||
private const val MAX_PARALLELISM = (1 shl 24) - 1
|
private const val MAX_PARALLELISM: Long = ((1 shl 24) - 1).toLong()
|
||||||
|
|
||||||
private const val DEFAULT_ITERATIONS: Long = 2
|
private const val DEFAULT_ITERATIONS: Long = 2L
|
||||||
private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
|
private const val DEFAULT_MEMORY = (1024 * 1024).toLong()
|
||||||
private const val DEFAULT_PARALLELISM: Long = 2
|
private val DEFAULT_PARALLELISM = UnsignedInt(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,20 +20,29 @@
|
|||||||
package com.kunzisoft.keepass.crypto.keyDerivation;
|
package com.kunzisoft.keepass.crypto.keyDerivation;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.NativeLib;
|
import com.kunzisoft.keepass.crypto.NativeLib;
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class Argon2Native {
|
public class Argon2Native {
|
||||||
|
|
||||||
public static byte[] transformKey(byte[] password, byte[] salt, int parallelism,
|
public static byte[] transformKey(byte[] password, byte[] salt, UnsignedInt parallelism,
|
||||||
long memory, long iterations, byte[] secretKey,
|
UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
|
||||||
byte[] associatedData, long version) throws IOException {
|
byte[] associatedData, UnsignedInt version) throws IOException {
|
||||||
NativeLib.INSTANCE.init();
|
NativeLib.INSTANCE.init();
|
||||||
|
|
||||||
return nTransformMasterKey(password, salt, parallelism, memory, iterations, secretKey, associatedData, version);
|
return nTransformMasterKey(
|
||||||
|
password,
|
||||||
|
salt,
|
||||||
|
parallelism.toInt(),
|
||||||
|
memory.toInt(),
|
||||||
|
iterations.toInt(),
|
||||||
|
secretKey,
|
||||||
|
associatedData,
|
||||||
|
version.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism,
|
private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism,
|
||||||
long memory, long iterations, byte[] secretKey,
|
int memory, int iterations, byte[] secretKey,
|
||||||
byte[] associatedData, long version) throws IOException;
|
byte[] associatedData, int version) throws IOException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.crypto.keyDerivation
|
package com.kunzisoft.keepass.crypto.keyDerivation
|
||||||
|
|
||||||
import com.kunzisoft.keepass.utils.ObjectNameResource
|
import com.kunzisoft.keepass.utils.ObjectNameResource
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
@@ -33,17 +34,17 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
|||||||
abstract val defaultParameters: KdfParameters
|
abstract val defaultParameters: KdfParameters
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
abstract fun transform(masterKey: ByteArray, p: KdfParameters): ByteArray
|
abstract fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray
|
||||||
|
|
||||||
abstract fun randomize(p: KdfParameters)
|
abstract fun randomize(kdfParameters: KdfParameters)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ITERATIONS
|
* ITERATIONS
|
||||||
*/
|
*/
|
||||||
|
|
||||||
abstract fun getKeyRounds(p: KdfParameters): Long
|
abstract fun getKeyRounds(kdfParameters: KdfParameters): Long
|
||||||
|
|
||||||
abstract fun setKeyRounds(p: KdfParameters, keyRounds: Long)
|
abstract fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long)
|
||||||
|
|
||||||
abstract val defaultKeyRounds: Long
|
abstract val defaultKeyRounds: Long
|
||||||
|
|
||||||
@@ -51,51 +52,51 @@ abstract class KdfEngine : ObjectNameResource, Serializable {
|
|||||||
get() = 1
|
get() = 1
|
||||||
|
|
||||||
open val maxKeyRounds: Long
|
open val maxKeyRounds: Long
|
||||||
get() = Int.MAX_VALUE.toLong()
|
get() = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MEMORY
|
* MEMORY
|
||||||
*/
|
*/
|
||||||
|
|
||||||
open fun getMemoryUsage(p: KdfParameters): Long {
|
open fun getMemoryUsage(kdfParameters: KdfParameters): Long {
|
||||||
return UNKNOWN_VALUE.toLong()
|
return UNKNOWN_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setMemoryUsage(p: KdfParameters, memory: Long) {
|
open fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
|
||||||
// Do nothing by default
|
// Do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
open val defaultMemoryUsage: Long
|
open val defaultMemoryUsage: Long
|
||||||
get() = UNKNOWN_VALUE.toLong()
|
get() = UNKNOWN_VALUE
|
||||||
|
|
||||||
open val minMemoryUsage: Long
|
open val minMemoryUsage: Long
|
||||||
get() = 1
|
get() = 1
|
||||||
|
|
||||||
open val maxMemoryUsage: Long
|
open val maxMemoryUsage: Long
|
||||||
get() = Int.MAX_VALUE.toLong()
|
get() = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* PARALLELISM
|
* PARALLELISM
|
||||||
*/
|
*/
|
||||||
|
|
||||||
open fun getParallelism(p: KdfParameters): Int {
|
open fun getParallelism(kdfParameters: KdfParameters): Long {
|
||||||
return UNKNOWN_VALUE
|
return UNKNOWN_VALUE
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setParallelism(p: KdfParameters, parallelism: Int) {
|
open fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
|
||||||
// Do nothing by default
|
// Do nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
open val defaultParallelism: Int
|
open val defaultParallelism: Long
|
||||||
get() = UNKNOWN_VALUE
|
get() = UNKNOWN_VALUE
|
||||||
|
|
||||||
open val minParallelism: Int
|
open val minParallelism: Long
|
||||||
get() = 1
|
get() = 1L
|
||||||
|
|
||||||
open val maxParallelism: Int
|
open val maxParallelism: Long
|
||||||
get() = Int.MAX_VALUE
|
get() = UnsignedInt.MAX_VALUE.toLong()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val UNKNOWN_VALUE = -1
|
const val UNKNOWN_VALUE: Long = -1L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import java.io.ByteArrayOutputStream
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
|
class KdfParameters(val uuid: UUID) : VariantDictionary() {
|
||||||
|
|
||||||
fun setParamUUID() {
|
fun setParamUUID() {
|
||||||
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
|
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
|
||||||
@@ -41,26 +41,25 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun deserialize(data: ByteArray): KdfParameters? {
|
fun deserialize(data: ByteArray): KdfParameters? {
|
||||||
val bis = ByteArrayInputStream(data)
|
val inputStream = LittleEndianDataInputStream(ByteArrayInputStream(data))
|
||||||
val lis = LittleEndianDataInputStream(bis)
|
val dictionary = deserialize(inputStream)
|
||||||
|
|
||||||
val d = deserialize(lis) ?: return null
|
val uuidBytes = dictionary.getByteArray(PARAM_UUID) ?: return null
|
||||||
|
val uuid = bytes16ToUuid(uuidBytes)
|
||||||
|
|
||||||
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
|
val kdfParameters = KdfParameters(uuid)
|
||||||
|
kdfParameters.copyTo(dictionary)
|
||||||
val kdfP = KdfParameters(uuid)
|
return kdfParameters
|
||||||
kdfP.copyTo(d)
|
|
||||||
return kdfP
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun serialize(kdf: KdfParameters): ByteArray {
|
fun serialize(kdfParameters: KdfParameters): ByteArray {
|
||||||
val bos = ByteArrayOutputStream()
|
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||||
val los = LittleEndianDataOutputStream(bos)
|
val outputStream = LittleEndianDataOutputStream(byteArrayOutputStream)
|
||||||
|
|
||||||
serialize(kdf, los)
|
serialize(kdfParameters, outputStream)
|
||||||
|
|
||||||
return bos.toByteArray()
|
return byteArrayOutputStream.toByteArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ import androidx.fragment.app.FragmentActivity
|
|||||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.database.element.*
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.element.node.Node
|
import com.kunzisoft.keepass.database.element.node.Node
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||||
@@ -54,6 +55,8 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
|
|||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||||
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK
|
||||||
@@ -61,12 +64,10 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
|
|||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK
|
|
||||||
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
|
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
|
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.retrieveProgressDialog
|
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||||
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
||||||
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
||||||
@@ -85,26 +86,25 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
|
|
||||||
private var serviceConnection: ServiceConnection? = null
|
private var serviceConnection: ServiceConnection? = null
|
||||||
|
|
||||||
|
private var progressTaskDialogFragment: ProgressTaskDialogFragment? = null
|
||||||
|
|
||||||
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
private val actionTaskListener = object: DatabaseTaskNotificationService.ActionTaskListener {
|
||||||
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
override fun onStartAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||||
TimeoutHelper.temporarilyDisableTimeout()
|
TimeoutHelper.temporarilyDisableTimeout()
|
||||||
// Stop the opening notification
|
// Stop the opening notification
|
||||||
DatabaseOpenNotificationService.stop(activity)
|
DatabaseOpenNotificationService.stop(activity)
|
||||||
startOrUpdateDialog(titleId, messageId, warningId)
|
startOrUpdateDialog(titleId, messageId, warningId)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
override fun onUpdateAction(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||||
TimeoutHelper.temporarilyDisableTimeout()
|
|
||||||
// Stop the opening notification
|
|
||||||
DatabaseOpenNotificationService.stop(activity)
|
|
||||||
startOrUpdateDialog(titleId, messageId, warningId)
|
startOrUpdateDialog(titleId, messageId, warningId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
|
override fun onStopAction(actionTask: String, result: ActionRunnable.Result) {
|
||||||
onActionFinish?.invoke(actionTask, result)
|
onActionFinish?.invoke(actionTask, result)
|
||||||
|
|
||||||
// Remove the progress task
|
// Remove the progress task
|
||||||
ProgressTaskDialogFragment.stop(activity)
|
stopDialog()
|
||||||
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
TimeoutHelper.releaseTemporarilyDisableTimeout()
|
||||||
|
|
||||||
val inTime = if (activity is LockingActivity) {
|
val inTime = if (activity is LockingActivity) {
|
||||||
@@ -121,12 +121,15 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
|
private fun startOrUpdateDialog(titleId: Int?, messageId: Int?, warningId: Int?) {
|
||||||
var progressTaskDialogFragment = retrieveProgressDialog(activity)
|
|
||||||
if (progressTaskDialogFragment == null) {
|
if (progressTaskDialogFragment == null) {
|
||||||
progressTaskDialogFragment = ProgressTaskDialogFragment.build()
|
progressTaskDialogFragment = activity.supportFragmentManager
|
||||||
ProgressTaskDialogFragment.start(activity, progressTaskDialogFragment)
|
.findFragmentByTag(PROGRESS_TASK_DIALOG_TAG) as ProgressTaskDialogFragment?
|
||||||
}
|
}
|
||||||
progressTaskDialogFragment.apply {
|
if (progressTaskDialogFragment == null) {
|
||||||
|
progressTaskDialogFragment = ProgressTaskDialogFragment()
|
||||||
|
progressTaskDialogFragment?.show(activity.supportFragmentManager, PROGRESS_TASK_DIALOG_TAG)
|
||||||
|
}
|
||||||
|
progressTaskDialogFragment?.apply {
|
||||||
titleId?.let {
|
titleId?.let {
|
||||||
updateTitle(it)
|
updateTitle(it)
|
||||||
}
|
}
|
||||||
@@ -139,7 +142,11 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
private fun stopDialog() {
|
||||||
|
progressTaskDialogFragment?.dismissAllowingStateLoss()
|
||||||
|
progressTaskDialogFragment = null
|
||||||
|
}
|
||||||
|
|
||||||
private fun initServiceConnection() {
|
private fun initServiceConnection() {
|
||||||
if (serviceConnection == null) {
|
if (serviceConnection == null) {
|
||||||
serviceConnection = object : ServiceConnection {
|
serviceConnection = object : ServiceConnection {
|
||||||
@@ -158,7 +165,6 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun bindService() {
|
private fun bindService() {
|
||||||
initServiceConnection()
|
initServiceConnection()
|
||||||
serviceConnection?.let {
|
serviceConnection?.let {
|
||||||
@@ -169,7 +175,6 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
/**
|
/**
|
||||||
* Unbind the service and assign null to the service connection to check if already unbind or not
|
* Unbind the service and assign null to the service connection to check if already unbind or not
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
|
||||||
private fun unBindService() {
|
private fun unBindService() {
|
||||||
serviceConnection?.let {
|
serviceConnection?.let {
|
||||||
activity.unbindService(it)
|
activity.unbindService(it)
|
||||||
@@ -177,22 +182,19 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
serviceConnection = null
|
serviceConnection = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun registerProgressTask() {
|
fun registerProgressTask() {
|
||||||
ProgressTaskDialogFragment.stop(activity)
|
stopDialog()
|
||||||
|
|
||||||
// Register a database task receiver to stop loading dialog when service finish the task
|
// Register a database task receiver to stop loading dialog when service finish the task
|
||||||
databaseTaskBroadcastReceiver = object : BroadcastReceiver() {
|
databaseTaskBroadcastReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
activity.runOnUiThread {
|
when (intent?.action) {
|
||||||
when (intent?.action) {
|
DATABASE_START_TASK_ACTION -> {
|
||||||
DATABASE_START_TASK_ACTION -> {
|
// Bind to the service when is starting
|
||||||
// Bind to the service when is starting
|
bindService()
|
||||||
bindService()
|
}
|
||||||
}
|
DATABASE_STOP_TASK_ACTION -> {
|
||||||
DATABASE_STOP_TASK_ACTION -> {
|
unBindService()
|
||||||
unBindService()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -208,9 +210,8 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
bindService()
|
bindService()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun unregisterProgressTask() {
|
fun unregisterProgressTask() {
|
||||||
ProgressTaskDialogFragment.stop(activity)
|
stopDialog()
|
||||||
|
|
||||||
mBinder?.removeActionTaskListener(actionTaskListener)
|
mBinder?.removeActionTaskListener(actionTaskListener)
|
||||||
mBinder = null
|
mBinder = null
|
||||||
@@ -224,18 +225,15 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun start(bundle: Bundle? = null, actionTask: String) {
|
private fun start(bundle: Bundle? = null, actionTask: String) {
|
||||||
activity.stopService(intentDatabaseTask)
|
activity.stopService(intentDatabaseTask)
|
||||||
if (bundle != null)
|
if (bundle != null)
|
||||||
intentDatabaseTask.putExtras(bundle)
|
intentDatabaseTask.putExtras(bundle)
|
||||||
activity.runOnUiThread {
|
|
||||||
intentDatabaseTask.action = actionTask
|
intentDatabaseTask.action = actionTask
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
activity.startForegroundService(intentDatabaseTask)
|
activity.startForegroundService(intentDatabaseTask)
|
||||||
} else {
|
} else {
|
||||||
activity.startService(intentDatabaseTask)
|
activity.startService(intentDatabaseTask)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,12 +550,12 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
|
|||||||
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
|
, ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startDatabaseSaveParallelism(oldParallelism: Int,
|
fun startDatabaseSaveParallelism(oldParallelism: Long,
|
||||||
newParallelism: Int,
|
newParallelism: Long,
|
||||||
save: Boolean) {
|
save: Boolean) {
|
||||||
start(Bundle().apply {
|
start(Bundle().apply {
|
||||||
putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
|
putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism)
|
||||||
putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
|
putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism)
|
||||||
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
|
||||||
}
|
}
|
||||||
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK)
|
, ACTION_DATABASE_UPDATE_PARALLELISM_TASK)
|
||||||
|
|||||||
@@ -20,15 +20,11 @@
|
|||||||
package com.kunzisoft.keepass.database.element
|
package com.kunzisoft.keepass.database.element
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.Cursor
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||||
import com.kunzisoft.keepass.database.cursor.EntryCursorKDB
|
|
||||||
import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX
|
|
||||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||||
@@ -48,8 +44,10 @@ import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
|
|||||||
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
|
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
|
||||||
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
|
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
|
||||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||||
|
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||||
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
import com.kunzisoft.keepass.icons.IconDrawableFactory
|
||||||
import com.kunzisoft.keepass.stream.readBytes4ToInt
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
import com.kunzisoft.keepass.stream.readBytes4ToUInt
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
import com.kunzisoft.keepass.utils.SingletonHolder
|
import com.kunzisoft.keepass.utils.SingletonHolder
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
import com.kunzisoft.keepass.utils.UriUtil
|
||||||
@@ -137,6 +135,9 @@ class Database {
|
|||||||
val version: String
|
val version: String
|
||||||
get() = mDatabaseKDB?.version ?: mDatabaseKDBX?.version ?: "-"
|
get() = mDatabaseKDB?.version ?: mDatabaseKDBX?.version ?: "-"
|
||||||
|
|
||||||
|
val type: Class<*>?
|
||||||
|
get() = mDatabaseKDB?.javaClass ?: mDatabaseKDBX?.javaClass
|
||||||
|
|
||||||
val allowDataCompression: Boolean
|
val allowDataCompression: Boolean
|
||||||
get() = mDatabaseKDBX != null
|
get() = mDatabaseKDBX != null
|
||||||
|
|
||||||
@@ -205,7 +206,6 @@ class Database {
|
|||||||
|
|
||||||
var numberKeyEncryptionRounds: Long
|
var numberKeyEncryptionRounds: Long
|
||||||
get() = mDatabaseKDB?.numberKeyEncryptionRounds ?: mDatabaseKDBX?.numberKeyEncryptionRounds ?: 0
|
get() = mDatabaseKDB?.numberKeyEncryptionRounds ?: mDatabaseKDBX?.numberKeyEncryptionRounds ?: 0
|
||||||
@Throws(NumberFormatException::class)
|
|
||||||
set(numberRounds) {
|
set(numberRounds) {
|
||||||
mDatabaseKDB?.numberKeyEncryptionRounds = numberRounds
|
mDatabaseKDB?.numberKeyEncryptionRounds = numberRounds
|
||||||
mDatabaseKDBX?.numberKeyEncryptionRounds = numberRounds
|
mDatabaseKDBX?.numberKeyEncryptionRounds = numberRounds
|
||||||
@@ -213,13 +213,13 @@ class Database {
|
|||||||
|
|
||||||
var memoryUsage: Long
|
var memoryUsage: Long
|
||||||
get() {
|
get() {
|
||||||
return mDatabaseKDBX?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE.toLong()
|
return mDatabaseKDBX?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE
|
||||||
}
|
}
|
||||||
set(memory) {
|
set(memory) {
|
||||||
mDatabaseKDBX?.memoryUsage = memory
|
mDatabaseKDBX?.memoryUsage = memory
|
||||||
}
|
}
|
||||||
|
|
||||||
var parallelism: Int
|
var parallelism: Long
|
||||||
get() = mDatabaseKDBX?.parallelism ?: KdfEngine.UNKNOWN_VALUE
|
get() = mDatabaseKDBX?.parallelism ?: KdfEngine.UNKNOWN_VALUE
|
||||||
set(parallelism) {
|
set(parallelism) {
|
||||||
mDatabaseKDBX?.parallelism = parallelism
|
mDatabaseKDBX?.parallelism = parallelism
|
||||||
@@ -338,7 +338,10 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load Data, pass Uris as InputStreams
|
// Load Data, pass Uris as InputStreams
|
||||||
databaseInputStream = BufferedInputStream(UriUtil.getUriInputStream(contentResolver, uri))
|
val databaseStream = UriUtil.getUriInputStream(contentResolver, uri)
|
||||||
|
?: throw IOException("Database input stream cannot be retrieve")
|
||||||
|
|
||||||
|
databaseInputStream = BufferedInputStream(databaseStream)
|
||||||
if (!databaseInputStream.markSupported()) {
|
if (!databaseInputStream.markSupported()) {
|
||||||
throw IOException("Input stream does not support mark.")
|
throw IOException("Input stream does not support mark.")
|
||||||
}
|
}
|
||||||
@@ -347,8 +350,8 @@ class Database {
|
|||||||
databaseInputStream.mark(10)
|
databaseInputStream.mark(10)
|
||||||
|
|
||||||
// Get the file directory to save the attachments
|
// Get the file directory to save the attachments
|
||||||
val sig1 = databaseInputStream.readBytes4ToInt()
|
val sig1 = databaseInputStream.readBytes4ToUInt()
|
||||||
val sig2 = databaseInputStream.readBytes4ToInt()
|
val sig2 = databaseInputStream.readBytes4ToUInt()
|
||||||
|
|
||||||
// Return to the start
|
// Return to the start
|
||||||
databaseInputStream.reset()
|
databaseInputStream.reset()
|
||||||
@@ -397,54 +400,29 @@ class Database {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmOverloads
|
fun createVirtualGroupFromSearch(searchQuery: String,
|
||||||
fun search(str: String, max: Int = Integer.MAX_VALUE): Group? {
|
max: Int = Integer.MAX_VALUE): Group? {
|
||||||
return mSearchHelper?.search(this, str, max)
|
return mSearchHelper?.createVirtualGroupWithSearchResult(this, searchQuery, SearchParameters(), max)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchEntries(context: Context, query: String): Cursor? {
|
fun createVirtualGroupFromSearch(searchInfo: SearchInfo,
|
||||||
|
max: Int = Integer.MAX_VALUE): Group? {
|
||||||
var cursorKDB: EntryCursorKDB? = null
|
val query = (if (searchInfo.webDomain != null)
|
||||||
var cursorKDBX: EntryCursorKDBX? = null
|
searchInfo.webDomain
|
||||||
|
else
|
||||||
if (mDatabaseKDB != null)
|
searchInfo.applicationId)
|
||||||
cursorKDB = EntryCursorKDB()
|
?: return null
|
||||||
if (mDatabaseKDBX != null)
|
return mSearchHelper?.createVirtualGroupWithSearchResult(this, query, SearchParameters().apply {
|
||||||
cursorKDBX = EntryCursorKDBX()
|
searchInTitles = false
|
||||||
|
searchInUserNames = false
|
||||||
val searchResult = search(query, SearchHelper.MAX_SEARCH_ENTRY)
|
searchInPasswords = false
|
||||||
if (searchResult != null) {
|
searchInUrls = true
|
||||||
// Search in hide entries but not meta-stream
|
searchInNotes = true
|
||||||
for (entry in searchResult.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) {
|
searchInOther = true
|
||||||
entry.entryKDB?.let {
|
searchInUUIDs = false
|
||||||
cursorKDB?.addEntry(it)
|
searchInTags = false
|
||||||
}
|
ignoreCase = true
|
||||||
entry.entryKDBX?.let {
|
}, max)
|
||||||
cursorKDBX?.addEntry(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursorKDB ?: cursorKDBX
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getEntryFrom(cursor: Cursor): Entry? {
|
|
||||||
val iconFactory = mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory()
|
|
||||||
|
|
||||||
return createEntry()?.apply {
|
|
||||||
startManageEntry(this)
|
|
||||||
mDatabaseKDB?.let {
|
|
||||||
entryKDB?.let { entryKDB ->
|
|
||||||
(cursor as EntryCursorKDB).populateEntry(entryKDB, iconFactory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mDatabaseKDBX?.let {
|
|
||||||
entryKDBX?.let { entryKDBX ->
|
|
||||||
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, iconFactory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stopManageEntry(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(DatabaseOutputException::class)
|
@Throws(DatabaseOutputException::class)
|
||||||
|
|||||||
@@ -398,6 +398,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
|||||||
database?.startManageEntry(this)
|
database?.startManageEntry(this)
|
||||||
entryInfo.id = nodeId.toString()
|
entryInfo.id = nodeId.toString()
|
||||||
entryInfo.title = title
|
entryInfo.title = title
|
||||||
|
entryInfo.icon = icon
|
||||||
entryInfo.username = username
|
entryInfo.username = username
|
||||||
entryInfo.password = password
|
entryInfo.password = password
|
||||||
entryInfo.url = url
|
entryInfo.url = url
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
|
|||||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||||
import com.kunzisoft.keepass.database.element.node.*
|
import com.kunzisoft.keepass.database.element.node.*
|
||||||
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
@@ -251,6 +252,14 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
|||||||
ArrayList()
|
ArrayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getChildEntriesInfo(database: Database): List<EntryInfo> {
|
||||||
|
val entriesInfo = ArrayList<EntryInfo>()
|
||||||
|
getChildEntries().forEach { entry ->
|
||||||
|
entriesInfo.add(entry.getEntryInfo(database))
|
||||||
|
}
|
||||||
|
return entriesInfo
|
||||||
|
}
|
||||||
|
|
||||||
fun getFilteredChildEntries(vararg filter: ChildFilter): List<Entry> {
|
fun getFilteredChildEntries(vararg filter: ChildFilter): List<Entry> {
|
||||||
val withoutMetaStream = filter.contains(ChildFilter.META_STREAM)
|
val withoutMetaStream = filter.contains(ChildFilter.META_STREAM)
|
||||||
val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED)
|
val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED)
|
||||||
|
|||||||
@@ -19,14 +19,13 @@
|
|||||||
|
|
||||||
package com.kunzisoft.keepass.database.element.database
|
package com.kunzisoft.keepass.database.element.database
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
|
import com.kunzisoft.keepass.crypto.finalkey.AESKeyTransformerFactory
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
import com.kunzisoft.keepass.database.element.node.NodeIdInt
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
|
||||||
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
|
||||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -39,8 +38,6 @@ import kotlin.collections.ArrayList
|
|||||||
|
|
||||||
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||||
|
|
||||||
private var numKeyEncRounds: Int = 0
|
|
||||||
|
|
||||||
var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
|
var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
|
||||||
|
|
||||||
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
|
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
|
||||||
@@ -88,19 +85,10 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
override val passwordEncoding: String
|
override val passwordEncoding: String
|
||||||
get() = "ISO-8859-1"
|
get() = "ISO-8859-1"
|
||||||
|
|
||||||
override var numberKeyEncryptionRounds: Long
|
override var numberKeyEncryptionRounds = 300L
|
||||||
get() = numKeyEncRounds.toLong()
|
|
||||||
@Throws(NumberFormatException::class)
|
|
||||||
set(rounds) {
|
|
||||||
if (rounds > Integer.MAX_VALUE || rounds < Integer.MIN_VALUE) {
|
|
||||||
throw NumberFormatException()
|
|
||||||
}
|
|
||||||
numKeyEncRounds = rounds.toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
algorithm = EncryptionAlgorithm.AESRijndael
|
algorithm = EncryptionAlgorithm.AESRijndael
|
||||||
numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -159,9 +147,9 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
val nos = NullOutputStream()
|
val nos = NullOutputStream()
|
||||||
val dos = DigestOutputStream(nos, messageDigest)
|
val dos = DigestOutputStream(nos, messageDigest)
|
||||||
|
|
||||||
val transformedMasterKey = transformMasterKey(masterSeed2, masterKey, numRounds)
|
// Encrypt the master key a few times to make brute-force key-search harder
|
||||||
dos.write(masterSeed)
|
dos.write(masterSeed)
|
||||||
dos.write(transformedMasterKey)
|
dos.write(AESKeyTransformerFactory.transformMasterKey(masterSeed2, masterKey, numRounds) ?: ByteArray(0))
|
||||||
|
|
||||||
finalKey = messageDigest.digest()
|
finalKey = messageDigest.digest()
|
||||||
}
|
}
|
||||||
@@ -262,23 +250,11 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val TYPE = DatabaseKDB::class.java
|
||||||
|
|
||||||
const val BACKUP_FOLDER_TITLE = "Backup"
|
const val BACKUP_FOLDER_TITLE = "Backup"
|
||||||
private const val BACKUP_FOLDER_UNDEFINED_ID = -1
|
private const val BACKUP_FOLDER_UNDEFINED_ID = -1
|
||||||
|
|
||||||
private const val DEFAULT_ENCRYPTION_ROUNDS = 300
|
|
||||||
|
|
||||||
const val BUFFER_SIZE_BYTES = 3 * 128
|
const val BUFFER_SIZE_BYTES = 3 * 128
|
||||||
|
|
||||||
/**
|
|
||||||
* Encrypt the master key a few times to make brute-force key-search harder
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
@Throws(IOException::class)
|
|
||||||
private fun transformMasterKey(pKeySeed: ByteArray, pKey: ByteArray, rounds: Long): ByteArray {
|
|
||||||
val key = FinalKeyFactory.createFinalKey()
|
|
||||||
|
|
||||||
return key.transformMasterKey(pKeySeed, pKey, rounds)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import com.kunzisoft.keepass.crypto.engine.CipherEngine
|
|||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
||||||
import com.kunzisoft.keepass.database.element.*
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
|
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||||
@@ -41,6 +42,7 @@ import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
|||||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
import org.w3c.dom.Text
|
import org.w3c.dom.Text
|
||||||
@@ -66,7 +68,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
private var numKeyEncRounds: Long = 0
|
private var numKeyEncRounds: Long = 0
|
||||||
var publicCustomData = VariantDictionary()
|
var publicCustomData = VariantDictionary()
|
||||||
|
|
||||||
var kdbxVersion: Long = 0
|
var kdbxVersion = UnsignedInt(0)
|
||||||
var name = ""
|
var name = ""
|
||||||
var nameChanged = DateInstant()
|
var nameChanged = DateInstant()
|
||||||
// TODO change setting date
|
// TODO change setting date
|
||||||
@@ -76,13 +78,13 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
var defaultUserName = ""
|
var defaultUserName = ""
|
||||||
var defaultUserNameChanged = DateInstant()
|
var defaultUserNameChanged = DateInstant()
|
||||||
|
|
||||||
// TODO date
|
// TODO last change date
|
||||||
var keyLastChanged = DateInstant()
|
var keyLastChanged = DateInstant()
|
||||||
var keyChangeRecDays: Long = -1
|
var keyChangeRecDays: Long = -1
|
||||||
var keyChangeForceDays: Long = 1
|
var keyChangeForceDays: Long = 1
|
||||||
var isKeyChangeForceOnce = false
|
var isKeyChangeForceOnce = false
|
||||||
|
|
||||||
var maintenanceHistoryDays: Long = 365
|
var maintenanceHistoryDays = UnsignedInt(365)
|
||||||
var color = ""
|
var color = ""
|
||||||
/**
|
/**
|
||||||
* Determine if RecycleBin is enable or not
|
* Determine if RecycleBin is enable or not
|
||||||
@@ -218,7 +220,6 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!)
|
numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!)
|
||||||
return numKeyEncRounds
|
return numKeyEncRounds
|
||||||
}
|
}
|
||||||
@Throws(NumberFormatException::class)
|
|
||||||
set(rounds) {
|
set(rounds) {
|
||||||
val kdfEngine = kdfEngine
|
val kdfEngine = kdfEngine
|
||||||
if (kdfEngine != null && kdfParameters != null)
|
if (kdfEngine != null && kdfParameters != null)
|
||||||
@@ -231,7 +232,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
val kdfEngine = kdfEngine
|
val kdfEngine = kdfEngine
|
||||||
return if (kdfEngine != null && kdfParameters != null) {
|
return if (kdfEngine != null && kdfParameters != null) {
|
||||||
kdfEngine.getMemoryUsage(kdfParameters!!)
|
kdfEngine.getMemoryUsage(kdfParameters!!)
|
||||||
} else KdfEngine.UNKNOWN_VALUE.toLong()
|
} else KdfEngine.UNKNOWN_VALUE
|
||||||
}
|
}
|
||||||
set(memory) {
|
set(memory) {
|
||||||
val kdfEngine = kdfEngine
|
val kdfEngine = kdfEngine
|
||||||
@@ -239,7 +240,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
kdfEngine.setMemoryUsage(kdfParameters!!, memory)
|
kdfEngine.setMemoryUsage(kdfParameters!!, memory)
|
||||||
}
|
}
|
||||||
|
|
||||||
var parallelism: Int
|
var parallelism: Long
|
||||||
get() {
|
get() {
|
||||||
val kdfEngine = kdfEngine
|
val kdfEngine = kdfEngine
|
||||||
return if (kdfEngine != null && kdfParameters != null) {
|
return if (kdfEngine != null && kdfParameters != null) {
|
||||||
@@ -551,6 +552,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
val TYPE = DatabaseKDBX::class.java
|
||||||
private val TAG = DatabaseKDBX::class.java.name
|
private val TAG = DatabaseKDBX::class.java.name
|
||||||
|
|
||||||
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
|
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
|
||||||
|
|||||||
@@ -104,10 +104,7 @@ abstract class DatabaseVersioned<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
protected fun getPasswordKey(key: String?): ByteArray {
|
protected fun getPasswordKey(key: String): ByteArray {
|
||||||
if (key == null)
|
|
||||||
throw IllegalArgumentException("Key cannot be empty.") // TODO
|
|
||||||
|
|
||||||
val messageDigest: MessageDigest
|
val messageDigest: MessageDigest
|
||||||
try {
|
try {
|
||||||
messageDigest = MessageDigest.getInstance("SHA-256")
|
messageDigest = MessageDigest.getInstance("SHA-256")
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.os.Parcel
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
|
||||||
import com.kunzisoft.keepass.utils.ParcelableUtil
|
import com.kunzisoft.keepass.utils.ParcelableUtil
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ class AutoType : Parcelable {
|
|||||||
|
|
||||||
constructor(parcel: Parcel) {
|
constructor(parcel: Parcel) {
|
||||||
this.enabled = parcel.readByte().toInt() != 0
|
this.enabled = parcel.readByte().toInt() != 0
|
||||||
this.obfuscationOptions = parcel.readLong()
|
this.obfuscationOptions = UnsignedInt(parcel.readInt())
|
||||||
this.defaultSequence = parcel.readString() ?: defaultSequence
|
this.defaultSequence = parcel.readString() ?: defaultSequence
|
||||||
this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel)
|
this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel)
|
||||||
}
|
}
|
||||||
@@ -57,7 +58,7 @@ class AutoType : Parcelable {
|
|||||||
|
|
||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
dest.writeByte((if (enabled) 1 else 0).toByte())
|
dest.writeByte((if (enabled) 1 else 0).toByte())
|
||||||
dest.writeLong(obfuscationOptions)
|
dest.writeInt(obfuscationOptions.toInt())
|
||||||
dest.writeString(defaultSequence)
|
dest.writeString(defaultSequence)
|
||||||
ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs)
|
ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs)
|
||||||
}
|
}
|
||||||
@@ -71,7 +72,7 @@ class AutoType : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val OBF_OPT_NONE: Long = 0
|
private val OBF_OPT_NONE = UnsignedInt(0)
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val CREATOR: Parcelable.Creator<AutoType> = object : Parcelable.Creator<AutoType> {
|
val CREATOR: Parcelable.Creator<AutoType> = object : Parcelable.Creator<AutoType> {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import com.kunzisoft.keepass.database.element.node.Type
|
|||||||
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.utils.ParcelableUtil
|
import com.kunzisoft.keepass.utils.ParcelableUtil
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
||||||
@@ -104,7 +105,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
|
|
||||||
constructor(parcel: Parcel) : super(parcel) {
|
constructor(parcel: Parcel) : super(parcel) {
|
||||||
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
|
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
|
||||||
usageCount = parcel.readLong()
|
usageCount = UnsignedLong(parcel.readLong())
|
||||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||||
customData = ParcelableUtil.readStringParcelableMap(parcel)
|
customData = ParcelableUtil.readStringParcelableMap(parcel)
|
||||||
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
|
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
|
||||||
@@ -122,7 +123,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
super.writeToParcel(dest, flags)
|
super.writeToParcel(dest, flags)
|
||||||
dest.writeParcelable(iconCustom, flags)
|
dest.writeParcelable(iconCustom, flags)
|
||||||
dest.writeLong(usageCount)
|
dest.writeLong(usageCount.toLong())
|
||||||
dest.writeParcelable(locationChanged, flags)
|
dest.writeParcelable(locationChanged, flags)
|
||||||
ParcelableUtil.writeStringParcelableMap(dest, customData)
|
ParcelableUtil.writeStringParcelableMap(dest, customData)
|
||||||
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
|
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
|
||||||
@@ -243,7 +244,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
fields[STR_NOTES] = ProtectedString(protect, value)
|
fields[STR_NOTES] = ProtectedString(protect, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override var usageCount: Long = 0
|
override var usageCount = UnsignedLong(0)
|
||||||
|
|
||||||
override var locationChanged = DateInstant()
|
override var locationChanged = DateInstant()
|
||||||
|
|
||||||
@@ -332,7 +333,8 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
|
|
||||||
override fun touch(modified: Boolean, touchParents: Boolean) {
|
override fun touch(modified: Boolean, touchParents: Boolean) {
|
||||||
super.touch(modified, touchParents)
|
super.touch(modified, touchParents)
|
||||||
++usageCount
|
// TODO unsigned long
|
||||||
|
usageCount = UnsignedLong(usageCount.toLong() + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ package com.kunzisoft.keepass.database.element.entry
|
|||||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||||
import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler
|
import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler
|
||||||
import com.kunzisoft.keepass.database.search.SearchParametersKDBX
|
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||||
import com.kunzisoft.keepass.utils.StringUtil
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class FieldReferencesEngine {
|
class FieldReferencesEngine {
|
||||||
@@ -76,11 +75,11 @@ class FieldReferencesEngine {
|
|||||||
for (i in 0..19) {
|
for (i in 0..19) {
|
||||||
text = fillRefsUsingCache(text, contextV4)
|
text = fillRefsUsingCache(text, contextV4)
|
||||||
|
|
||||||
val start = StringUtil.indexOfIgnoreCase(text, STR_REF_START, offset, Locale.ENGLISH)
|
val start = text.indexOf(STR_REF_START, offset, true)
|
||||||
if (start < 0) {
|
if (start < 0) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
val end = StringUtil.indexOfIgnoreCase(text, STR_REF_END, start + 1, Locale.ENGLISH)
|
val end = text.indexOf(STR_REF_END, start + 1, true)
|
||||||
if (end <= start) {
|
if (end <= start) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -142,24 +141,23 @@ class FieldReferencesEngine {
|
|||||||
val scan = Character.toUpperCase(ref[2])
|
val scan = Character.toUpperCase(ref[2])
|
||||||
val wanted = Character.toUpperCase(ref[0])
|
val wanted = Character.toUpperCase(ref[0])
|
||||||
|
|
||||||
val searchParametersV4 = SearchParametersKDBX()
|
val searchParameters = SearchParameters()
|
||||||
searchParametersV4.setupNone()
|
searchParameters.setupNone()
|
||||||
|
|
||||||
searchParametersV4.searchString = ref.substring(4)
|
searchParameters.searchString = ref.substring(4)
|
||||||
when (scan) {
|
when (scan) {
|
||||||
'T' -> searchParametersV4.searchInTitles = true
|
'T' -> searchParameters.searchInTitles = true
|
||||||
'U' -> searchParametersV4.searchInUserNames = true
|
'U' -> searchParameters.searchInUserNames = true
|
||||||
'A' -> searchParametersV4.searchInUrls = true
|
'A' -> searchParameters.searchInUrls = true
|
||||||
'P' -> searchParametersV4.searchInPasswords = true
|
'P' -> searchParameters.searchInPasswords = true
|
||||||
'N' -> searchParametersV4.searchInNotes = true
|
'N' -> searchParameters.searchInNotes = true
|
||||||
'I' -> searchParametersV4.searchInUUIDs = true
|
'I' -> searchParameters.searchInUUIDs = true
|
||||||
'O' -> searchParametersV4.searchInOther = true
|
'O' -> searchParameters.searchInOther = true
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val list = ArrayList<EntryKDBX>()
|
val list = ArrayList<EntryKDBX>()
|
||||||
// TODO type parameter
|
searchEntries(contextV4.databaseV4?.rootGroup, searchParameters, list)
|
||||||
searchEntries(contextV4.databaseV4!!.rootGroup, searchParametersV4, list)
|
|
||||||
|
|
||||||
return if (list.size > 0) {
|
return if (list.size > 0) {
|
||||||
TargetResult(list[0], wanted)
|
TargetResult(list[0], wanted)
|
||||||
@@ -186,22 +184,22 @@ class FieldReferencesEngine {
|
|||||||
private fun fillRefsUsingCache(text: String, sprContextV4: SprContextV4): String {
|
private fun fillRefsUsingCache(text: String, sprContextV4: SprContextV4): String {
|
||||||
var newText = text
|
var newText = text
|
||||||
for ((key, value) in sprContextV4.refsCache) {
|
for ((key, value) in sprContextV4.refsCache) {
|
||||||
newText = StringUtil.replaceAllIgnoresCase(text, key, value, Locale.ENGLISH)
|
newText = text.replace(key, value, true)
|
||||||
}
|
}
|
||||||
return newText
|
return newText
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchEntries(root: GroupKDBX?, searchParametersV4: SearchParametersKDBX?, listStorage: MutableList<EntryKDBX>?) {
|
private fun searchEntries(root: GroupKDBX?, searchParameters: SearchParameters?, listStorage: MutableList<EntryKDBX>?) {
|
||||||
if (searchParametersV4 == null) {
|
if (searchParameters == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (listStorage == null) {
|
if (listStorage == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val terms = StringUtil.splitStringTerms(searchParametersV4.searchString)
|
val terms = splitStringTerms(searchParameters.searchString)
|
||||||
if (terms.size <= 1 || searchParametersV4.regularExpression) {
|
if (terms.size <= 1 || searchParameters.regularExpression) {
|
||||||
root!!.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, listStorage), null)
|
root!!.doForEachChild(EntryKDBXSearchHandler(searchParameters, listStorage), null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,41 +207,76 @@ class FieldReferencesEngine {
|
|||||||
val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length }
|
val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length }
|
||||||
Collections.sort(terms, stringLengthComparator)
|
Collections.sort(terms, stringLengthComparator)
|
||||||
|
|
||||||
val fullSearch = searchParametersV4.searchString
|
val fullSearch = searchParameters.searchString
|
||||||
var childEntries: List<EntryKDBX>? = root!!.getChildEntries()
|
var childEntries: List<EntryKDBX>? = root!!.getChildEntries()
|
||||||
for (i in terms.indices) {
|
for (i in terms.indices) {
|
||||||
val pgNew = ArrayList<EntryKDBX>()
|
val pgNew = ArrayList<EntryKDBX>()
|
||||||
|
|
||||||
searchParametersV4.searchString = terms[i]
|
searchParameters.searchString = terms[i]
|
||||||
|
|
||||||
var negate = false
|
var negate = false
|
||||||
if (searchParametersV4.searchString.startsWith("-")) {
|
if (searchParameters.searchString.startsWith("-")) {
|
||||||
searchParametersV4.searchString = searchParametersV4.searchString.substring(1)
|
searchParameters.searchString = searchParameters.searchString.substring(1)
|
||||||
negate = searchParametersV4.searchString.isNotEmpty()
|
negate = searchParameters.searchString.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, pgNew), null)) {
|
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParameters, pgNew), null)) {
|
||||||
childEntries = null
|
childEntries = null
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
val complement = ArrayList<EntryKDBX>()
|
childEntries = if (negate) {
|
||||||
if (negate) {
|
val complement = ArrayList<EntryKDBX>()
|
||||||
for (entry in childEntries!!) {
|
for (entry in childEntries!!) {
|
||||||
if (!pgNew.contains(entry)) {
|
if (!pgNew.contains(entry)) {
|
||||||
complement.add(entry)
|
complement.add(entry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
childEntries = complement
|
complement
|
||||||
} else {
|
} else {
|
||||||
childEntries = pgNew
|
pgNew
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (childEntries != null) {
|
if (childEntries != null) {
|
||||||
listStorage.addAll(childEntries)
|
listStorage.addAll(childEntries)
|
||||||
}
|
}
|
||||||
searchParametersV4.searchString = fullSearch
|
searchParameters.searchString = fullSearch
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a list of String by split text when ' ', '\t', '\r' or '\n' is found
|
||||||
|
*/
|
||||||
|
private fun splitStringTerms(text: String?): List<String> {
|
||||||
|
val list = ArrayList<String>()
|
||||||
|
if (text == null) {
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
var quoted = false
|
||||||
|
|
||||||
|
for (element in text) {
|
||||||
|
|
||||||
|
if ((element == ' ' || element == '\t' || element == '\r' || element == '\n') && !quoted) {
|
||||||
|
|
||||||
|
val len = stringBuilder.length
|
||||||
|
when {
|
||||||
|
len > 0 -> {
|
||||||
|
list.add(stringBuilder.toString())
|
||||||
|
stringBuilder.delete(0, len)
|
||||||
|
}
|
||||||
|
element == '\"' -> quoted = !quoted
|
||||||
|
else -> stringBuilder.append(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringBuilder.isNotEmpty()) {
|
||||||
|
list.add(stringBuilder.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ import java.util.*
|
|||||||
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
|
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
|
||||||
|
|
||||||
var level = 0 // short
|
var level = 0 // short
|
||||||
/** Used by KeePass internally, don't use */
|
// Used by KeePass internally, don't use
|
||||||
var flags: Int = 0
|
var groupFlags = 0
|
||||||
|
|
||||||
constructor() : super()
|
constructor() : super()
|
||||||
|
|
||||||
constructor(parcel: Parcel) : super(parcel) {
|
constructor(parcel: Parcel) : super(parcel) {
|
||||||
level = parcel.readInt()
|
level = parcel.readInt()
|
||||||
flags = parcel.readInt()
|
groupFlags = parcel.readInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
|
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
|
||||||
@@ -53,13 +53,13 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
|||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
super.writeToParcel(dest, flags)
|
super.writeToParcel(dest, flags)
|
||||||
dest.writeInt(level)
|
dest.writeInt(level)
|
||||||
dest.writeInt(flags)
|
dest.writeInt(groupFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateWith(source: GroupKDB) {
|
fun updateWith(source: GroupKDB) {
|
||||||
super.updateWith(source)
|
super.updateWith(source)
|
||||||
level = source.level
|
level = source.level
|
||||||
flags = source.flags
|
groupFlags = source.groupFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
override val type: Type
|
override val type: Type
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||||
import com.kunzisoft.keepass.database.element.node.Type
|
import com.kunzisoft.keepass.database.element.node.Type
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
|
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -77,9 +78,9 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
|
|
||||||
constructor(parcel: Parcel) : super(parcel) {
|
constructor(parcel: Parcel) : super(parcel) {
|
||||||
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
|
iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom
|
||||||
usageCount = parcel.readLong()
|
usageCount = UnsignedLong(parcel.readLong())
|
||||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||||
// TODO customData = ParcelableUtil.readStringParcelableMap(in);
|
// TODO customData = ParcelableUtil.readStringParcelableMap(parcel);
|
||||||
notes = parcel.readString() ?: notes
|
notes = parcel.readString() ?: notes
|
||||||
isExpanded = parcel.readByte().toInt() != 0
|
isExpanded = parcel.readByte().toInt() != 0
|
||||||
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
|
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
|
||||||
@@ -101,7 +102,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
super.writeToParcel(dest, flags)
|
super.writeToParcel(dest, flags)
|
||||||
dest.writeParcelable(iconCustom, flags)
|
dest.writeParcelable(iconCustom, flags)
|
||||||
dest.writeLong(usageCount)
|
dest.writeLong(usageCount.toLong())
|
||||||
dest.writeParcelable(locationChanged, flags)
|
dest.writeParcelable(locationChanged, flags)
|
||||||
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
|
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
|
||||||
dest.writeString(notes)
|
dest.writeString(notes)
|
||||||
@@ -130,7 +131,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
|||||||
lastTopVisibleEntry = source.lastTopVisibleEntry
|
lastTopVisibleEntry = source.lastTopVisibleEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
override var usageCount: Long = 0
|
override var usageCount = UnsignedLong(0)
|
||||||
|
|
||||||
override var locationChanged = DateInstant()
|
override var locationChanged = DateInstant()
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class IconImageCustom : IconImage {
|
|||||||
constructor(parcel: Parcel) {
|
constructor(parcel: Parcel) {
|
||||||
uuid = parcel.readSerializable() as UUID
|
uuid = parcel.readSerializable() as UUID
|
||||||
// TODO Take too much memories
|
// TODO Take too much memories
|
||||||
// in.readByteArray(imageData);
|
// parcel.readByteArray(imageData);
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||||
|
|||||||
@@ -20,10 +20,11 @@
|
|||||||
package com.kunzisoft.keepass.database.element.node
|
package com.kunzisoft.keepass.database.element.node
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
|
|
||||||
interface NodeKDBXInterface : NodeTimeInterface {
|
interface NodeKDBXInterface : NodeTimeInterface {
|
||||||
|
|
||||||
var usageCount: Long
|
var usageCount: UnsignedLong
|
||||||
|
|
||||||
var locationChanged: DateInstant
|
var locationChanged: DateInstant
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.file
|
package com.kunzisoft.keepass.database.file
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
|
||||||
abstract class DatabaseHeader {
|
abstract class DatabaseHeader {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +34,7 @@ abstract class DatabaseHeader {
|
|||||||
var encryptionIV = ByteArray(16)
|
var encryptionIV = ByteArray(16)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PWM_DBSIG_1 = -0x655d26fd
|
val PWM_DBSIG_1 = UnsignedInt(-0x655d26fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
package com.kunzisoft.keepass.database.file
|
package com.kunzisoft.keepass.database.file
|
||||||
|
|
||||||
import com.kunzisoft.keepass.stream.bytes4ToInt
|
|
||||||
import com.kunzisoft.keepass.stream.readBytesLength
|
import com.kunzisoft.keepass.stream.readBytesLength
|
||||||
import com.kunzisoft.keepass.stream.readBytes4ToInt
|
import com.kunzisoft.keepass.stream.readBytes4ToUInt
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
@@ -34,15 +34,15 @@ class DatabaseHeaderKDB : DatabaseHeader() {
|
|||||||
*/
|
*/
|
||||||
var transformSeed = ByteArray(32)
|
var transformSeed = ByteArray(32)
|
||||||
|
|
||||||
var signature1: Int = 0 // = PWM_DBSIG_1
|
var signature1 = UnsignedInt(0) // = PWM_DBSIG_1
|
||||||
var signature2: Int = 0 // = DBSIG_2
|
var signature2 = UnsignedInt(0) // = DBSIG_2
|
||||||
var flags: Int = 0
|
var flags= UnsignedInt(0)
|
||||||
var version: Int = 0
|
var version= UnsignedInt(0)
|
||||||
|
|
||||||
/** Number of groups in the database */
|
/** Number of groups in the database */
|
||||||
var numGroups: Int = 0
|
var numGroups = UnsignedInt(0)
|
||||||
/** Number of entries in the database */
|
/** Number of entries in the database */
|
||||||
var numEntries: Int = 0
|
var numEntries = UnsignedInt(0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SHA-256 hash of the database, used for integrity check
|
* SHA-256 hash of the database, used for integrity check
|
||||||
@@ -50,28 +50,24 @@ class DatabaseHeaderKDB : DatabaseHeader() {
|
|||||||
var contentsHash = ByteArray(32)
|
var contentsHash = ByteArray(32)
|
||||||
|
|
||||||
// As UInt
|
// As UInt
|
||||||
var numKeyEncRounds: Int = 0
|
var numKeyEncRounds = UnsignedInt(0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse given buf, as read from file.
|
* Parse given buf, as read from file.
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun loadFromFile(inputStream: InputStream) {
|
fun loadFromFile(inputStream: InputStream) {
|
||||||
signature1 = inputStream.readBytes4ToInt() // 4 bytes
|
signature1 = inputStream.readBytes4ToUInt() // 4 bytes
|
||||||
signature2 = inputStream.readBytes4ToInt() // 4 bytes
|
signature2 = inputStream.readBytes4ToUInt() // 4 bytes
|
||||||
flags = inputStream.readBytes4ToInt() // 4 bytes
|
flags = inputStream.readBytes4ToUInt() // 4 bytes
|
||||||
version = inputStream.readBytes4ToInt() // 4 bytes
|
version = inputStream.readBytes4ToUInt() // 4 bytes
|
||||||
masterSeed = inputStream.readBytesLength(16) // 16 bytes
|
masterSeed = inputStream.readBytesLength(16) // 16 bytes
|
||||||
encryptionIV = inputStream.readBytesLength(16) // 16 bytes
|
encryptionIV = inputStream.readBytesLength(16) // 16 bytes
|
||||||
numGroups = inputStream.readBytes4ToInt() // 4 bytes
|
numGroups = inputStream.readBytes4ToUInt() // 4 bytes
|
||||||
numEntries = inputStream.readBytes4ToInt() // 4 bytes
|
numEntries = inputStream.readBytes4ToUInt() // 4 bytes
|
||||||
contentsHash = inputStream.readBytesLength(32) // 32 bytes
|
contentsHash = inputStream.readBytesLength(32) // 32 bytes
|
||||||
transformSeed = inputStream.readBytesLength(32) // 32 bytes
|
transformSeed = inputStream.readBytesLength(32) // 32 bytes
|
||||||
numKeyEncRounds = inputStream.readBytes4ToInt()
|
numKeyEncRounds = inputStream.readBytes4ToUInt()
|
||||||
if (numKeyEncRounds < 0) {
|
|
||||||
// TODO: Really treat this like an unsigned integer
|
|
||||||
throw IOException("Does not support more than " + Integer.MAX_VALUE + " rounds.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -88,24 +84,24 @@ class DatabaseHeaderKDB : DatabaseHeader() {
|
|||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
// DB sig from KeePass 1.03
|
// DB sig from KeePass 1.03
|
||||||
const val DBSIG_2 = -0x4ab4049b
|
val DBSIG_2 = UnsignedInt(-0x4ab4049b)
|
||||||
// DB sig from KeePass 1.03
|
// DB sig from KeePass 1.03
|
||||||
const val DBVER_DW = 0x00030003
|
val DBVER_DW = UnsignedInt(0x00030003)
|
||||||
|
|
||||||
const val FLAG_SHA2 = 1
|
val FLAG_SHA2 = UnsignedInt(1)
|
||||||
const val FLAG_RIJNDAEL = 2
|
val FLAG_RIJNDAEL = UnsignedInt(2)
|
||||||
const val FLAG_ARCFOUR = 4
|
val FLAG_ARCFOUR = UnsignedInt(4)
|
||||||
const val FLAG_TWOFISH = 8
|
val FLAG_TWOFISH = UnsignedInt(8)
|
||||||
|
|
||||||
/** Size of byte buffer needed to hold this struct. */
|
/** Size of byte buffer needed to hold this struct. */
|
||||||
const val BUF_SIZE = 124
|
const val BUF_SIZE = 124
|
||||||
|
|
||||||
fun matchesHeader(sig1: Int, sig2: Int): Boolean {
|
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
|
||||||
return sig1 == PWM_DBSIG_1 && sig2 == DBSIG_2
|
return sig1.toInt() == PWM_DBSIG_1.toInt() && sig2.toInt() == DBSIG_2.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun compatibleHeaders(one: Int, two: Int): Boolean {
|
fun compatibleHeaders(one: UnsignedInt, two: UnsignedInt): Boolean {
|
||||||
return one and -0x100 == two and -0x100
|
return one.toInt() and -0x100 == two.toInt() and -0x100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -31,6 +31,8 @@ import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
|||||||
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||||
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
|
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
@@ -45,14 +47,16 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
var innerRandomStreamKey: ByteArray = ByteArray(32)
|
var innerRandomStreamKey: ByteArray = ByteArray(32)
|
||||||
var streamStartBytes: ByteArray = ByteArray(32)
|
var streamStartBytes: ByteArray = ByteArray(32)
|
||||||
var innerRandomStream: CrsAlgorithm? = null
|
var innerRandomStream: CrsAlgorithm? = null
|
||||||
var version: Long = 0
|
var version: UnsignedInt = UnsignedInt(0)
|
||||||
|
|
||||||
// version < FILE_VERSION_32_4)
|
// version < FILE_VERSION_32_4)
|
||||||
var transformSeed: ByteArray?
|
var transformSeed: ByteArray?
|
||||||
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
|
get() = databaseV4.kdfParameters?.getByteArray(AesKdf.PARAM_SEED)
|
||||||
private set(seed) {
|
private set(seed) {
|
||||||
assignAesKdfEngineIfNotExists()
|
assignAesKdfEngineIfNotExists()
|
||||||
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, seed)
|
seed?.let {
|
||||||
|
databaseV4.kdfParameters?.setByteArray(AesKdf.PARAM_SEED, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object PwDbHeaderV4Fields {
|
object PwDbHeaderV4Fields {
|
||||||
@@ -103,7 +107,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): Long {
|
private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): UnsignedInt {
|
||||||
// https://keepass.info/help/kb/kdbx_4.html
|
// https://keepass.info/help/kb/kdbx_4.html
|
||||||
|
|
||||||
// Return v4 if AES is not use
|
// Return v4 if AES is not use
|
||||||
@@ -147,8 +151,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
|
val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
|
||||||
val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
|
val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
|
||||||
|
|
||||||
val sig1 = littleEndianDataInputStream.readInt()
|
val sig1 = littleEndianDataInputStream.readUInt()
|
||||||
val sig2 = littleEndianDataInputStream.readInt()
|
val sig2 = littleEndianDataInputStream.readUInt()
|
||||||
|
|
||||||
if (!matchesHeader(sig1, sig2)) {
|
if (!matchesHeader(sig1, sig2)) {
|
||||||
throw VersionDatabaseException()
|
throw VersionDatabaseException()
|
||||||
@@ -172,10 +176,10 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean {
|
private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean {
|
||||||
val fieldID = dis.read().toByte()
|
val fieldID = dis.read().toByte()
|
||||||
|
|
||||||
val fieldSize: Int = if (version < FILE_VERSION_32_4) {
|
val fieldSize: Int = if (version.toLong() < FILE_VERSION_32_4.toLong()) {
|
||||||
dis.readUShort()
|
dis.readUShort()
|
||||||
} else {
|
} else {
|
||||||
dis.readInt()
|
dis.readUInt().toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
var fieldData: ByteArray? = null
|
var fieldData: ByteArray? = null
|
||||||
@@ -198,20 +202,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
|
|
||||||
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
|
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
|
||||||
|
|
||||||
PwDbHeaderV4Fields.TransformSeed -> if (version < FILE_VERSION_32_4)
|
PwDbHeaderV4Fields.TransformSeed -> if (version.toLong() < FILE_VERSION_32_4.toLong())
|
||||||
transformSeed = fieldData
|
transformSeed = fieldData
|
||||||
|
|
||||||
PwDbHeaderV4Fields.TransformRounds -> if (version < FILE_VERSION_32_4)
|
PwDbHeaderV4Fields.TransformRounds -> if (version.toLong() < FILE_VERSION_32_4.toLong())
|
||||||
setTransformRound(fieldData)
|
setTransformRound(fieldData)
|
||||||
|
|
||||||
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
|
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
|
||||||
|
|
||||||
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version < FILE_VERSION_32_4)
|
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.toLong() < FILE_VERSION_32_4.toLong())
|
||||||
innerRandomStreamKey = fieldData
|
innerRandomStreamKey = fieldData
|
||||||
|
|
||||||
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
|
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
|
||||||
|
|
||||||
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version < FILE_VERSION_32_4)
|
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.toLong() < FILE_VERSION_32_4.toLong())
|
||||||
setRandomStreamID(fieldData)
|
setRandomStreamID(fieldData)
|
||||||
|
|
||||||
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
|
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
|
||||||
@@ -256,8 +260,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
throw IOException("Invalid compression flags.")
|
throw IOException("Invalid compression flags.")
|
||||||
}
|
}
|
||||||
|
|
||||||
val flag = bytes4ToInt(pbFlags)
|
val flag = bytes4ToUInt(pbFlags)
|
||||||
if (flag < 0 || flag >= CompressionAlgorithm.values().size) {
|
if (flag.toLong() < 0 || flag.toLong() >= CompressionAlgorithm.values().size) {
|
||||||
throw IOException("Unrecognized compression flag.")
|
throw IOException("Unrecognized compression flag.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,8 +276,8 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
throw IOException("Invalid stream id.")
|
throw IOException("Invalid stream id.")
|
||||||
}
|
}
|
||||||
|
|
||||||
val id = bytes4ToInt(streamID)
|
val id = bytes4ToUInt(streamID)
|
||||||
if (id < 0 || id >= CrsAlgorithm.values().size) {
|
if (id.toInt() < 0 || id.toInt() >= CrsAlgorithm.values().size) {
|
||||||
throw IOException("Invalid stream id.")
|
throw IOException("Invalid stream id.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,43 +291,42 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
|||||||
* @param version Database version
|
* @param version Database version
|
||||||
* @return true if it's a supported version
|
* @return true if it's a supported version
|
||||||
*/
|
*/
|
||||||
private fun validVersion(version: Long): Boolean {
|
private fun validVersion(version: UnsignedInt): Boolean {
|
||||||
return version and FILE_VERSION_CRITICAL_MASK <= FILE_VERSION_32_4 and FILE_VERSION_CRITICAL_MASK
|
return version.toInt() and FILE_VERSION_CRITICAL_MASK.toInt() <=
|
||||||
|
FILE_VERSION_32_4.toInt() and FILE_VERSION_CRITICAL_MASK.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
var ULONG_MAX_VALUE: Long = -1
|
val DBSIG_PRE2 = UnsignedInt(-0x4ab4049a)
|
||||||
|
val DBSIG_2 = UnsignedInt(-0x4ab40499)
|
||||||
|
|
||||||
const val DBSIG_PRE2 = -0x4ab4049a
|
private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000)
|
||||||
const val DBSIG_2 = -0x4ab40499
|
val FILE_VERSION_32_3 = UnsignedInt(0x00030001)
|
||||||
|
val FILE_VERSION_32_4 = UnsignedInt(0x00040000)
|
||||||
|
|
||||||
private const val FILE_VERSION_CRITICAL_MASK: Long = -0x10000
|
fun getCompressionFromFlag(flag: UnsignedInt): CompressionAlgorithm? {
|
||||||
const val FILE_VERSION_32_3: Long = 0x00030001
|
return when (flag.toInt()) {
|
||||||
const val FILE_VERSION_32_4: Long = 0x00040000
|
|
||||||
|
|
||||||
fun getCompressionFromFlag(flag: Int): CompressionAlgorithm? {
|
|
||||||
return when (flag) {
|
|
||||||
0 -> CompressionAlgorithm.None
|
0 -> CompressionAlgorithm.None
|
||||||
1 -> CompressionAlgorithm.GZip
|
1 -> CompressionAlgorithm.GZip
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFlagFromCompression(compression: CompressionAlgorithm): Int {
|
fun getFlagFromCompression(compression: CompressionAlgorithm): UnsignedInt {
|
||||||
return when (compression) {
|
return when (compression) {
|
||||||
CompressionAlgorithm.GZip -> 1
|
CompressionAlgorithm.GZip -> UnsignedInt(1)
|
||||||
else -> 0
|
else -> UnsignedInt(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun matchesHeader(sig1: Int, sig2: Int): Boolean {
|
fun matchesHeader(sig1: UnsignedInt, sig2: UnsignedInt): Boolean {
|
||||||
return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
|
return sig1 == PWM_DBSIG_1 && (sig2 == DBSIG_PRE2 || sig2 == DBSIG_2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
|
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
|
||||||
val blockKey = HmacBlockStream.getHmacKey64(key, ULONG_MAX_VALUE)
|
val blockKey = HmacBlockStream.getHmacKey64(key, UnsignedLong.MAX_VALUE)
|
||||||
|
|
||||||
val hmac: Mac
|
val hmac: Mac
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -90,10 +90,10 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
|
|
||||||
// Select algorithm
|
// Select algorithm
|
||||||
when {
|
when {
|
||||||
header.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> {
|
header.flags.toInt() and DatabaseHeaderKDB.FLAG_RIJNDAEL.toInt() != 0 -> {
|
||||||
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael
|
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael
|
||||||
}
|
}
|
||||||
header.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> {
|
header.flags.toInt() and DatabaseHeaderKDB.FLAG_TWOFISH.toInt() != 0 -> {
|
||||||
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish
|
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish
|
||||||
}
|
}
|
||||||
else -> throw InvalidAlgorithmDatabaseException()
|
else -> throw InvalidAlgorithmDatabaseException()
|
||||||
@@ -150,26 +150,6 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
/* TODO checksum
|
|
||||||
// Add a mark to the content start
|
|
||||||
if (!cipherInputStream.markSupported()) {
|
|
||||||
throw IOException("Input stream does not support mark.")
|
|
||||||
}
|
|
||||||
cipherInputStream.mark(cipherInputStream.available() +1)
|
|
||||||
// Consume all data to get the digest
|
|
||||||
var numberRead = 0
|
|
||||||
while (numberRead > -1) {
|
|
||||||
numberRead = cipherInputStream.read(ByteArray(1024))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check sum
|
|
||||||
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
|
|
||||||
throw InvalidCredentialsDatabaseException()
|
|
||||||
}
|
|
||||||
// Back to the content start
|
|
||||||
cipherInputStream.reset()
|
|
||||||
*/
|
|
||||||
|
|
||||||
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
|
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
|
||||||
val newRoot = mDatabaseToOpen.createGroup()
|
val newRoot = mDatabaseToOpen.createGroup()
|
||||||
newRoot.level = -1
|
newRoot.level = -1
|
||||||
@@ -180,8 +160,8 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
var newEntry: EntryKDB? = null
|
var newEntry: EntryKDB? = null
|
||||||
var currentGroupNumber = 0
|
var currentGroupNumber = 0
|
||||||
var currentEntryNumber = 0
|
var currentEntryNumber = 0
|
||||||
while (currentGroupNumber < header.numGroups
|
while (currentGroupNumber < header.numGroups.toLong()
|
||||||
|| currentEntryNumber < header.numEntries) {
|
|| currentEntryNumber < header.numEntries.toLong()) {
|
||||||
|
|
||||||
val fieldType = cipherInputStream.readBytes2ToUShort()
|
val fieldType = cipherInputStream.readBytes2ToUShort()
|
||||||
val fieldSize = cipherInputStream.readBytes4ToUInt().toInt()
|
val fieldSize = cipherInputStream.readBytes4ToUInt().toInt()
|
||||||
@@ -195,7 +175,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
when (fieldSize) {
|
when (fieldSize) {
|
||||||
4 -> {
|
4 -> {
|
||||||
newGroup = mDatabaseToOpen.createGroup().apply {
|
newGroup = mDatabaseToOpen.createGroup().apply {
|
||||||
setGroupId(cipherInputStream.readBytes4ToInt())
|
setGroupId(cipherInputStream.readBytes4ToUInt().toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
16 -> {
|
16 -> {
|
||||||
@@ -214,7 +194,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
} ?:
|
} ?:
|
||||||
newEntry?.let { entry ->
|
newEntry?.let { entry ->
|
||||||
val groupKDB = mDatabaseToOpen.createGroup()
|
val groupKDB = mDatabaseToOpen.createGroup()
|
||||||
groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToInt())
|
groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToUInt().toInt())
|
||||||
entry.parent = groupKDB
|
entry.parent = groupKDB
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,7 +203,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
group.creationTime = cipherInputStream.readBytes5ToDate()
|
group.creationTime = cipherInputStream.readBytes5ToDate()
|
||||||
} ?:
|
} ?:
|
||||||
newEntry?.let { entry ->
|
newEntry?.let { entry ->
|
||||||
var iconId = cipherInputStream.readBytes4ToInt()
|
var iconId = cipherInputStream.readBytes4ToUInt().toInt()
|
||||||
// Clean up after bug that set icon ids to -1
|
// Clean up after bug that set icon ids to -1
|
||||||
if (iconId == -1) {
|
if (iconId == -1) {
|
||||||
iconId = 0
|
iconId = 0
|
||||||
@@ -257,7 +237,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
}
|
}
|
||||||
0x0007 -> {
|
0x0007 -> {
|
||||||
newGroup?.let { group ->
|
newGroup?.let { group ->
|
||||||
group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToInt())
|
group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToUInt().toInt())
|
||||||
} ?:
|
} ?:
|
||||||
newEntry?.let { entry ->
|
newEntry?.let { entry ->
|
||||||
entry.password = cipherInputStream.readBytesToString(fieldSize,false)
|
entry.password = cipherInputStream.readBytesToString(fieldSize,false)
|
||||||
@@ -273,7 +253,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
|||||||
}
|
}
|
||||||
0x0009 -> {
|
0x0009 -> {
|
||||||
newGroup?.let { group ->
|
newGroup?.let { group ->
|
||||||
group.flags = cipherInputStream.readBytes4ToInt()
|
group.groupFlags = cipherInputStream.readBytes4ToUInt().toInt()
|
||||||
} ?:
|
} ?:
|
||||||
newEntry?.let { entry ->
|
newEntry?.let { entry ->
|
||||||
entry.creationTime = cipherInputStream.readBytes5ToDate()
|
entry.creationTime = cipherInputStream.readBytes5ToDate()
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
|
|||||||
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||||
import org.spongycastle.crypto.StreamCipher
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
|
import org.bouncycastle.crypto.StreamCipher
|
||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import org.xmlpull.v1.XmlPullParserFactory
|
import org.xmlpull.v1.XmlPullParserFactory
|
||||||
@@ -130,7 +132,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
}
|
}
|
||||||
|
|
||||||
val isPlain: InputStream
|
val isPlain: InputStream
|
||||||
if (mDatabase.kdbxVersion < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (mDatabase.kdbxVersion.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
|
|
||||||
val decrypted = attachCipherStream(databaseInputStream, cipher)
|
val decrypted = attachCipherStream(databaseInputStream, cipher)
|
||||||
val dataDecrypted = LittleEndianDataInputStream(decrypted)
|
val dataDecrypted = LittleEndianDataInputStream(decrypted)
|
||||||
@@ -178,7 +180,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
else -> isPlain
|
else -> isPlain
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (mDatabase.kdbxVersion.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
loadInnerHeader(inputStreamXml, header)
|
loadInnerHeader(inputStreamXml, header)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,19 +228,24 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
header: DatabaseHeaderKDBX): Boolean {
|
header: DatabaseHeaderKDBX): Boolean {
|
||||||
val fieldId = dataInputStream.read().toByte()
|
val fieldId = dataInputStream.read().toByte()
|
||||||
|
|
||||||
val size = dataInputStream.readInt()
|
val size = dataInputStream.readUInt().toInt()
|
||||||
if (size < 0) throw IOException("Corrupted file")
|
if (size < 0) throw IOException("Corrupted file")
|
||||||
|
|
||||||
|
var data = ByteArray(0)
|
||||||
|
if (size > 0) {
|
||||||
|
if (fieldId != DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
|
||||||
|
data = dataInputStream.readBytes(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = true
|
||||||
when (fieldId) {
|
when (fieldId) {
|
||||||
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> {
|
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> {
|
||||||
return false
|
result = false
|
||||||
}
|
}
|
||||||
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> {
|
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> {
|
||||||
val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0)
|
|
||||||
header.setRandomStreamID(data)
|
header.setRandomStreamID(data)
|
||||||
}
|
}
|
||||||
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> {
|
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> {
|
||||||
val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0)
|
|
||||||
header.innerRandomStreamKey = data
|
header.innerRandomStreamKey = data
|
||||||
}
|
}
|
||||||
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
|
DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> {
|
||||||
@@ -255,12 +262,9 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
val protectedBinary = BinaryAttachment(file, protectedFlag)
|
val protectedBinary = BinaryAttachment(file, protectedFlag)
|
||||||
mDatabase.binaryPool.add(protectedBinary)
|
mDatabase.binaryPool.add(protectedBinary)
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class KdbContext {
|
private enum class KdbContext {
|
||||||
@@ -488,7 +492,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
} else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) {
|
||||||
ctxGroup?.notes = readString(xpp)
|
ctxGroup?.notes = readString(xpp)
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
|
||||||
ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt())
|
ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toInt())
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
|
||||||
ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
|
ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
|
||||||
@@ -542,7 +546,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
|
KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
|
||||||
ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp))
|
ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp))
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) {
|
||||||
ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt())
|
ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, UnsignedInt(0)).toInt())
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
|
||||||
ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
|
ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp))
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) {
|
||||||
@@ -598,7 +602,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp)
|
name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp)
|
||||||
name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp)
|
name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp)
|
||||||
name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false)
|
name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false)
|
||||||
name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, 0)
|
name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, UnsignedLong(0))
|
||||||
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp)
|
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp)
|
||||||
else -> readUnknown(xpp)
|
else -> readUnknown(xpp)
|
||||||
}
|
}
|
||||||
@@ -621,7 +625,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
KdbContext.EntryAutoType -> if (name.equals(DatabaseKDBXXML.ElemAutoTypeEnabled, ignoreCase = true)) {
|
KdbContext.EntryAutoType -> if (name.equals(DatabaseKDBXXML.ElemAutoTypeEnabled, ignoreCase = true)) {
|
||||||
ctxEntry?.autoType?.enabled = readBool(xpp, true)
|
ctxEntry?.autoType?.enabled = readBool(xpp, true)
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeObfuscation, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeObfuscation, ignoreCase = true)) {
|
||||||
ctxEntry?.autoType?.obfuscationOptions = readUInt(xpp, 0)
|
ctxEntry?.autoType?.obfuscationOptions = readUInt(xpp, UnsignedInt(0))
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, ignoreCase = true)) {
|
||||||
ctxEntry?.autoType?.defaultSequence = readString(xpp)
|
ctxEntry?.autoType?.defaultSequence = readString(xpp)
|
||||||
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) {
|
} else if (name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) {
|
||||||
@@ -815,7 +819,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
val sDate = readString(xpp)
|
val sDate = readString(xpp)
|
||||||
var utcDate: Date? = null
|
var utcDate: Date? = null
|
||||||
|
|
||||||
if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (mDatabase.kdbxVersion.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
var buf = Base64.decode(sDate, BASE_64_FLAG)
|
var buf = Base64.decode(sDate, BASE_64_FLAG)
|
||||||
if (buf.size != 8) {
|
if (buf.size != 8) {
|
||||||
val buf8 = ByteArray(8)
|
val buf8 = ByteArray(8)
|
||||||
@@ -887,48 +891,39 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun readInt(xpp: XmlPullParser, def: Int): Int {
|
private fun readInt(xpp: XmlPullParser, default: Int): Int {
|
||||||
val str = readString(xpp)
|
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
Integer.parseInt(str)
|
readString(xpp).toInt()
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: Exception) {
|
||||||
def
|
default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun readUInt(xpp: XmlPullParser, uDefault: Long): Long {
|
private fun readUInt(xpp: XmlPullParser, default: UnsignedInt): UnsignedInt {
|
||||||
val u: Long = readULong(xpp, uDefault)
|
|
||||||
|
|
||||||
if (u < 0 || u > MAX_UINT) {
|
|
||||||
throw NumberFormatException("Outside of the uint size")
|
|
||||||
}
|
|
||||||
|
|
||||||
return u
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
|
||||||
private fun readLong(xpp: XmlPullParser, def: Long): Long {
|
|
||||||
val str = readString(xpp)
|
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
java.lang.Long.parseLong(str)
|
UnsignedInt(readString(xpp).toInt())
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: Exception) {
|
||||||
def
|
default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, XmlPullParserException::class)
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
private fun readULong(xpp: XmlPullParser, uDefault: Long): Long {
|
private fun readLong(xpp: XmlPullParser, default: Long): Long {
|
||||||
var u = readLong(xpp, uDefault)
|
return try {
|
||||||
|
readString(xpp).toLong()
|
||||||
if (u < 0) {
|
} catch (e: Exception) {
|
||||||
u = uDefault
|
default
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return u
|
@Throws(IOException::class, XmlPullParserException::class)
|
||||||
|
private fun readULong(xpp: XmlPullParser, default: UnsignedLong): UnsignedLong {
|
||||||
|
return try {
|
||||||
|
UnsignedLong(readString(xpp).toLong())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
default
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(XmlPullParserException::class, IOException::class)
|
@Throws(XmlPullParserException::class, IOException::class)
|
||||||
@@ -1050,7 +1045,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val DEFAULT_HISTORY_DAYS: Long = 365
|
private val DEFAULT_HISTORY_DAYS = UnsignedInt(365)
|
||||||
|
|
||||||
@Throws(XmlPullParserException::class)
|
@Throws(XmlPullParserException::class)
|
||||||
private fun createPullParser(readerStream: InputStream): XmlPullParser {
|
private fun createPullParser(readerStream: InputStream): XmlPullParser {
|
||||||
@@ -1062,8 +1057,6 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
|||||||
|
|
||||||
return xpp
|
return xpp
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val MAX_UINT = 4294967296L // 2^32
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.database.file.output
|
package com.kunzisoft.keepass.database.file.output
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||||
import com.kunzisoft.keepass.stream.intTo4Bytes
|
import com.kunzisoft.keepass.stream.uIntTo4Bytes
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
@@ -29,14 +29,14 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun outputStart() {
|
fun outputStart() {
|
||||||
mOutputStream.write(intTo4Bytes(mHeader.signature1))
|
mOutputStream.write(uIntTo4Bytes(mHeader.signature1))
|
||||||
mOutputStream.write(intTo4Bytes(mHeader.signature2))
|
mOutputStream.write(uIntTo4Bytes(mHeader.signature2))
|
||||||
mOutputStream.write(intTo4Bytes(mHeader.flags))
|
mOutputStream.write(uIntTo4Bytes(mHeader.flags))
|
||||||
mOutputStream.write(intTo4Bytes(mHeader.version))
|
mOutputStream.write(uIntTo4Bytes(mHeader.version))
|
||||||
mOutputStream.write(mHeader.masterSeed)
|
mOutputStream.write(mHeader.masterSeed)
|
||||||
mOutputStream.write(mHeader.encryptionIV)
|
mOutputStream.write(mHeader.encryptionIV)
|
||||||
mOutputStream.write(intTo4Bytes(mHeader.numGroups))
|
mOutputStream.write(uIntTo4Bytes(mHeader.numGroups))
|
||||||
mOutputStream.write(intTo4Bytes(mHeader.numEntries))
|
mOutputStream.write(uIntTo4Bytes(mHeader.numEntries))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@@ -47,7 +47,7 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun outputEnd() {
|
fun outputEnd() {
|
||||||
mOutputStream.write(mHeader.transformSeed)
|
mOutputStream.write(mHeader.transformSeed)
|
||||||
mOutputStream.write(intTo4Bytes(mHeader.numKeyEncRounds))
|
mOutputStream.write(uIntTo4Bytes(mHeader.numKeyEncRounds))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
|||||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
|
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -66,7 +66,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
|||||||
val hmac: Mac
|
val hmac: Mac
|
||||||
try {
|
try {
|
||||||
hmac = Mac.getInstance("HmacSHA256")
|
hmac = Mac.getInstance("HmacSHA256")
|
||||||
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, ULONG_MAX_VALUE), "HmacSHA256")
|
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, UnsignedLong.MAX_VALUE), "HmacSHA256")
|
||||||
hmac.init(signingKey)
|
hmac.init(signingKey)
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
} catch (e: NoSuchAlgorithmException) {
|
||||||
throw DatabaseOutputException(e)
|
throw DatabaseOutputException(e)
|
||||||
@@ -82,15 +82,15 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun output() {
|
fun output() {
|
||||||
|
|
||||||
los.writeUInt(DatabaseHeader.PWM_DBSIG_1.toLong())
|
los.writeUInt(DatabaseHeader.PWM_DBSIG_1)
|
||||||
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2.toLong())
|
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2)
|
||||||
los.writeUInt(header.version)
|
los.writeUInt(header.version)
|
||||||
|
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, intTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
|
||||||
|
|
||||||
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
|
||||||
} else {
|
} else {
|
||||||
@@ -101,10 +101,10 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
|||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
|
||||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, intTo4Bytes(header.innerRandomStream!!.id))
|
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (databaseKDBX.containsPublicCustomData()) {
|
if (databaseKDBX.containsPublicCustomData()) {
|
||||||
@@ -136,7 +136,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun writeHeaderFieldSize(size: Int) {
|
private fun writeHeaderFieldSize(size: Int) {
|
||||||
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
los.writeUShort(size)
|
los.writeUShort(size)
|
||||||
} else {
|
} else {
|
||||||
los.writeInt(size)
|
los.writeInt(size)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
|
|||||||
dataOutputStream.writeInt(4)
|
dataOutputStream.writeInt(4)
|
||||||
if (header.innerRandomStream == null)
|
if (header.innerRandomStream == null)
|
||||||
throw IOException("Can't write innerRandomStream")
|
throw IOException("Can't write innerRandomStream")
|
||||||
dataOutputStream.writeInt(header.innerRandomStream!!.id)
|
dataOutputStream.writeInt(header.innerRandomStream!!.id.toInt())
|
||||||
|
|
||||||
val streamKeySize = header.innerRandomStreamKey.size
|
val streamKeySize = header.innerRandomStreamKey.size
|
||||||
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt())
|
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt())
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.file.DatabaseHeader
|
|||||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||||
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
|
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
|
||||||
import com.kunzisoft.keepass.stream.NullOutputStream
|
import com.kunzisoft.keepass.stream.NullOutputStream
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -117,18 +118,18 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
|||||||
|
|
||||||
when {
|
when {
|
||||||
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
|
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
|
||||||
header.flags = header.flags or DatabaseHeaderKDB.FLAG_RIJNDAEL
|
header.flags = UnsignedInt(header.flags.toInt() or DatabaseHeaderKDB.FLAG_RIJNDAEL.toInt())
|
||||||
}
|
}
|
||||||
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
|
mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
|
||||||
header.flags = header.flags or DatabaseHeaderKDB.FLAG_TWOFISH
|
header.flags = UnsignedInt(header.flags.toInt() or DatabaseHeaderKDB.FLAG_TWOFISH.toInt())
|
||||||
}
|
}
|
||||||
else -> throw DatabaseOutputException("Unsupported algorithm.")
|
else -> throw DatabaseOutputException("Unsupported algorithm.")
|
||||||
}
|
}
|
||||||
|
|
||||||
header.version = DatabaseHeaderKDB.DBVER_DW
|
header.version = DatabaseHeaderKDB.DBVER_DW
|
||||||
header.numGroups = mDatabaseKDB.numberOfGroups()
|
header.numGroups = UnsignedInt(mDatabaseKDB.numberOfGroups())
|
||||||
header.numEntries = mDatabaseKDB.numberOfEntries()
|
header.numEntries = UnsignedInt(mDatabaseKDB.numberOfEntries())
|
||||||
header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt() // TODO Signed Long - Unsigned Int
|
header.numKeyEncRounds = UnsignedInt.fromLong(mDatabaseKDB.numberKeyEncryptionRounds)
|
||||||
|
|
||||||
setIVs(header)
|
setIVs(header)
|
||||||
|
|
||||||
@@ -279,6 +280,5 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
|||||||
if (data != null) {
|
if (data != null) {
|
||||||
los.write(data)
|
los.write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,8 +47,8 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
|||||||
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
|
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
|
||||||
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
|
import org.bouncycastle.crypto.StreamCipher
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import org.spongycastle.crypto.StreamCipher
|
|
||||||
import org.xmlpull.v1.XmlSerializer
|
import org.xmlpull.v1.XmlSerializer
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -85,7 +85,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
header = outputHeader(mOS)
|
header = outputHeader(mOS)
|
||||||
|
|
||||||
val osPlain: OutputStream
|
val osPlain: OutputStream
|
||||||
osPlain = if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
osPlain = if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
val cos = attachStreamEncryptor(header!!, mOS)
|
val cos = attachStreamEncryptor(header!!, mOS)
|
||||||
cos.write(header!!.streamStartBytes)
|
cos.write(header!!.streamStartBytes)
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
else -> osPlain
|
else -> osPlain
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header!!.version >= DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (header!!.version.toLong() >= DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml)
|
val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml)
|
||||||
ihOut.output()
|
ihOut.output()
|
||||||
}
|
}
|
||||||
@@ -209,7 +209,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
writeObject(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged.date)
|
writeObject(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged.date)
|
||||||
writeObject(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true)
|
writeObject(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true)
|
||||||
writeObject(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged.date)
|
writeObject(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged.date)
|
||||||
writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays)
|
writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays.toLong())
|
||||||
writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
|
writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
|
||||||
writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date)
|
writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date)
|
||||||
writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
|
writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
|
||||||
@@ -230,7 +230,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
|
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
|
||||||
|
|
||||||
// Seem to work properly if always in meta
|
// Seem to work properly if always in meta
|
||||||
if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4)
|
if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong())
|
||||||
writeMetaBinaries()
|
writeMetaBinaries()
|
||||||
|
|
||||||
writeCustomData(mDatabaseKDBX.customData)
|
writeCustomData(mDatabaseKDBX.customData)
|
||||||
@@ -274,7 +274,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
Log.e(TAG, "Unable to retrieve header", unknownKDF)
|
Log.e(TAG, "Unable to retrieve header", unknownKDF)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
header.innerRandomStream = CrsAlgorithm.Salsa20
|
header.innerRandomStream = CrsAlgorithm.Salsa20
|
||||||
header.innerRandomStreamKey = ByteArray(32)
|
header.innerRandomStreamKey = ByteArray(32)
|
||||||
} else {
|
} else {
|
||||||
@@ -288,7 +288,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
throw DatabaseOutputException("Invalid random cipher")
|
throw DatabaseOutputException("Invalid random cipher")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (header.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
random.nextBytes(header.streamStartBytes)
|
random.nextBytes(header.streamStartBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +385,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
|
|
||||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||||
private fun writeObject(name: String, value: Date) {
|
private fun writeObject(name: String, value: Date) {
|
||||||
if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
|
if (header!!.version.toLong() < DatabaseHeaderKDBX.FILE_VERSION_32_4.toLong()) {
|
||||||
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
|
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
|
||||||
} else {
|
} else {
|
||||||
val dt = DateTime(value)
|
val dt = DateTime(value)
|
||||||
@@ -489,7 +489,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
xml.startTag(null, DatabaseKDBXXML.ElemAutoType)
|
xml.startTag(null, DatabaseKDBXXML.ElemAutoType)
|
||||||
|
|
||||||
writeObject(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled)
|
writeObject(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled)
|
||||||
writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions)
|
writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions.toLong())
|
||||||
|
|
||||||
if (autoType.defaultSequence.isNotEmpty()) {
|
if (autoType.defaultSequence.isNotEmpty()) {
|
||||||
writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
|
writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
|
||||||
@@ -629,7 +629,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
|||||||
writeObject(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime.date)
|
writeObject(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime.date)
|
||||||
writeObject(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime.date)
|
writeObject(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime.date)
|
||||||
writeObject(DatabaseKDBXXML.ElemExpires, node.expires)
|
writeObject(DatabaseKDBXXML.ElemExpires, node.expires)
|
||||||
writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount)
|
writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount.toLong())
|
||||||
writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date)
|
writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date)
|
||||||
|
|
||||||
xml.endTag(null, DatabaseKDBXXML.ElemTimes)
|
xml.endTag(null, DatabaseKDBXXML.ElemTimes)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
|||||||
import com.kunzisoft.keepass.database.file.output.GroupOutputKDB.Companion.GROUPID_FIELD_SIZE
|
import com.kunzisoft.keepass.database.file.output.GroupOutputKDB.Companion.GROUPID_FIELD_SIZE
|
||||||
import com.kunzisoft.keepass.stream.*
|
import com.kunzisoft.keepass.stream.*
|
||||||
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
|
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
@@ -54,12 +55,12 @@ class EntryOutputKDB
|
|||||||
// Group ID
|
// Group ID
|
||||||
mOutputStream.write(GROUPID_FIELD_TYPE)
|
mOutputStream.write(GROUPID_FIELD_TYPE)
|
||||||
mOutputStream.write(GROUPID_FIELD_SIZE)
|
mOutputStream.write(GROUPID_FIELD_SIZE)
|
||||||
mOutputStream.write(intTo4Bytes(mEntry.parent!!.id))
|
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.parent!!.id)))
|
||||||
|
|
||||||
// Image ID
|
// Image ID
|
||||||
mOutputStream.write(IMAGEID_FIELD_TYPE)
|
mOutputStream.write(IMAGEID_FIELD_TYPE)
|
||||||
mOutputStream.write(IMAGEID_FIELD_SIZE)
|
mOutputStream.write(IMAGEID_FIELD_SIZE)
|
||||||
mOutputStream.write(intTo4Bytes(mEntry.icon.iconId))
|
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mEntry.icon.iconId)))
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
//byte[] title = mEntry.title.getBytes("UTF-8");
|
//byte[] title = mEntry.title.getBytes("UTF-8");
|
||||||
@@ -101,14 +102,9 @@ class EntryOutputKDB
|
|||||||
// Binary
|
// Binary
|
||||||
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
|
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
|
||||||
val binaryData = mEntry.binaryData
|
val binaryData = mEntry.binaryData
|
||||||
val binaryDataLength = binaryData?.length() ?: 0
|
val binaryDataLength = binaryData?.length() ?: 0L
|
||||||
val binaryDataLengthRightSize = if (binaryDataLength <= Int.MAX_VALUE) {
|
|
||||||
binaryDataLength.toInt()
|
|
||||||
} else {
|
|
||||||
0 // TODO if length > UInt.maxvalue show exception
|
|
||||||
}
|
|
||||||
// Write data length
|
// Write data length
|
||||||
mOutputStream.write(intTo4Bytes(binaryDataLengthRightSize))
|
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromLong(binaryDataLength)))
|
||||||
// Write data
|
// Write data
|
||||||
if (binaryDataLength > 0) {
|
if (binaryDataLength > 0) {
|
||||||
binaryData?.getInputDataStream().use { inputStream ->
|
binaryData?.getInputDataStream().use { inputStream ->
|
||||||
@@ -140,7 +136,7 @@ class EntryOutputKDB
|
|||||||
private fun writePassword(str: String, os: OutputStream): Int {
|
private fun writePassword(str: String, os: OutputStream): Int {
|
||||||
val initial = str.toByteArray(Charset.forName("UTF-8"))
|
val initial = str.toByteArray(Charset.forName("UTF-8"))
|
||||||
val length = initial.size + 1
|
val length = initial.size + 1
|
||||||
os.write(intTo4Bytes(length))
|
os.write(uIntTo4Bytes(UnsignedInt(length)))
|
||||||
os.write(initial)
|
os.write(initial)
|
||||||
os.write(0x00)
|
os.write(0x00)
|
||||||
return length
|
return length
|
||||||
@@ -164,13 +160,13 @@ class EntryOutputKDB
|
|||||||
val BINARY_DATA_FIELD_TYPE:ByteArray = uShortTo2Bytes(14)
|
val BINARY_DATA_FIELD_TYPE:ByteArray = uShortTo2Bytes(14)
|
||||||
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
|
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
|
||||||
|
|
||||||
val LONG_FOUR:ByteArray = intTo4Bytes(4)
|
val LONG_FOUR:ByteArray = uIntTo4Bytes(UnsignedInt(4))
|
||||||
val UUID_FIELD_SIZE:ByteArray = intTo4Bytes(16)
|
val UUID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(16))
|
||||||
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
|
val DATE_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(5))
|
||||||
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
|
val IMAGEID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
|
||||||
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(4)
|
val LEVEL_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
|
||||||
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
|
val FLAGS_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
|
||||||
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
|
val ZERO_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(0))
|
||||||
val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)
|
val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ package com.kunzisoft.keepass.database.file.output
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||||
import com.kunzisoft.keepass.stream.dateTo5Bytes
|
import com.kunzisoft.keepass.stream.dateTo5Bytes
|
||||||
import com.kunzisoft.keepass.stream.intTo4Bytes
|
import com.kunzisoft.keepass.stream.uIntTo4Bytes
|
||||||
import com.kunzisoft.keepass.stream.uShortTo2Bytes
|
import com.kunzisoft.keepass.stream.uShortTo2Bytes
|
||||||
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
|
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
|
||||||
|
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
|
|||||||
// Group ID
|
// Group ID
|
||||||
mOutputStream.write(GROUPID_FIELD_TYPE)
|
mOutputStream.write(GROUPID_FIELD_TYPE)
|
||||||
mOutputStream.write(GROUPID_FIELD_SIZE)
|
mOutputStream.write(GROUPID_FIELD_SIZE)
|
||||||
mOutputStream.write(intTo4Bytes(mGroup.id))
|
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.id)))
|
||||||
|
|
||||||
// Name
|
// Name
|
||||||
mOutputStream.write(NAME_FIELD_TYPE)
|
mOutputStream.write(NAME_FIELD_TYPE)
|
||||||
@@ -68,7 +69,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
|
|||||||
// Image ID
|
// Image ID
|
||||||
mOutputStream.write(IMAGEID_FIELD_TYPE)
|
mOutputStream.write(IMAGEID_FIELD_TYPE)
|
||||||
mOutputStream.write(IMAGEID_FIELD_SIZE)
|
mOutputStream.write(IMAGEID_FIELD_SIZE)
|
||||||
mOutputStream.write(intTo4Bytes(mGroup.icon.iconId))
|
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.icon.iconId)))
|
||||||
|
|
||||||
// Level
|
// Level
|
||||||
mOutputStream.write(LEVEL_FIELD_TYPE)
|
mOutputStream.write(LEVEL_FIELD_TYPE)
|
||||||
@@ -78,7 +79,7 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
|
|||||||
// Flags
|
// Flags
|
||||||
mOutputStream.write(FLAGS_FIELD_TYPE)
|
mOutputStream.write(FLAGS_FIELD_TYPE)
|
||||||
mOutputStream.write(FLAGS_FIELD_SIZE)
|
mOutputStream.write(FLAGS_FIELD_SIZE)
|
||||||
mOutputStream.write(intTo4Bytes(mGroup.flags))
|
mOutputStream.write(uIntTo4Bytes(UnsignedInt(mGroup.groupFlags)))
|
||||||
|
|
||||||
// End
|
// End
|
||||||
mOutputStream.write(END_FIELD_TYPE)
|
mOutputStream.write(END_FIELD_TYPE)
|
||||||
@@ -98,11 +99,11 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
|
|||||||
val FLAGS_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
|
val FLAGS_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
|
||||||
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
|
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
|
||||||
|
|
||||||
val GROUPID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
|
val GROUPID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
|
||||||
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
|
val DATE_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(5))
|
||||||
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
|
val IMAGEID_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
|
||||||
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(2)
|
val LEVEL_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(2))
|
||||||
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
|
val FLAGS_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(4))
|
||||||
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
|
val ZERO_FIELD_SIZE:ByteArray = uIntTo4Bytes(UnsignedInt(0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -22,11 +22,10 @@ package com.kunzisoft.keepass.database.search
|
|||||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||||
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
|
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
|
||||||
import com.kunzisoft.keepass.utils.StringUtil
|
|
||||||
|
|
||||||
import java.util.Locale
|
class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters,
|
||||||
|
private val mListStorage: MutableList<EntryKDBX>)
|
||||||
class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParametersKDBX, private val mListStorage: MutableList<EntryKDBX>) : NodeHandler<EntryKDBX>() {
|
: NodeHandler<EntryKDBX>() {
|
||||||
|
|
||||||
override fun operate(node: EntryKDBX): Boolean {
|
override fun operate(node: EntryKDBX): Boolean {
|
||||||
|
|
||||||
@@ -35,32 +34,17 @@ class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
var term = mSearchParametersKDBX.searchString
|
if (searchStrings(node)) {
|
||||||
if (mSearchParametersKDBX.ignoreCase) {
|
|
||||||
term = term.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchStrings(node, term)) {
|
|
||||||
mListStorage.add(node)
|
mListStorage.add(node)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mSearchParametersKDBX.searchInGroupNames) {
|
if (searchInGroupNames(node)) {
|
||||||
val parent = node.parent
|
mListStorage.add(node)
|
||||||
if (parent != null) {
|
return true
|
||||||
var groupName = parent.title
|
|
||||||
if (mSearchParametersKDBX.ignoreCase) {
|
|
||||||
groupName = groupName.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (groupName.contains(term)) {
|
|
||||||
mListStorage.add(node)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchID(node)) {
|
if (searchInUUID(node)) {
|
||||||
mListStorage.add(node)
|
mListStorage.add(node)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -68,25 +52,33 @@ class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchID(entry: EntryKDBX): Boolean {
|
private fun searchInGroupNames(entry: EntryKDBX): Boolean {
|
||||||
if (mSearchParametersKDBX.searchInUUIDs) {
|
if (mSearchParametersKDBX.searchInGroupNames) {
|
||||||
val hex = UuidUtil.toHexString(entry.id)
|
val parent = entry.parent
|
||||||
return StringUtil.indexOfIgnoreCase(hex, mSearchParametersKDBX.searchString, Locale.ENGLISH) >= 0
|
if (parent != null) {
|
||||||
|
return parent.title
|
||||||
|
.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchStrings(entry: EntryKDBX, term: String): Boolean {
|
private fun searchInUUID(entry: EntryKDBX): Boolean {
|
||||||
|
if (mSearchParametersKDBX.searchInUUIDs) {
|
||||||
|
return UuidUtil.toHexString(entry.id)
|
||||||
|
.contains(mSearchParametersKDBX.searchString, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchStrings(entry: EntryKDBX): Boolean {
|
||||||
val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX)
|
val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX)
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
var str = iterator.next()
|
val stringValue = iterator.next()
|
||||||
if (str.isNotEmpty()) {
|
if (stringValue.isNotEmpty()) {
|
||||||
if (mSearchParametersKDBX.ignoreCase) {
|
if (stringValue.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)) {
|
||||||
str = str.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str.contains(term)) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ import com.kunzisoft.keepass.database.action.node.NodeHandler
|
|||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.Group
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIterator
|
|
||||||
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB
|
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB
|
||||||
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
|
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class SearchHelper(private val isOmitBackup: Boolean) {
|
class SearchHelper(private val isOmitBackup: Boolean) {
|
||||||
|
|
||||||
@@ -36,22 +34,22 @@ class SearchHelper(private val isOmitBackup: Boolean) {
|
|||||||
|
|
||||||
private var incrementEntry = 0
|
private var incrementEntry = 0
|
||||||
|
|
||||||
fun search(database: Database, qStr: String, max: Int): Group? {
|
fun createVirtualGroupWithSearchResult(database: Database,
|
||||||
|
searchQuery: String,
|
||||||
|
searchParameters: SearchParameters,
|
||||||
|
max: Int): Group? {
|
||||||
|
|
||||||
val searchGroup = database.createGroup()
|
val searchGroup = database.createGroup()
|
||||||
searchGroup?.title = "\"" + qStr + "\""
|
searchGroup?.title = "\"" + searchQuery + "\""
|
||||||
|
|
||||||
// Search all entries
|
// Search all entries
|
||||||
val loc = Locale.getDefault()
|
|
||||||
val finalQStr = qStr.toLowerCase(loc)
|
|
||||||
|
|
||||||
incrementEntry = 0
|
incrementEntry = 0
|
||||||
database.rootGroup?.doForEachChild(
|
database.rootGroup?.doForEachChild(
|
||||||
object : NodeHandler<Entry>() {
|
object : NodeHandler<Entry>() {
|
||||||
override fun operate(node: Entry): Boolean {
|
override fun operate(node: Entry): Boolean {
|
||||||
if (incrementEntry >= max)
|
if (incrementEntry >= max)
|
||||||
return false
|
return false
|
||||||
if (entryContainsString(node, finalQStr, loc)) {
|
if (entryContainsString(node, searchQuery, searchParameters)) {
|
||||||
searchGroup?.addChildEntry(node)
|
searchGroup?.addChildEntry(node)
|
||||||
incrementEntry++
|
incrementEntry++
|
||||||
}
|
}
|
||||||
@@ -73,28 +71,29 @@ class SearchHelper(private val isOmitBackup: Boolean) {
|
|||||||
return searchGroup
|
return searchGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun entryContainsString(entry: Entry, searchString: String, locale: Locale): Boolean {
|
private fun entryContainsString(entry: Entry,
|
||||||
|
searchQuery: String,
|
||||||
|
searchParameters: SearchParameters): Boolean {
|
||||||
|
|
||||||
// Entry don't contains string if the search string is empty
|
// Entry don't contains string if the search string is empty
|
||||||
if (searchString.isEmpty())
|
if (searchQuery.isEmpty())
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// Search all strings in the entry
|
// Search all strings in the entry
|
||||||
var iterator: EntrySearchStringIterator? = null
|
var iterator: Iterator<String>? = null
|
||||||
entry.entryKDB?.let {
|
entry.entryKDB?.let {
|
||||||
iterator = EntrySearchStringIteratorKDB(it)
|
iterator = EntrySearchStringIteratorKDB(it, searchParameters)
|
||||||
}
|
}
|
||||||
entry.entryKDBX?.let {
|
entry.entryKDBX?.let {
|
||||||
iterator = EntrySearchStringIteratorKDBX(it)
|
iterator = EntrySearchStringIteratorKDBX(it, searchParameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator?.let {
|
iterator?.let {
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
val str = it.next()
|
val currentString = it.next()
|
||||||
if (str.isNotEmpty()) {
|
if (currentString.isNotEmpty()
|
||||||
if (str.toLowerCase(locale).contains(searchString)) {
|
&& currentString.contains(searchQuery, true)) {
|
||||||
return true
|
return true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.database.search
|
|||||||
/**
|
/**
|
||||||
* Parameters for searching strings in the database.
|
* Parameters for searching strings in the database.
|
||||||
*/
|
*/
|
||||||
open class SearchParameters {
|
class SearchParameters {
|
||||||
|
|
||||||
var searchString: String = ""
|
var searchString: String = ""
|
||||||
|
|
||||||
@@ -33,6 +33,9 @@ open class SearchParameters {
|
|||||||
var searchInUrls = true
|
var searchInUrls = true
|
||||||
var searchInGroupNames = false
|
var searchInGroupNames = false
|
||||||
var searchInNotes = true
|
var searchInNotes = true
|
||||||
|
var searchInOther = true
|
||||||
|
var searchInUUIDs = false
|
||||||
|
var searchInTags = true
|
||||||
var ignoreCase = true
|
var ignoreCase = true
|
||||||
var ignoreExpired = false
|
var ignoreExpired = false
|
||||||
var excludeExpired = false
|
var excludeExpired = false
|
||||||
@@ -44,24 +47,30 @@ open class SearchParameters {
|
|||||||
* @param source
|
* @param source
|
||||||
*/
|
*/
|
||||||
constructor(source: SearchParameters) {
|
constructor(source: SearchParameters) {
|
||||||
regularExpression = source.regularExpression
|
this.regularExpression = source.regularExpression
|
||||||
searchInTitles = source.searchInTitles
|
this.searchInTitles = source.searchInTitles
|
||||||
searchInUserNames = source.searchInUserNames
|
this.searchInUserNames = source.searchInUserNames
|
||||||
searchInPasswords = source.searchInPasswords
|
this.searchInPasswords = source.searchInPasswords
|
||||||
searchInUrls = source.searchInUrls
|
this.searchInUrls = source.searchInUrls
|
||||||
searchInGroupNames = source.searchInGroupNames
|
this.searchInGroupNames = source.searchInGroupNames
|
||||||
searchInNotes = source.searchInNotes
|
this.searchInNotes = source.searchInNotes
|
||||||
ignoreCase = source.ignoreCase
|
this.searchInOther = source.searchInOther
|
||||||
ignoreExpired = source.ignoreExpired
|
this.searchInUUIDs = source.searchInUUIDs
|
||||||
excludeExpired = source.excludeExpired
|
this.searchInTags = source.searchInTags
|
||||||
|
this.ignoreCase = source.ignoreCase
|
||||||
|
this.ignoreExpired = source.ignoreExpired
|
||||||
|
this.excludeExpired = source.excludeExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun setupNone() {
|
fun setupNone() {
|
||||||
searchInTitles = false
|
searchInTitles = false
|
||||||
searchInUserNames = false
|
searchInUserNames = false
|
||||||
searchInPasswords = false
|
searchInPasswords = false
|
||||||
searchInUrls = false
|
searchInUrls = false
|
||||||
searchInGroupNames = false
|
searchInGroupNames = false
|
||||||
searchInNotes = false
|
searchInNotes = false
|
||||||
|
searchInOther = false
|
||||||
|
searchInUUIDs = false
|
||||||
|
searchInTags = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePassDX.
|
|
||||||
*
|
|
||||||
* KeePassDX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePassDX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.database.search
|
|
||||||
|
|
||||||
class SearchParametersKDBX : SearchParameters {
|
|
||||||
|
|
||||||
var searchInOther = true
|
|
||||||
var searchInUUIDs = false
|
|
||||||
var searchInTags = true
|
|
||||||
|
|
||||||
constructor() : super()
|
|
||||||
|
|
||||||
constructor(searchParametersV4: SearchParametersKDBX) : super(searchParametersV4) {
|
|
||||||
this.searchInOther = searchParametersV4.searchInOther
|
|
||||||
this.searchInUUIDs = searchParametersV4.searchInUUIDs
|
|
||||||
this.searchInTags = searchParametersV4.searchInTags
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setupNone() {
|
|
||||||
super.setupNone()
|
|
||||||
searchInOther = false
|
|
||||||
searchInUUIDs = false
|
|
||||||
searchInTags = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePassDX.
|
|
||||||
*
|
|
||||||
* KeePassDX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePassDX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.database.search.iterator
|
|
||||||
|
|
||||||
abstract class EntrySearchStringIterator : Iterator<String>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -24,12 +24,10 @@ import com.kunzisoft.keepass.database.search.SearchParameters
|
|||||||
|
|
||||||
import java.util.NoSuchElementException
|
import java.util.NoSuchElementException
|
||||||
|
|
||||||
class EntrySearchStringIteratorKDB
|
class EntrySearchStringIteratorKDB(
|
||||||
|
private val mEntry: EntryKDB,
|
||||||
@JvmOverloads
|
private val mSearchParameters: SearchParameters)
|
||||||
constructor(private val mEntry: EntryKDB,
|
: Iterator<String> {
|
||||||
private val mSearchParameters: SearchParameters? = SearchParameters())
|
|
||||||
: EntrySearchStringIterator() {
|
|
||||||
|
|
||||||
private var current = 0
|
private var current = 0
|
||||||
|
|
||||||
@@ -39,7 +37,7 @@ constructor(private val mEntry: EntryKDB,
|
|||||||
title -> mEntry.title
|
title -> mEntry.title
|
||||||
url -> mEntry.url
|
url -> mEntry.url
|
||||||
username -> mEntry.username
|
username -> mEntry.username
|
||||||
comment -> mEntry.notes
|
notes -> mEntry.notes
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,18 +60,13 @@ constructor(private val mEntry: EntryKDB,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun useSearchParameters() {
|
private fun useSearchParameters() {
|
||||||
|
|
||||||
if (mSearchParameters == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var found = false
|
var found = false
|
||||||
while (!found) {
|
while (!found) {
|
||||||
found = when (current) {
|
found = when (current) {
|
||||||
title -> mSearchParameters.searchInTitles
|
title -> mSearchParameters.searchInTitles
|
||||||
url -> mSearchParameters.searchInUrls
|
url -> mSearchParameters.searchInUrls
|
||||||
username -> mSearchParameters.searchInUserNames
|
username -> mSearchParameters.searchInUserNames
|
||||||
comment -> mSearchParameters.searchInNotes
|
notes -> mSearchParameters.searchInNotes
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,11 +77,10 @@ constructor(private val mEntry: EntryKDB,
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val title = 0
|
private const val title = 0
|
||||||
private const val url = 1
|
private const val url = 1
|
||||||
private const val username = 2
|
private const val username = 2
|
||||||
private const val comment = 3
|
private const val notes = 3
|
||||||
private const val maxEntries = 4
|
private const val maxEntries = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,25 +20,20 @@
|
|||||||
package com.kunzisoft.keepass.database.search.iterator
|
package com.kunzisoft.keepass.database.search.iterator
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||||
import com.kunzisoft.keepass.database.search.SearchParametersKDBX
|
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
|
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.Map.Entry
|
import kotlin.collections.Map.Entry
|
||||||
|
|
||||||
class EntrySearchStringIteratorKDBX : EntrySearchStringIterator {
|
class EntrySearchStringIteratorKDBX(
|
||||||
|
entry: EntryKDBX,
|
||||||
|
private val mSearchParameters: SearchParameters)
|
||||||
|
: Iterator<String> {
|
||||||
|
|
||||||
private var mCurrent: String? = null
|
private var mCurrent: String? = null
|
||||||
private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null
|
private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null
|
||||||
private var mSearchParametersV4: SearchParametersKDBX
|
|
||||||
|
|
||||||
constructor(entry: EntryKDBX) {
|
init {
|
||||||
this.mSearchParametersV4 = SearchParametersKDBX()
|
|
||||||
mSetIterator = entry.fields.entries.iterator()
|
|
||||||
advance()
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(entry: EntryKDBX, searchParametersV4: SearchParametersKDBX) {
|
|
||||||
this.mSearchParametersV4 = searchParametersV4
|
|
||||||
mSetIterator = entry.fields.entries.iterator()
|
mSetIterator = entry.fields.entries.iterator()
|
||||||
advance()
|
advance()
|
||||||
}
|
}
|
||||||
@@ -75,12 +70,12 @@ class EntrySearchStringIteratorKDBX : EntrySearchStringIterator {
|
|||||||
|
|
||||||
private fun searchInField(key: String): Boolean {
|
private fun searchInField(key: String): Boolean {
|
||||||
return when (key) {
|
return when (key) {
|
||||||
EntryKDBX.STR_TITLE -> mSearchParametersV4.searchInTitles
|
EntryKDBX.STR_TITLE -> mSearchParameters.searchInTitles
|
||||||
EntryKDBX.STR_USERNAME -> mSearchParametersV4.searchInUserNames
|
EntryKDBX.STR_USERNAME -> mSearchParameters.searchInUserNames
|
||||||
EntryKDBX.STR_PASSWORD -> mSearchParametersV4.searchInPasswords
|
EntryKDBX.STR_PASSWORD -> mSearchParameters.searchInPasswords
|
||||||
EntryKDBX.STR_URL -> mSearchParametersV4.searchInUrls
|
EntryKDBX.STR_URL -> mSearchParameters.searchInUrls
|
||||||
EntryKDBX.STR_NOTES -> mSearchParametersV4.searchInNotes
|
EntryKDBX.STR_NOTES -> mSearchParameters.searchInNotes
|
||||||
else -> mSearchParametersV4.searchInOther
|
else -> mSearchParameters.searchInOther
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ package com.kunzisoft.keepass.icons
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Bitmap
|
import android.graphics.*
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import androidx.core.content.res.ResourcesCompat
|
|
||||||
import androidx.core.widget.ImageViewCompat
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import android.widget.RemoteViews
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import androidx.core.widget.ImageViewCompat
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||||
@@ -62,7 +62,7 @@ class IconDrawableFactory {
|
|||||||
fun assignDrawableToImageView(superDrawable: SuperDrawable, imageView: ImageView?, tint: Boolean, tintColor: Int) {
|
fun assignDrawableToImageView(superDrawable: SuperDrawable, imageView: ImageView?, tint: Boolean, tintColor: Int) {
|
||||||
if (imageView != null) {
|
if (imageView != null) {
|
||||||
imageView.setImageDrawable(superDrawable.drawable)
|
imageView.setImageDrawable(superDrawable.drawable)
|
||||||
if (!superDrawable.custom && tint) {
|
if (superDrawable.tintable && tint) {
|
||||||
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor))
|
ImageViewCompat.setImageTintList(imageView, ColorStateList.valueOf(tintColor))
|
||||||
} else {
|
} else {
|
||||||
ImageViewCompat.setImageTintList(imageView, null)
|
ImageViewCompat.setImageTintList(imageView, null)
|
||||||
@@ -70,6 +70,23 @@ class IconDrawableFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to assign a drawable to a RemoteView and tint it
|
||||||
|
*/
|
||||||
|
fun assignDrawableToRemoteViews(superDrawable: SuperDrawable,
|
||||||
|
remoteViews: RemoteViews,
|
||||||
|
imageId: Int,
|
||||||
|
tintColor: Int = Color.BLACK) {
|
||||||
|
val bitmap = superDrawable.drawable.toBitmap()
|
||||||
|
// Tint bitmap if it's not a custom icon
|
||||||
|
if (superDrawable.tintable && bitmap.isMutable) {
|
||||||
|
Canvas(bitmap).drawBitmap(bitmap, 0.0F, 0.0F, Paint().apply {
|
||||||
|
colorFilter = PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
remoteViews.setImageViewBitmap(imageId, bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed
|
* Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed
|
||||||
*/
|
*/
|
||||||
@@ -80,7 +97,7 @@ class IconDrawableFactory {
|
|||||||
getIconSuperDrawable(context, resId, width, tint, tintColor)
|
getIconSuperDrawable(context, resId, width, tint, tintColor)
|
||||||
}
|
}
|
||||||
is IconImageCustom -> {
|
is IconImageCustom -> {
|
||||||
SuperDrawable(getIconDrawable(context.resources, icon), true)
|
SuperDrawable(getIconDrawable(context.resources, icon))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
SuperDrawable(PatternIcon(context.resources).blankDrawable)
|
SuperDrawable(PatternIcon(context.resources).blankDrawable)
|
||||||
@@ -93,7 +110,7 @@ class IconDrawableFactory {
|
|||||||
* , then [tint] it with [tintColor] if needed
|
* , then [tint] it with [tintColor] if needed
|
||||||
*/
|
*/
|
||||||
fun getIconSuperDrawable(context: Context, iconId: Int, width: Int, tint: Boolean, tintColor: Int): SuperDrawable {
|
fun getIconSuperDrawable(context: Context, iconId: Int, width: Int, tint: Boolean, tintColor: Int): SuperDrawable {
|
||||||
return SuperDrawable(getIconDrawable(context.resources, iconId, width, tint, tintColor))
|
return SuperDrawable(getIconDrawable(context.resources, iconId, width, tint, tintColor), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,7 +236,7 @@ class IconDrawableFactory {
|
|||||||
/**
|
/**
|
||||||
* Utility class to prevent a custom icon to be tint
|
* Utility class to prevent a custom icon to be tint
|
||||||
*/
|
*/
|
||||||
class SuperDrawable(var drawable: Drawable, var custom: Boolean = false)
|
class SuperDrawable(var drawable: Drawable, var tintable: Boolean = false)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@@ -231,35 +248,64 @@ class IconDrawableFactory {
|
|||||||
/**
|
/**
|
||||||
* Assign a default database icon to an ImageView and tint it with [tintColor] if needed
|
* Assign a default database icon to an ImageView and tint it with [tintColor] if needed
|
||||||
*/
|
*/
|
||||||
fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory, tintColor: Int = Color.WHITE) {
|
fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory,
|
||||||
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
|
tintColor: Int = Color.WHITE) {
|
||||||
|
try {
|
||||||
iconFactory.assignDrawableToImageView(
|
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
|
||||||
iconFactory.getIconSuperDrawable(context,
|
iconFactory.assignDrawableToImageView(
|
||||||
selectedIconPack.defaultIconId,
|
iconFactory.getIconSuperDrawable(context,
|
||||||
width,
|
selectedIconPack.defaultIconId,
|
||||||
selectedIconPack.tintable(),
|
width,
|
||||||
tintColor),
|
selectedIconPack.tintable(),
|
||||||
this,
|
tintColor),
|
||||||
selectedIconPack.tintable(),
|
this,
|
||||||
tintColor)
|
selectedIconPack.tintable(),
|
||||||
|
tintColor)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(ImageView::class.java.name, "Unable to assign icon in image view", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign a database [icon] to an ImageView and tint it with [tintColor] if needed
|
* Assign a database [icon] to an ImageView and tint it with [tintColor] if needed
|
||||||
*/
|
*/
|
||||||
fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory, icon: IconImage, tintColor: Int = Color.WHITE) {
|
fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory,
|
||||||
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
|
icon: IconImage,
|
||||||
|
tintColor: Int = Color.WHITE) {
|
||||||
iconFactory.assignDrawableToImageView(
|
try {
|
||||||
iconFactory.getIconSuperDrawable(context,
|
IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack ->
|
||||||
icon,
|
iconFactory.assignDrawableToImageView(
|
||||||
width,
|
iconFactory.getIconSuperDrawable(context,
|
||||||
true,
|
icon,
|
||||||
tintColor),
|
width,
|
||||||
this,
|
true,
|
||||||
selectedIconPack.tintable(),
|
tintColor),
|
||||||
tintColor)
|
this,
|
||||||
|
selectedIconPack.tintable(),
|
||||||
|
tintColor)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(ImageView::class.java.name, "Unable to assign icon in image view", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RemoteViews.assignDatabaseIcon(context: Context,
|
||||||
|
imageId: Int,
|
||||||
|
iconFactory: IconDrawableFactory,
|
||||||
|
icon: IconImage,
|
||||||
|
tintColor: Int = Color.BLACK) {
|
||||||
|
try {
|
||||||
|
iconFactory.assignDrawableToRemoteViews(
|
||||||
|
iconFactory.getIconSuperDrawable(context,
|
||||||
|
icon,
|
||||||
|
24,
|
||||||
|
true,
|
||||||
|
tintColor),
|
||||||
|
this,
|
||||||
|
imageId,
|
||||||
|
tintColor)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(RemoteViews::class.java.name, "Unable to assign icon in remote view", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
|||||||
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
|
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
|
||||||
override fun onItemClick(item: Field) {
|
override fun onItemClick(item: Field) {
|
||||||
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
|
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
|
||||||
goNextAutomatically()
|
actionTabAutomatically()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
|
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
|
||||||
@@ -225,19 +225,19 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
|||||||
if (entryInfoKey != null) {
|
if (entryInfoKey != null) {
|
||||||
currentInputConnection.commitText(entryInfoKey!!.username, 1)
|
currentInputConnection.commitText(entryInfoKey!!.username, 1)
|
||||||
}
|
}
|
||||||
goNextAutomatically()
|
actionTabAutomatically()
|
||||||
}
|
}
|
||||||
KEY_PASSWORD -> {
|
KEY_PASSWORD -> {
|
||||||
if (entryInfoKey != null) {
|
if (entryInfoKey != null) {
|
||||||
currentInputConnection.commitText(entryInfoKey!!.password, 1)
|
currentInputConnection.commitText(entryInfoKey!!.password, 1)
|
||||||
}
|
}
|
||||||
goNextAutomatically()
|
actionGoAutomatically()
|
||||||
}
|
}
|
||||||
KEY_URL -> {
|
KEY_URL -> {
|
||||||
if (entryInfoKey != null) {
|
if (entryInfoKey != null) {
|
||||||
currentInputConnection.commitText(entryInfoKey!!.url, 1)
|
currentInputConnection.commitText(entryInfoKey!!.url, 1)
|
||||||
}
|
}
|
||||||
goNextAutomatically()
|
actionTabAutomatically()
|
||||||
}
|
}
|
||||||
KEY_FIELDS -> {
|
KEY_FIELDS -> {
|
||||||
if (entryInfoKey != null) {
|
if (entryInfoKey != null) {
|
||||||
@@ -250,10 +250,15 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
|||||||
}
|
}
|
||||||
Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0)
|
Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0)
|
||||||
Keyboard.KEYCODE_DONE -> inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
|
Keyboard.KEYCODE_DONE -> inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
|
||||||
}// TODO Unlock key
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun goNextAutomatically() {
|
private fun actionTabAutomatically() {
|
||||||
|
if (PreferencesUtil.isAutoGoActionEnable(this))
|
||||||
|
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun actionGoAutomatically() {
|
||||||
if (PreferencesUtil.isAutoGoActionEnable(this))
|
if (PreferencesUtil.isAutoGoActionEnable(this))
|
||||||
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
|
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package com.kunzisoft.keepass.model
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -29,6 +30,7 @@ class EntryInfo : Parcelable {
|
|||||||
|
|
||||||
var id: String = ""
|
var id: String = ""
|
||||||
var title: String = ""
|
var title: String = ""
|
||||||
|
var icon: IconImage? = null
|
||||||
var username: String = ""
|
var username: String = ""
|
||||||
var password: String = ""
|
var password: String = ""
|
||||||
var url: String = ""
|
var url: String = ""
|
||||||
@@ -41,6 +43,7 @@ class EntryInfo : Parcelable {
|
|||||||
private constructor(parcel: Parcel) {
|
private constructor(parcel: Parcel) {
|
||||||
id = parcel.readString() ?: id
|
id = parcel.readString() ?: id
|
||||||
title = parcel.readString() ?: title
|
title = parcel.readString() ?: title
|
||||||
|
icon = parcel.readParcelable(IconImage::class.java.classLoader)
|
||||||
username = parcel.readString() ?: username
|
username = parcel.readString() ?: username
|
||||||
password = parcel.readString() ?: password
|
password = parcel.readString() ?: password
|
||||||
url = parcel.readString() ?: url
|
url = parcel.readString() ?: url
|
||||||
@@ -56,6 +59,7 @@ class EntryInfo : Parcelable {
|
|||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
parcel.writeString(id)
|
parcel.writeString(id)
|
||||||
parcel.writeString(title)
|
parcel.writeString(title)
|
||||||
|
parcel.writeParcelable(icon, flags)
|
||||||
parcel.writeString(username)
|
parcel.writeString(username)
|
||||||
parcel.writeString(password)
|
parcel.writeString(password)
|
||||||
parcel.writeString(url)
|
parcel.writeString(url)
|
||||||
|
|||||||
42
app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt
Normal file
42
app/src/main/java/com/kunzisoft/keepass/model/SearchInfo.kt
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package com.kunzisoft.keepass.model
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
|
||||||
|
class SearchInfo : Parcelable {
|
||||||
|
|
||||||
|
var applicationId: String? = null
|
||||||
|
var webDomain: String? = null
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
|
||||||
|
private constructor(parcel: Parcel) {
|
||||||
|
val readAppId = parcel.readString()
|
||||||
|
applicationId = if (readAppId.isNullOrEmpty()) null else readAppId
|
||||||
|
val readDomain = parcel.readString()
|
||||||
|
webDomain = if (readDomain.isNullOrEmpty()) null else readDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeString(applicationId ?: "")
|
||||||
|
parcel.writeString(webDomain ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.Creator<SearchInfo> = object : Parcelable.Creator<SearchInfo> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): SearchInfo {
|
||||||
|
return SearchInfo(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<SearchInfo?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,10 +21,7 @@ package com.kunzisoft.keepass.notifications
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.AsyncTask
|
import android.os.*
|
||||||
import android.os.Binder
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.IBinder
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||||
import com.kunzisoft.keepass.database.action.*
|
import com.kunzisoft.keepass.database.action.*
|
||||||
@@ -42,6 +39,7 @@ import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
|||||||
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
||||||
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdater {
|
class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdater {
|
||||||
@@ -63,6 +61,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
|
|||||||
|
|
||||||
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||||
mActionTaskListeners.add(actionTaskListener)
|
mActionTaskListeners.add(actionTaskListener)
|
||||||
|
// To prevent task dialog to be unbound before the display
|
||||||
|
actionRunnableAsyncTask?.allowFinishTask?.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
|
||||||
@@ -571,6 +571,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
|
|||||||
private val onPostExecute: (result: ActionRunnable.Result) -> Unit)
|
private val onPostExecute: (result: ActionRunnable.Result) -> Unit)
|
||||||
: AsyncTask<((ProgressTaskUpdater?) -> ActionRunnable), Void, ActionRunnable.Result>() {
|
: AsyncTask<((ProgressTaskUpdater?) -> ActionRunnable), Void, ActionRunnable.Result>() {
|
||||||
|
|
||||||
|
var allowFinishTask = AtomicBoolean(false)
|
||||||
|
|
||||||
override fun onPreExecute() {
|
override fun onPreExecute() {
|
||||||
super.onPreExecute()
|
super.onPreExecute()
|
||||||
onPreExecute.invoke()
|
onPreExecute.invoke()
|
||||||
@@ -578,14 +580,16 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
|
|||||||
|
|
||||||
override fun doInBackground(vararg actionRunnables: ((ProgressTaskUpdater?)-> ActionRunnable)?): ActionRunnable.Result {
|
override fun doInBackground(vararg actionRunnables: ((ProgressTaskUpdater?)-> ActionRunnable)?): ActionRunnable.Result {
|
||||||
var resultTask = ActionRunnable.Result(false)
|
var resultTask = ActionRunnable.Result(false)
|
||||||
// Without that, bind listeners don't work properly (I don't know why?)
|
|
||||||
Thread.sleep(500)
|
|
||||||
actionRunnables.forEach {
|
actionRunnables.forEach {
|
||||||
it?.invoke(progressTaskUpdater)?.apply {
|
it?.invoke(progressTaskUpdater)?.apply {
|
||||||
run()
|
run()
|
||||||
resultTask = result
|
resultTask = result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Additional wait if the dialog take time to show
|
||||||
|
while(!allowFinishTask.get()) {
|
||||||
|
Thread.sleep(250)
|
||||||
|
}
|
||||||
return resultTask
|
return resultTask
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,15 +53,12 @@ abstract class NotificationService : Service() {
|
|||||||
protected fun buildNewNotification(): NotificationCompat.Builder {
|
protected fun buildNewNotification(): NotificationCompat.Builder {
|
||||||
return NotificationCompat.Builder(this, CHANNEL_ID_KEEPASS)
|
return NotificationCompat.Builder(this, CHANNEL_ID_KEEPASS)
|
||||||
.setColor(colorNotificationAccent)
|
.setColor(colorNotificationAccent)
|
||||||
//TODO .setGroup(GROUP_KEEPASS)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO only for > lollipop
|
|
||||||
protected fun buildSummaryNotification(): NotificationCompat.Builder {
|
protected fun buildSummaryNotification(): NotificationCompat.Builder {
|
||||||
return buildNewNotification().apply {
|
return buildNewNotification().apply {
|
||||||
// TODO Ic setSmallIcon(R.drawable.notification_ic_data_usage_24dp)
|
|
||||||
setGroupSummary(true)
|
setGroupSummary(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +72,5 @@ abstract class NotificationService : Service() {
|
|||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL_ID_KEEPASS = "com.kunzisoft.keepass.notification.channel"
|
const val CHANNEL_ID_KEEPASS = "com.kunzisoft.keepass.notification.channel"
|
||||||
const val CHANNEL_NAME_KEEPASS = "KeePassDX notification"
|
const val CHANNEL_NAME_KEEPASS = "KeePassDX notification"
|
||||||
const val GROUP_KEEPASS = "GROUP_KEEPASS"
|
|
||||||
const val SUMMARY_ID = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ object OtpEntryFields {
|
|||||||
// [^&=\s]+=[^&=\s]+(&[^&=\s]+=[^&=\s]+)*
|
// [^&=\s]+=[^&=\s]+(&[^&=\s]+=[^&=\s]+)*
|
||||||
private const val validKeyValue = "[^&=\\s]+"
|
private const val validKeyValue = "[^&=\\s]+"
|
||||||
private const val validKeyValuePair = "$validKeyValue=$validKeyValue"
|
private const val validKeyValuePair = "$validKeyValue=$validKeyValue"
|
||||||
private const val validKeyValueRegex = "$validKeyValuePair&($validKeyValuePair)*"
|
private const val validKeyValueRegex = "$validKeyValuePair(&$validKeyValuePair)*"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse fields of an entry to retrieve an OtpElement
|
* Parse fields of an entry to retrieve an OtpElement
|
||||||
@@ -243,21 +243,18 @@ object OtpEntryFields {
|
|||||||
val plainText = getField(OTP_FIELD)
|
val plainText = getField(OTP_FIELD)
|
||||||
if (plainText != null && plainText.isNotEmpty()) {
|
if (plainText != null && plainText.isNotEmpty()) {
|
||||||
if (Pattern.matches(validKeyValueRegex, plainText)) {
|
if (Pattern.matches(validKeyValueRegex, plainText)) {
|
||||||
try {
|
return try {
|
||||||
// KeeOtp string format
|
// KeeOtp string format
|
||||||
val query = breakDownKeyValuePairs(plainText)
|
val query = breakDownKeyValuePairs(plainText)
|
||||||
|
|
||||||
var secretString = query[SEED_KEY]
|
otpElement.setBase32Secret(query[SEED_KEY] ?: "")
|
||||||
if (secretString == null)
|
otpElement.digits = query[DIGITS_KEY]?.toIntOrNull() ?: OTP_DEFAULT_DIGITS
|
||||||
secretString = ""
|
otpElement.period = query[STEP_KEY]?.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
|
||||||
otpElement.setBase32Secret(secretString)
|
|
||||||
otpElement.digits = query[DIGITS_KEY]?.toIntOrNull() ?: OTP_DEFAULT_DIGITS
|
|
||||||
otpElement.period = query[STEP_KEY]?.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
|
|
||||||
|
|
||||||
otpElement.type = OtpType.TOTP
|
otpElement.type = OtpType.TOTP
|
||||||
return true
|
true
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Malformed
|
// Malformed
|
||||||
|
|||||||
@@ -65,14 +65,14 @@ class PasswordGenerator(private val resources: Resources) {
|
|||||||
|
|
||||||
// No option has been checked
|
// No option has been checked
|
||||||
if (!upperCase
|
if (!upperCase
|
||||||
&& !lowerCase
|
&& !lowerCase
|
||||||
&& !digits
|
&& !digits
|
||||||
&& !minus
|
&& !minus
|
||||||
&& !underline
|
&& !underline
|
||||||
&& !space
|
&& !space
|
||||||
&& !specials
|
&& !specials
|
||||||
&& !brackets
|
&& !brackets
|
||||||
&& !extended) {
|
&& !extended) {
|
||||||
throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type))
|
throw IllegalArgumentException(resources.getString(R.string.error_pass_gen_type))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,35 +114,27 @@ class PasswordGenerator(private val resources: Resources) {
|
|||||||
if (upperCase) {
|
if (upperCase) {
|
||||||
charSet.append(UPPERCASE_CHARS)
|
charSet.append(UPPERCASE_CHARS)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lowerCase) {
|
if (lowerCase) {
|
||||||
charSet.append(LOWERCASE_CHARS)
|
charSet.append(LOWERCASE_CHARS)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (digits) {
|
if (digits) {
|
||||||
charSet.append(DIGIT_CHARS)
|
charSet.append(DIGIT_CHARS)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minus) {
|
if (minus) {
|
||||||
charSet.append(MINUS_CHAR)
|
charSet.append(MINUS_CHAR)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (underline) {
|
if (underline) {
|
||||||
charSet.append(UNDERLINE_CHAR)
|
charSet.append(UNDERLINE_CHAR)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (space) {
|
if (space) {
|
||||||
charSet.append(SPACE_CHAR)
|
charSet.append(SPACE_CHAR)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (specials) {
|
if (specials) {
|
||||||
charSet.append(SPECIAL_CHARS)
|
charSet.append(SPECIAL_CHARS)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (brackets) {
|
if (brackets) {
|
||||||
charSet.append(BRACKET_CHARS)
|
charSet.append(BRACKET_CHARS)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extended) {
|
if (extended) {
|
||||||
charSet.append(extendedChars())
|
charSet.append(extendedChars())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDX.
|
||||||
|
*
|
||||||
|
* KeePassDX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.settings
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
|
|
||||||
|
class AutofillSettingsActivity : StylishActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_toolbar)
|
||||||
|
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||||
|
toolbar.setTitle(R.string.autofill_preference_title)
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.fragment_container, AutofillSettingsFragment())
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
android.R.id.home -> onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePassDX.
|
* This file is part of KeePassDX.
|
||||||
*
|
*
|
||||||
@@ -20,16 +20,14 @@
|
|||||||
package com.kunzisoft.keepass.settings
|
package com.kunzisoft.keepass.settings
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
|
||||||
class SettingsAutofillActivity : SettingsActivity() {
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
class AutofillSettingsFragment : PreferenceFragmentCompat() {
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
mTimeoutEnable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun retrieveMainFragment(): Fragment {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
return NestedSettingsFragment.newInstance(NestedSettingsFragment.Screen.FORM_FILLING)
|
// Load the preferences from an XML resource
|
||||||
|
setPreferencesFromResource(R.xml.preferences_autofill, rootKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ import android.view.MenuItem
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
import com.kunzisoft.keepass.activities.stylish.StylishActivity
|
||||||
|
|
||||||
class MagikIMESettings : StylishActivity() {
|
class MagikeyboardSettingsActivity : StylishActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -39,7 +39,7 @@ class MagikIMESettings : StylishActivity() {
|
|||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragment_container, MagikIMESettingsFragment())
|
.replace(R.id.fragment_container, MagikeyboardSettingsFragment())
|
||||||
.commit()
|
.commit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ import androidx.preference.PreferenceFragmentCompat
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
|
|
||||||
class MagikIMESettingsFragment : PreferenceFragmentCompat() {
|
class MagikeyboardSettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
// Load the preferences from an XML resource
|
// Load the preferences from an XML resource
|
||||||
@@ -24,7 +24,6 @@ import android.os.Bundle
|
|||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
|
|
||||||
class MainPreferenceFragment : PreferenceFragmentCompat() {
|
class MainPreferenceFragment : PreferenceFragmentCompat() {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
override fun onPreferenceClick(preference: Preference): Boolean {
|
override fun onPreferenceClick(preference: Preference): Boolean {
|
||||||
if ((preference as SwitchPreference).isChecked) {
|
if ((preference as SwitchPreference).isChecked) {
|
||||||
try {
|
try {
|
||||||
startEnableService()
|
enableService()
|
||||||
} catch (e: ActivityNotFoundException) {
|
} catch (e: ActivityNotFoundException) {
|
||||||
val error = getString(R.string.error_autofill_enable_service)
|
val error = getString(R.string.error_autofill_enable_service)
|
||||||
preference.isChecked = false
|
preference.isChecked = false
|
||||||
@@ -124,21 +124,20 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) {
|
if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) {
|
||||||
autofillManager.disableAutofillServices()
|
autofillManager.disableAutofillServices()
|
||||||
} else {
|
} else {
|
||||||
Log.d(javaClass.name, "Sample service already disabled.")
|
Log.d(javaClass.name, "Autofill service already disabled.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@Throws(ActivityNotFoundException::class)
|
@Throws(ActivityNotFoundException::class)
|
||||||
private fun startEnableService() {
|
private fun enableService() {
|
||||||
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
|
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
|
||||||
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
|
val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE)
|
||||||
// TODO Autofill
|
intent.data = Uri.parse("package:com.kunzisoft.keepass.autofill.KeeAutofillService")
|
||||||
intent.data = Uri.parse("package:com.example.android.autofill.service")
|
Log.d(javaClass.name, "Autofill enable service: intent=$intent")
|
||||||
Log.d(javaClass.name, "enableService(): intent=$intent")
|
|
||||||
startActivityForResult(intent, REQUEST_CODE_AUTOFILL)
|
startActivityForResult(intent, REQUEST_CODE_AUTOFILL)
|
||||||
} else {
|
} else {
|
||||||
Log.d(javaClass.name, "Sample service already enabled.")
|
Log.d(javaClass.name, "Autofill service already enabled.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +147,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener {
|
findPreference<Preference>(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener {
|
||||||
UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url)
|
UriUtil.gotoUrl(requireContext(), R.string.magic_keyboard_explanation_url)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,17 +159,22 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener {
|
findPreference<Preference>(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener {
|
||||||
startActivity(Intent(context, MagikIMESettings::class.java))
|
startActivity(Intent(context, MagikeyboardSettingsActivity::class.java))
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
|
|
||||||
UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url)
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener {
|
findPreference<Preference>(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener {
|
||||||
UriUtil.gotoUrl(context!!, R.string.autofill_explanation_url)
|
UriUtil.gotoUrl(requireContext(), R.string.autofill_explanation_url)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
findPreference<Preference>(getString(R.string.settings_autofill_key))?.setOnPreferenceClickListener {
|
||||||
|
startActivity(Intent(context, AutofillSettingsActivity::class.java))
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
findPreference<Preference>(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener {
|
||||||
|
UriUtil.gotoUrl(requireContext(), R.string.clipboard_explanation_url)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +190,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
"\n\n" +
|
"\n\n" +
|
||||||
getString(R.string.clipboard_warning)
|
getString(R.string.clipboard_warning)
|
||||||
AlertDialog
|
AlertDialog
|
||||||
.Builder(context!!)
|
.Builder(requireContext())
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.create()
|
.create()
|
||||||
.apply {
|
.apply {
|
||||||
@@ -223,11 +227,9 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
biometricUnlockEnablePreference?.apply {
|
biometricUnlockEnablePreference?.apply {
|
||||||
isChecked = false
|
isChecked = false
|
||||||
setOnPreferenceClickListener { preference ->
|
setOnPreferenceClickListener { preference ->
|
||||||
fragmentManager?.let { fragmentManager ->
|
(preference as SwitchPreference).isChecked = false
|
||||||
(preference as SwitchPreference).isChecked = false
|
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
||||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
.show(parentFragmentManager, "unavailableFeatureDialog")
|
||||||
.show(fragmentManager, "unavailableFeatureDialog")
|
|
||||||
}
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +276,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener {
|
findPreference<Preference>(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener {
|
||||||
UriUtil.gotoUrl(context!!, R.string.advanced_unlock_explanation_url)
|
UriUtil.gotoUrl(requireContext(), R.string.advanced_unlock_explanation_url)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,13 +291,11 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
|
findPreference<ListPreference>(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
var styleEnabled = true
|
var styleEnabled = true
|
||||||
val styleIdString = newValue as String
|
val styleIdString = newValue as String
|
||||||
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!))
|
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(activity))
|
||||||
for (themeIdDisabled in BuildConfig.STYLES_DISABLED) {
|
for (themeIdDisabled in BuildConfig.STYLES_DISABLED) {
|
||||||
if (themeIdDisabled == styleIdString) {
|
if (themeIdDisabled == styleIdString) {
|
||||||
styleEnabled = false
|
styleEnabled = false
|
||||||
fragmentManager?.let { fragmentManager ->
|
ProFeatureDialogFragment().show(parentFragmentManager, "pro_feature_dialog")
|
||||||
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (styleEnabled) {
|
if (styleEnabled) {
|
||||||
@@ -308,13 +308,11 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue ->
|
findPreference<IconPackListPreference>(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
var iconPackEnabled = true
|
var iconPackEnabled = true
|
||||||
val iconPackId = newValue as String
|
val iconPackId = newValue as String
|
||||||
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!))
|
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(activity))
|
||||||
for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) {
|
for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) {
|
||||||
if (iconPackIdDisabled == iconPackId) {
|
if (iconPackIdDisabled == iconPackId) {
|
||||||
iconPackEnabled = false
|
iconPackEnabled = false
|
||||||
fragmentManager?.let { fragmentManager ->
|
ProFeatureDialogFragment().show(parentFragmentManager, "pro_feature_dialog")
|
||||||
ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (iconPackEnabled) {
|
if (iconPackEnabled) {
|
||||||
@@ -326,7 +324,7 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
|||||||
findPreference<Preference>(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener {
|
findPreference<Preference>(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener {
|
||||||
// To allow only one toast
|
// To allow only one toast
|
||||||
if (mCount == 0) {
|
if (mCount == 0) {
|
||||||
val sharedPreferences = Education.getEducationSharedPreferences(context!!)
|
val sharedPreferences = Education.getEducationSharedPreferences(activity)
|
||||||
val editor = sharedPreferences.edit()
|
val editor = sharedPreferences.edit()
|
||||||
for (resourceId in Education.educationResourcesKeys) {
|
for (resourceId in Education.educationResourcesKeys) {
|
||||||
editor.putBoolean(getString(resourceId), false)
|
editor.putBoolean(getString(resourceId), false)
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import com.kunzisoft.androidclearchroma.ChromaUtil
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||||
import com.kunzisoft.keepass.activities.lock.lock
|
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||||
@@ -252,10 +251,8 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
|
findPreference<Preference>(getString(R.string.settings_database_change_credentials_key))?.apply {
|
||||||
isEnabled = if (!mDatabaseReadOnly) {
|
isEnabled = if (!mDatabaseReadOnly) {
|
||||||
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
fragmentManager?.let { fragmentManager ->
|
AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey)
|
||||||
AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey)
|
.show(parentFragmentManager, "passwordDialog")
|
||||||
.show(fragmentManager, "passwordDialog")
|
|
||||||
}
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -282,7 +279,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// To reassign color listener after orientation change
|
// To reassign color listener after orientation change
|
||||||
val chromaDialog = fragmentManager?.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
|
val chromaDialog = parentFragmentManager.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat?
|
||||||
chromaDialog?.onColorSelectedListener = colorSelectedListener
|
chromaDialog?.onColorSelectedListener = colorSelectedListener
|
||||||
} catch (e: Exception) {}
|
} catch (e: Exception) {}
|
||||||
|
|
||||||
@@ -445,8 +442,8 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
mMemoryPref?.summary = memoryToShow.toString()
|
mMemoryPref?.summary = memoryToShow.toString()
|
||||||
}
|
}
|
||||||
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_PARALLELISM_TASK -> {
|
DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_PARALLELISM_TASK -> {
|
||||||
val oldParallelism = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
val oldParallelism = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)
|
||||||
val newParallelism = data.getInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
|
val newParallelism = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)
|
||||||
val parallelismToShow =
|
val parallelismToShow =
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
newParallelism
|
newParallelism
|
||||||
@@ -465,68 +462,64 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
|
|
||||||
var otherDialogFragment = false
|
var otherDialogFragment = false
|
||||||
|
|
||||||
fragmentManager?.let { fragmentManager ->
|
var dialogFragment: DialogFragment? = null
|
||||||
preference?.let { preference ->
|
// Main Preferences
|
||||||
var dialogFragment: DialogFragment? = null
|
when (preference?.key) {
|
||||||
when {
|
getString(R.string.database_name_key) -> {
|
||||||
// Main Preferences
|
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
preference.key == getString(R.string.database_name_key) -> {
|
}
|
||||||
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
getString(R.string.database_description_key) -> {
|
||||||
}
|
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
preference.key == getString(R.string.database_description_key) -> {
|
}
|
||||||
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
getString(R.string.database_default_username_key) -> {
|
||||||
}
|
dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
preference.key == getString(R.string.database_default_username_key) -> {
|
}
|
||||||
dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key)
|
getString(R.string.database_custom_color_key) -> {
|
||||||
}
|
dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply {
|
||||||
preference.key == getString(R.string.database_custom_color_key) -> {
|
onColorSelectedListener = colorSelectedListener
|
||||||
dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply {
|
|
||||||
onColorSelectedListener = colorSelectedListener
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference.key == getString(R.string.database_data_compression_key) -> {
|
|
||||||
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
}
|
|
||||||
preference.key == getString(R.string.max_history_items_key) -> {
|
|
||||||
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
}
|
|
||||||
preference.key == getString(R.string.max_history_size_key) -> {
|
|
||||||
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Security
|
|
||||||
preference.key == getString(R.string.encryption_algorithm_key) -> {
|
|
||||||
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
}
|
|
||||||
preference.key == getString(R.string.key_derivation_function_key) -> {
|
|
||||||
val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
// Add other prefs to manage
|
|
||||||
keyDerivationDialogFragment.setRoundPreference(mRoundPref)
|
|
||||||
keyDerivationDialogFragment.setMemoryPreference(mMemoryPref)
|
|
||||||
keyDerivationDialogFragment.setParallelismPreference(mParallelismPref)
|
|
||||||
dialogFragment = keyDerivationDialogFragment
|
|
||||||
}
|
|
||||||
preference.key == getString(R.string.transform_rounds_key) -> {
|
|
||||||
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
}
|
|
||||||
preference.key == getString(R.string.memory_usage_key) -> {
|
|
||||||
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
}
|
|
||||||
preference.key == getString(R.string.parallelism_key) -> {
|
|
||||||
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
|
|
||||||
}
|
|
||||||
else -> otherDialogFragment = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialogFragment != null && !mDatabaseReadOnly) {
|
|
||||||
dialogFragment.setTargetFragment(this, 0)
|
|
||||||
dialogFragment.show(fragmentManager, TAG_PREF_FRAGMENT)
|
|
||||||
}
|
|
||||||
// Could not be handled here. Try with the super method.
|
|
||||||
else if (otherDialogFragment) {
|
|
||||||
super.onDisplayPreferenceDialog(preference)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
getString(R.string.database_data_compression_key) -> {
|
||||||
|
dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
|
getString(R.string.max_history_items_key) -> {
|
||||||
|
dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
|
getString(R.string.max_history_size_key) -> {
|
||||||
|
dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security
|
||||||
|
getString(R.string.encryption_algorithm_key) -> {
|
||||||
|
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
|
getString(R.string.key_derivation_function_key) -> {
|
||||||
|
val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
// Add other prefs to manage
|
||||||
|
keyDerivationDialogFragment.setRoundPreference(mRoundPref)
|
||||||
|
keyDerivationDialogFragment.setMemoryPreference(mMemoryPref)
|
||||||
|
keyDerivationDialogFragment.setParallelismPreference(mParallelismPref)
|
||||||
|
dialogFragment = keyDerivationDialogFragment
|
||||||
|
}
|
||||||
|
getString(R.string.transform_rounds_key) -> {
|
||||||
|
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
|
getString(R.string.memory_usage_key) -> {
|
||||||
|
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
|
getString(R.string.parallelism_key) -> {
|
||||||
|
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key)
|
||||||
|
}
|
||||||
|
else -> otherDialogFragment = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialogFragment != null && !mDatabaseReadOnly) {
|
||||||
|
dialogFragment.setTargetFragment(this, 0)
|
||||||
|
dialogFragment.show(parentFragmentManager, TAG_PREF_FRAGMENT)
|
||||||
|
}
|
||||||
|
// Could not be handled here. Try with the super method.
|
||||||
|
else if (otherDialogFragment) {
|
||||||
|
super.onDisplayPreferenceDialog(preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,14 +544,10 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
|
|
||||||
val settingActivity = activity as SettingsActivity?
|
val settingActivity = activity as SettingsActivity?
|
||||||
|
|
||||||
when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.menu_lock -> {
|
|
||||||
settingActivity?.lock()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
R.id.menu_save_database -> {
|
R.id.menu_save_database -> {
|
||||||
settingActivity?.mProgressDialogThread?.startDatabaseSave(!mDatabaseReadOnly)
|
settingActivity?.mProgressDialogThread?.startDatabaseSave(!mDatabaseReadOnly)
|
||||||
return true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
@@ -566,7 +555,7 @@ class NestedDatabaseSettingsFragment : NestedSettingsFragment() {
|
|||||||
settingActivity?.let {
|
settingActivity?.let {
|
||||||
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true)
|
MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true)
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item)
|
super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,12 +36,10 @@ abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
onCreateScreenPreference(
|
||||||
var key = 0
|
Screen.values()[requireArguments().getInt(TAG_KEY)],
|
||||||
if (arguments != null)
|
savedInstanceState,
|
||||||
key = arguments!!.getInt(TAG_KEY)
|
rootKey)
|
||||||
|
|
||||||
onCreateScreenPreference(Screen.values()[key], savedInstanceState, rootKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?)
|
abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?)
|
||||||
@@ -51,13 +49,11 @@ abstract class NestedSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
|
|
||||||
protected fun preferenceInDevelopment(preferenceInDev: Preference) {
|
protected fun preferenceInDevelopment(preferenceInDev: Preference) {
|
||||||
preferenceInDev.setOnPreferenceClickListener { preference ->
|
preferenceInDev.setOnPreferenceClickListener { preference ->
|
||||||
fragmentManager?.let { fragmentManager ->
|
try { // don't check if we can
|
||||||
try { // don't check if we can
|
(preference as SwitchPreference).isChecked = false
|
||||||
(preference as SwitchPreference).isChecked = false
|
} catch (ignored: Exception) {
|
||||||
} catch (ignored: Exception) {
|
|
||||||
}
|
|
||||||
UnderDevelopmentFeatureDialogFragment().show(fragmentManager, "underDevFeatureDialog")
|
|
||||||
}
|
}
|
||||||
|
UnderDevelopmentFeatureDialogFragment().show(parentFragmentManager, "underDevFeatureDialog")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,6 +199,12 @@ object PreferencesUtil {
|
|||||||
context.resources.getBoolean(R.bool.lock_database_back_root_default))
|
context.resources.getBoolean(R.bool.lock_database_back_root_default))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showLockDatabaseButton(context: Context): Boolean {
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
return prefs.getBoolean(context.getString(R.string.lock_database_show_button_key),
|
||||||
|
context.resources.getBoolean(R.bool.lock_database_show_button_default))
|
||||||
|
}
|
||||||
|
|
||||||
fun isAutoSaveDatabaseEnabled(context: Context): Boolean {
|
fun isAutoSaveDatabaseEnabled(context: Context): Boolean {
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
return prefs.getBoolean(context.getString(R.string.enable_auto_save_database_key),
|
return prefs.getBoolean(context.getString(R.string.enable_auto_save_database_key),
|
||||||
@@ -348,4 +354,10 @@ object PreferencesUtil {
|
|||||||
return prefs.getBoolean(context.getString(R.string.keyboard_key_sound_key),
|
return prefs.getBoolean(context.getString(R.string.keyboard_key_sound_key),
|
||||||
context.resources.getBoolean(R.bool.keyboard_key_sound_default))
|
context.resources.getBoolean(R.bool.keyboard_key_sound_default))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isAutofillAutoSearchEnable(context: Context): Boolean {
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
return prefs.getBoolean(context.getString(R.string.autofill_auto_search_key),
|
||||||
|
context.resources.getBoolean(R.bool.autofill_auto_search_default))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user