mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
223 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
774dddca54 | ||
|
|
de980d030a | ||
|
|
0e859646fe | ||
|
|
059c7b7713 | ||
|
|
5fb7bf71c8 | ||
|
|
8b0133ff7f | ||
|
|
8d834946b8 | ||
|
|
2f646395d4 | ||
|
|
f6e79ba37b | ||
|
|
e633c7a861 | ||
|
|
dc02a8d78c | ||
|
|
baa9b88512 | ||
|
|
c522e87da8 | ||
|
|
ef5ebf2c15 | ||
|
|
4b147e770c | ||
|
|
157a5c0b05 | ||
|
|
f2288b0c64 | ||
|
|
d8506450aa | ||
|
|
f9b085e73f | ||
|
|
388cf6a91b | ||
|
|
9e6e77b363 | ||
|
|
ec33ca8173 | ||
|
|
6be0457947 | ||
|
|
f3b84aa845 | ||
|
|
bd0b5b0954 | ||
|
|
7dc93604ad | ||
|
|
0ab22698a6 | ||
|
|
c885ce7aaf | ||
|
|
92d1a7b901 | ||
|
|
6119054b45 | ||
|
|
e7aed72398 | ||
|
|
cee7fa50f5 | ||
|
|
39a38bb223 | ||
|
|
7159a993db | ||
|
|
23933e80e3 | ||
|
|
abc971b5cc | ||
|
|
7dedcc8a21 | ||
|
|
10d46e5dee | ||
|
|
139f7eb36d | ||
|
|
1ddfa894b6 | ||
|
|
d1695ab8c2 | ||
|
|
f27979e729 | ||
|
|
6e61e8172a | ||
|
|
21890894ae | ||
|
|
1feecd559d | ||
|
|
6ea4afe75b | ||
|
|
fd96f6367d | ||
|
|
8ce183c4c9 | ||
|
|
407a1db101 | ||
|
|
622d096e31 | ||
|
|
bf27fb1f89 | ||
|
|
860b9055c5 | ||
|
|
b3ae3a4148 | ||
|
|
0abd7d5762 | ||
|
|
0aac2bc55b | ||
|
|
fa08dc5cfb | ||
|
|
8d18970b4c | ||
|
|
173f5ce979 | ||
|
|
2e7088310a | ||
|
|
c75d99030c | ||
|
|
e4ba1d9bae | ||
|
|
e2886c342a | ||
|
|
e600d8a56c | ||
|
|
caeb305475 | ||
|
|
3d3a9d9bad | ||
|
|
5499ad5b94 | ||
|
|
0e29cd0cee | ||
|
|
24fb1b1a8f | ||
|
|
03fb4cbf0c | ||
|
|
e909280d5b | ||
|
|
d41ddf60b4 | ||
|
|
1e01a74986 | ||
|
|
96a007aace | ||
|
|
9f23bb6129 | ||
|
|
b7e8559773 | ||
|
|
5b247575c8 | ||
|
|
eb0e5b478f | ||
|
|
08906ae1da | ||
|
|
395a5efecd | ||
|
|
0452dd14f6 | ||
|
|
3906df314d | ||
|
|
ce49aa2ebd | ||
|
|
f2cb062b1e | ||
|
|
f25819a940 | ||
|
|
3075a9f9f4 | ||
|
|
52f1a672c8 | ||
|
|
45785fde1c | ||
|
|
6a7649e1d7 | ||
|
|
8b3831eb2b | ||
|
|
73e7f4669c | ||
|
|
c9f7bbbd25 | ||
|
|
ee67238133 | ||
|
|
b425da8d0f | ||
|
|
754a7f70bc | ||
|
|
a857ffa987 | ||
|
|
391ce2ebba | ||
|
|
086723adf4 | ||
|
|
e993279c35 | ||
|
|
aa64310875 | ||
|
|
795baf2c01 | ||
|
|
68ac453100 | ||
|
|
79d1f512e5 | ||
|
|
e739211314 | ||
|
|
d3f6374bb4 | ||
|
|
5add632cbc | ||
|
|
d210d1bcce | ||
|
|
6d6422cd63 | ||
|
|
66e8b7702b | ||
|
|
b75502ad87 | ||
|
|
3fba96d11f | ||
|
|
3571905705 | ||
|
|
acf0e2a1cb | ||
|
|
9e7dcb0d7c | ||
|
|
3c261e3cf7 | ||
|
|
b6f324f399 | ||
|
|
f2459489fa | ||
|
|
f8691cf285 | ||
|
|
2e631d3c42 | ||
|
|
1044dca936 | ||
|
|
56c3f495d5 | ||
|
|
0f3036dd9c | ||
|
|
af445ef157 | ||
|
|
25eb09f11c | ||
|
|
16f255aeca | ||
|
|
d0b340837d | ||
|
|
893828ac44 | ||
|
|
a3ca03636a | ||
|
|
582ffe3f23 | ||
|
|
3caad2cceb | ||
|
|
618dcf014d | ||
|
|
d88e20bb56 | ||
|
|
8a8b2b027e | ||
|
|
41cb223099 | ||
|
|
b93ea5e662 | ||
|
|
31c35939fd | ||
|
|
20a35f4221 | ||
|
|
bc6aeb2e93 | ||
|
|
a561299809 | ||
|
|
76efb938ab | ||
|
|
56abf73eaf | ||
|
|
2bc068d65a | ||
|
|
f191259f37 | ||
|
|
1384c6661d | ||
|
|
c047621548 | ||
|
|
017aaf2e54 | ||
|
|
e4b2b930af | ||
|
|
2646c0f0ee | ||
|
|
4afadb779c | ||
|
|
ad6e4daa22 | ||
|
|
d5cd07fe76 | ||
|
|
364065ed51 | ||
|
|
b4f05d4da7 | ||
|
|
3d12a0e8e9 | ||
|
|
e29f3194f3 | ||
|
|
6bac86638b | ||
|
|
d6ee1cdf6e | ||
|
|
e9d0efaf93 | ||
|
|
85467fa15b | ||
|
|
84bb47aa53 | ||
|
|
75f245c7dc | ||
|
|
6c5be88432 | ||
|
|
590b22de69 | ||
|
|
4770269f6f | ||
|
|
823f591aa8 | ||
|
|
c88c489633 | ||
|
|
8afb58a044 | ||
|
|
428fa8a61b | ||
|
|
3ba5e1ee79 | ||
|
|
d46d0cb384 | ||
|
|
f841884557 | ||
|
|
d381ab5316 | ||
|
|
a3e8e7ae77 | ||
|
|
a405753827 | ||
|
|
8df74d2c4b | ||
|
|
cbe0ffe52a | ||
|
|
a5631a0476 | ||
|
|
e4bd704a53 | ||
|
|
81a53440bc | ||
|
|
3466de1990 | ||
|
|
253b053c2c | ||
|
|
928d012046 | ||
|
|
19367406c6 | ||
|
|
f4e3717dd3 | ||
|
|
c01e1d91c5 | ||
|
|
6983f9f0b6 | ||
|
|
c13cf1a86c | ||
|
|
2a3dafe07f | ||
|
|
0104d02442 | ||
|
|
8cef4fde82 | ||
|
|
3dc02516ea | ||
|
|
3614529fda | ||
|
|
80d4f06e56 | ||
|
|
9ff4f395b5 | ||
|
|
61b8fa116a | ||
|
|
22a3541b7b | ||
|
|
c57515fed5 | ||
|
|
eec6199413 | ||
|
|
fe48955b94 | ||
|
|
03047ae6dd | ||
|
|
856d4867b4 | ||
|
|
b1b1aa0e13 | ||
|
|
ab68472698 | ||
|
|
5ae4ee1411 | ||
|
|
f1989bac21 | ||
|
|
1b20188b98 | ||
|
|
140a79d18c | ||
|
|
a9610ced0e | ||
|
|
17faee7719 | ||
|
|
120ca1c02c | ||
|
|
ef2d1ebe4f | ||
|
|
b9c931c97f | ||
|
|
60dd963d7d | ||
|
|
c414fac815 | ||
|
|
95041d6a0c | ||
|
|
1fab9c3279 | ||
|
|
3b8661249e | ||
|
|
2fba831851 | ||
|
|
fd4ac14ab3 | ||
|
|
1d9b9e9bfa | ||
|
|
14b2277313 | ||
|
|
0f57bf5235 | ||
|
|
4bb7fae1e3 | ||
|
|
ec0b8ebc92 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -20,7 +20,7 @@ Steps to reproduce the behavior:
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
** Keepass Database **
|
||||
**KeePass Database**
|
||||
- Created with: [e.g Windows KeePass 2.42]
|
||||
- Version: [e.g. 2]
|
||||
- Location: [e.g. Remote file retrieved with GDrive app]
|
||||
|
||||
28
CHANGELOG
28
CHANGELOG
@@ -1,3 +1,31 @@
|
||||
KeePassDX(2.9.5)
|
||||
* Unlock database by device credentials (PIN/Password/Pattern) with Android M+ #102 #152 #811
|
||||
* Prevent auto switch back to previous keyboard if otp field exists #814
|
||||
* Fix timeout reset #817
|
||||
|
||||
KeePassDX(2.9.4)
|
||||
* Fix small bugs #812
|
||||
* Argon2ID implementation #791
|
||||
|
||||
KeePassDX(2.9.3)
|
||||
* Unlock database by device credentials (PIN/Password/Pattern) #779 #102
|
||||
* Advanced unlock with timeout #102 #437 #566
|
||||
* Remove default database parameter when the file is no longer accessible #803
|
||||
* Move OTP button to the first view level in Magikeyboard #587
|
||||
* Tooltips for Magikeyboard #586
|
||||
* Fix small bugs #805
|
||||
|
||||
KeePassDX(2.9.2)
|
||||
* Managing OTP links from QR applications #556
|
||||
* Prevent manual creation of existing field name #718
|
||||
* Harmonization of field names #789
|
||||
* Different channels for each type of notification #688
|
||||
* Fix OTP #780 #781
|
||||
* Fix same save shared info #783
|
||||
* Fix switch back to previous keyboard #782
|
||||
* Fix read only #792
|
||||
* Better UI #719 #534 #617 #793
|
||||
|
||||
KeePassDX(2.9.1)
|
||||
* Copy password from generator #697
|
||||
* Fix Magikeyboard not fully visible #772
|
||||
|
||||
202
LICENSES/LICENSE_APACHE
Normal file
202
LICENSES/LICENSE_APACHE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
7
LICENSES/LICENSE_BOUNCY_CASTLE
Normal file
7
LICENSES/LICENSE_BOUNCY_CASTLE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2000 - 2011 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
111
LICENSES/LICENSE_ICON_PACK_CLASSIC
Normal file
111
LICENSES/LICENSE_ICON_PACK_CLASSIC
Normal file
@@ -0,0 +1,111 @@
|
||||
Files ic00.png to ic61.png under res/drawable, res/drawable-hdpi and res/drawable-ldpi
|
||||
|
||||
TITLE: NUVOLA ICON THEME for KDE 3.x
|
||||
AUTHOR: David Vignoni | ICON KING
|
||||
SITE: http://www.icon-king.com
|
||||
MAILING LIST: http://mail.icon-king.com/mailman/listinfo/nuvola_icon-king.com
|
||||
|
||||
Copyright (c) 2003-2004 David Vignoni.
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation,
|
||||
version 2.1 of the License.
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library (see the the license.txt file); if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#######**** NOTE THIS ADD-ON ****#######
|
||||
The GNU Lesser General Public License or LGPL is written for software libraries
|
||||
in the first place. The LGPL has to be considered valid for this artwork
|
||||
library too.
|
||||
Nuvola icon theme for KDE 3.x is a special kind of software library, it is an
|
||||
artwork library, it's elements can be used in a Graphical User Interface, or
|
||||
GUI.
|
||||
Source code, for this library means:
|
||||
- raster png image* .
|
||||
The LGPL in some sections obliges you to make the files carry
|
||||
notices. With images this is in some cases impossible or hardly usefull.
|
||||
With this library a notice is placed at a prominent place in the directory
|
||||
containing the elements. You may follow this practice.
|
||||
The exception in section 6 of the GNU Lesser General Public License covers
|
||||
the use of elements of this art library in a GUI.
|
||||
dave [at] icon-king.com
|
||||
|
||||
Date: 15 october 2004
|
||||
Version: 1.0
|
||||
|
||||
DESCRIPTION:
|
||||
|
||||
Icon theme for KDE 3.x.
|
||||
Icons where designed using Adobe Illustrator, and then exported to PNG format.
|
||||
Icons shadows and minor corrections were done using Adobe Photoshop.
|
||||
Kiconedit was used to correct some 16x16 and 22x22 icons.
|
||||
|
||||
LICENSE
|
||||
|
||||
Released under GNU Lesser General Public License (LGPL)
|
||||
Look at the license.txt file.
|
||||
|
||||
CONTACT
|
||||
|
||||
David Vignoni
|
||||
e-mail : david [at] icon-king.com
|
||||
ICQ : 117761009
|
||||
http: http://www.icon-king.com
|
||||
|
||||
---
|
||||
|
||||
Files ic62.png under res/drawable, res/drawable-hdpi and res/drawable-ldpi
|
||||
|
||||
Based on http://de.wikipedia.org/w/index.php?title=Datei:Tux.svg&filetimestamp=20090927073505
|
||||
|
||||
The copyright holder of this file allows anyone to use it for any purpose,
|
||||
provided that the copyright holders Larry Ewing, Simon Budig and
|
||||
Anja Gerwinski are mentioned.
|
||||
|
||||
---
|
||||
|
||||
Files ic63.png under res/drawable, res/drawable-hdpi and res/drawable-ldpi
|
||||
|
||||
Based on http://en.wikipedia.org/wiki/File:ASF-logo.svg
|
||||
|
||||
Apache logo
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License. You may
|
||||
obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
License for the specific language governing permissions and limitations under
|
||||
the License.
|
||||
|
||||
---
|
||||
|
||||
Files ic64.png under res/drawable, res/drawable-hdpi and res/drawable-ldpi
|
||||
|
||||
Created by Jeremy JAMET and licensed under the terms of the GPLv3.
|
||||
|
||||
---
|
||||
|
||||
Files ic65.png, ic67.png and ic68.png under res/drawable, res/drawable-hdpi and res/drawable-ldpi
|
||||
|
||||
Created by Tobias Selig and licensed under the terms of the GPLv2 or GPLv3.
|
||||
|
||||
---
|
||||
|
||||
File ic66.png under under res/drawable, res/drawable-hdpi and res/drawable-ldpi
|
||||
|
||||
Based on http://commons.wikimedia.org/wiki/File:Dollar_symbol_gold.svg
|
||||
|
||||
Author: Rugby471
|
||||
|
||||
Permission is granted to copy, distribute and/or modify this document under
|
||||
the terms of the GNU Free Documentation License, Version 1.2 or any later
|
||||
version published by the Free Software Foundation; with no Invariant Sections,
|
||||
no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is
|
||||
included in the section entitled "GNU Free Documentation License".
|
||||
|
||||
---
|
||||
@@ -1,6 +1,6 @@
|
||||
# Android KeepassDX
|
||||
# Android KeePassDX
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeepassDX is a **multi-format KeePass manager for Android devices**. The app allows creating keys and passwords in a secure way by integrating with the Android design standards.
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/icon.png"> KeePassDX is a **multi-format KeePass manager for Android devices**. The app allows creating keys and passwords in a secure way by integrating with the Android design standards.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen.jpg" width="220">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
- Precise management of **settings**.
|
||||
- Code written in **native languages** *(Kotlin / Java / JNI / C)*.
|
||||
|
||||
KeepassDX is **open source** and **ad-free**.
|
||||
KeePassDX is **open source** and **ad-free**.
|
||||
|
||||
## What is KeePassDX?
|
||||
|
||||
@@ -88,4 +88,4 @@ Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/w
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
|
||||
*This project is a fork of [KeePassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
theme: jekyll-theme-cayman
|
||||
@@ -6,20 +6,21 @@ apply plugin: 'kotlin-kapt'
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '30.0.2'
|
||||
ndkVersion '21.3.6528147'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 30
|
||||
versionCode = 45
|
||||
versionName = "2.9.1"
|
||||
versionCode = 49
|
||||
versionName = "2.9.5"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
||||
|
||||
buildConfigField "String[]", "ICON_PACKS", "{\"classic\",\"material\"}"
|
||||
manifestPlaceholders = [ googleAndroidBackupAPIKey:"" ]
|
||||
manifestPlaceholders = [ googleAndroidBackupAPIKey:"unused" ]
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
@@ -96,11 +97,11 @@ def room_version = "2.2.5"
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.preference:preference:1.1.1'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.3'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.biometric:biometric:1.1.0-beta01'
|
||||
implementation 'androidx.biometric:biometric:1.1.0-rc01'
|
||||
// Lifecycle - LiveData - ViewModel - Coroutines
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
implementation 'androidx.fragment:fragment-ktx:1.2.5'
|
||||
|
||||
@@ -150,6 +150,13 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="otpauth" android:host="totp" />
|
||||
<data android:scheme="otpauth" android:host="hotp" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity"
|
||||
@@ -167,13 +174,17 @@
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".notifications.AttachmentFileNotificationService"
|
||||
android:name="com.kunzisoft.keepass.notifications.AttachmentFileNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name="com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
<!-- Receiver for Autofill -->
|
||||
<service
|
||||
android:name="com.kunzisoft.keepass.autofill.KeeAutofillService"
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
@@ -133,7 +134,7 @@ class EntryActivity : LockingActivity() {
|
||||
}
|
||||
|
||||
// Focus view to reinitialize timeout
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
|
||||
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
|
||||
|
||||
// Init the clipboard helper
|
||||
clipboardHelper = ClipboardHelper(this)
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.kunzisoft.keepass.activities.dialogs.FileTooBigDialogFragment.Compani
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.SelectFileHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
@@ -134,7 +135,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
}
|
||||
|
||||
// Focus view to reinitialize timeout
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
|
||||
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
|
||||
|
||||
stopService(Intent(this, ClipboardEntryNotificationService::class.java))
|
||||
stopService(Intent(this, KeyboardEntryNotificationService::class.java))
|
||||
@@ -452,14 +453,35 @@ class EntryEditActivity : LockingActivity(),
|
||||
EntryCustomFieldDialogFragment.getInstance(field).show(supportFragmentManager, "customFieldDialog")
|
||||
}
|
||||
|
||||
private fun verifyNameField(field: Field,
|
||||
actionIfNewName: () -> Unit) {
|
||||
var extraFieldAlreadyContainsName = false
|
||||
entryEditFragment?.getExtraFields()?.forEach {
|
||||
if (it.name.equals(field.name, true))
|
||||
extraFieldAlreadyContainsName = true
|
||||
}
|
||||
|
||||
if (!extraFieldAlreadyContainsName
|
||||
&& Entry.newExtraFieldNameAllowed(field)) {
|
||||
actionIfNewName.invoke()
|
||||
} else {
|
||||
Log.e(TAG, "Unable to create the new field, field name already exists")
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, R.string.error_field_name_already_exists, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewCustomFieldApproved(newField: Field) {
|
||||
entryEditFragment?.apply {
|
||||
putExtraField(newField)
|
||||
verifyNameField(newField) {
|
||||
entryEditFragment?.putExtraField(newField)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEditCustomFieldApproved(oldField: Field, newField: Field) {
|
||||
entryEditFragment?.replaceExtraField(oldField, newField)
|
||||
verifyNameField(newField) {
|
||||
entryEditFragment?.replaceExtraField(oldField, newField)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleteCustomFieldApproved(oldField: Field) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.adapters.EntryAttachmentsItemsAdapter
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
@@ -148,6 +149,8 @@ class EntryEditFragment: StylishFragment() {
|
||||
iconColor = taIconColor?.getColor(0, Color.WHITE) ?: Color.WHITE
|
||||
taIconColor?.recycle()
|
||||
|
||||
rootView?.resetAppTimeoutWhenViewFocusedOrChanged(requireContext())
|
||||
|
||||
// Retrieve the new entry after an orientation change
|
||||
if (arguments?.containsKey(KEY_TEMP_ENTRY_INFO) == true)
|
||||
mEntryInfo = arguments?.getParcelable(KEY_TEMP_ENTRY_INFO) ?: mEntryInfo
|
||||
|
||||
@@ -23,15 +23,17 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
/**
|
||||
* Activity to search or select entry in database,
|
||||
@@ -42,22 +44,35 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
var sharedWebDomain: String? = null
|
||||
var otpString: String? = null
|
||||
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
if ("text/plain" == intent.type) {
|
||||
// Retrieve web domain
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||
sharedWebDomain = Uri.parse(it).host
|
||||
// Retrieve web domain or OTP
|
||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra ->
|
||||
if (OtpEntryFields.isOTPUri(extra))
|
||||
otpString = extra
|
||||
else
|
||||
sharedWebDomain = Uri.parse(extra).host
|
||||
}
|
||||
}
|
||||
}
|
||||
Intent.ACTION_VIEW -> {
|
||||
// Retrieve OTP
|
||||
intent.dataString?.let { extra ->
|
||||
if (OtpEntryFields.isOTPUri(extra))
|
||||
otpString = extra
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
// Build search param
|
||||
|
||||
// Build domain search param
|
||||
val searchInfo = SearchInfo().apply {
|
||||
webDomain = sharedWebDomain
|
||||
this.webDomain = sharedWebDomain
|
||||
this.otpString = otpString
|
||||
}
|
||||
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
||||
searchInfo.webDomain = concreteWebDomain
|
||||
@@ -68,62 +83,97 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun launch(searchInfo: SearchInfo) {
|
||||
// Setting to integrate Magikeyboard
|
||||
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
|
||||
|
||||
// If database is open
|
||||
val database = Database.getInstance()
|
||||
val readOnly = database.isReadOnly
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ items ->
|
||||
// Items found
|
||||
if (searchShareForMagikeyboard) {
|
||||
if (items.size == 1) {
|
||||
// Automatically populate keyboard
|
||||
val entryPopulate = items[0]
|
||||
populateKeyboardAndMoveAppToBackground(this,
|
||||
entryPopulate,
|
||||
intent)
|
||||
if (!searchInfo.containsOnlyNullValues()) {
|
||||
// Setting to integrate Magikeyboard
|
||||
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
|
||||
|
||||
// If database is open
|
||||
val database = Database.getInstance()
|
||||
val readOnly = database.isReadOnly
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
database,
|
||||
searchInfo,
|
||||
{ items ->
|
||||
// Items found
|
||||
if (searchInfo.otpString != null) {
|
||||
if (!readOnly) {
|
||||
GroupActivity.launchForSaveResult(this,
|
||||
searchInfo,
|
||||
false)
|
||||
} else {
|
||||
Toast.makeText(applicationContext,
|
||||
R.string.autofill_read_only_save,
|
||||
Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
} else if (searchShareForMagikeyboard) {
|
||||
if (items.size == 1) {
|
||||
// Automatically populate keyboard
|
||||
val entryPopulate = items[0]
|
||||
populateKeyboardAndMoveAppToBackground(this,
|
||||
entryPopulate,
|
||||
intent)
|
||||
} else {
|
||||
// Select the one we want
|
||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
true)
|
||||
}
|
||||
} else {
|
||||
// Select the one we want
|
||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||
GroupActivity.launchForSearchResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
true)
|
||||
}
|
||||
} else {
|
||||
GroupActivity.launchForSearchResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
true)
|
||||
},
|
||||
{
|
||||
// Show the database UI to select the entry
|
||||
if (searchInfo.otpString != null) {
|
||||
if (!readOnly) {
|
||||
GroupActivity.launchForSaveResult(this,
|
||||
searchInfo,
|
||||
false)
|
||||
} else {
|
||||
Toast.makeText(applicationContext,
|
||||
R.string.autofill_read_only_save,
|
||||
Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
} else if (readOnly || searchShareForMagikeyboard) {
|
||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
false)
|
||||
} else {
|
||||
GroupActivity.launchForSaveResult(this,
|
||||
searchInfo,
|
||||
false)
|
||||
}
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
if (searchInfo.otpString != null) {
|
||||
if (!readOnly) {
|
||||
FileDatabaseSelectActivity.launchForSaveResult(this,
|
||||
searchInfo)
|
||||
} else {
|
||||
Toast.makeText(applicationContext,
|
||||
R.string.autofill_read_only_save,
|
||||
Toast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
} else if (searchShareForMagikeyboard) {
|
||||
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
|
||||
searchInfo)
|
||||
} else {
|
||||
FileDatabaseSelectActivity.launchForSearchResult(this,
|
||||
searchInfo)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Show the database UI to select the entry
|
||||
if (readOnly || searchShareForMagikeyboard) {
|
||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||
readOnly,
|
||||
searchInfo,
|
||||
false)
|
||||
} else {
|
||||
GroupActivity.launchForSaveResult(this,
|
||||
searchInfo,
|
||||
false)
|
||||
}
|
||||
},
|
||||
{
|
||||
// If database not open
|
||||
if (searchShareForMagikeyboard) {
|
||||
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
|
||||
searchInfo)
|
||||
} else {
|
||||
FileDatabaseSelectActivity.launchForSearchResult(this,
|
||||
searchInfo)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import androidx.activity.viewModels
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
@@ -162,7 +161,7 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
}
|
||||
|
||||
// Observe list of databases
|
||||
databaseFilesViewModel.databaseFilesLoaded.observe(this, Observer { databaseFiles ->
|
||||
databaseFilesViewModel.databaseFilesLoaded.observe(this) { databaseFiles ->
|
||||
when (databaseFiles.databaseFileAction) {
|
||||
DatabaseFilesViewModel.DatabaseFileAction.NONE -> {
|
||||
mAdapterDatabaseHistory?.replaceAllDatabaseFileHistoryList(databaseFiles.databaseFileList)
|
||||
@@ -186,13 +185,13 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
}
|
||||
}
|
||||
databaseFilesViewModel.consumeAction()
|
||||
})
|
||||
}
|
||||
|
||||
// Observe default database
|
||||
databaseFilesViewModel.defaultDatabase.observe(this, Observer {
|
||||
databaseFilesViewModel.defaultDatabase.observe(this) {
|
||||
// Retrieve settings for default database
|
||||
mAdapterDatabaseHistory?.setDefaultDatabase(it)
|
||||
})
|
||||
}
|
||||
|
||||
// Attach the dialog thread to this activity
|
||||
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
|
||||
@@ -237,10 +236,10 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
|
||||
private fun fileNoFoundAction(e: FileNotFoundException) {
|
||||
val error = getString(R.string.file_not_found_content)
|
||||
Log.e(TAG, error, e)
|
||||
coordinatorLayout?.let {
|
||||
Snackbar.make(it, error, Snackbar.LENGTH_LONG).asError().show()
|
||||
}
|
||||
Log.e(TAG, error, e)
|
||||
}
|
||||
|
||||
private fun launchPasswordActivity(databaseUri: Uri, keyFile: Uri?) {
|
||||
@@ -435,8 +434,8 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> UriUtil.gotoUrl(this, R.string.file_manager_explanation_url)
|
||||
}
|
||||
|
||||
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item) && super.onOptionsItemSelected(item)
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -468,6 +467,19 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
||||
searchInfo)
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
* Save Launch
|
||||
* -------------------------
|
||||
*/
|
||||
|
||||
fun launchForSaveResult(context: Context,
|
||||
searchInfo: SearchInfo) {
|
||||
EntrySelectionHelper.startActivityForSaveModeResult(context,
|
||||
Intent(context, FileDatabaseSelectActivity::class.java),
|
||||
searchInfo)
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
* Keyboard Launch
|
||||
|
||||
@@ -50,6 +50,7 @@ import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.SpecialMode
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
@@ -153,7 +154,7 @@ class GroupActivity : LockingActivity(),
|
||||
taTextColor.recycle()
|
||||
|
||||
// Focus view to reinitialize timeout
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(rootContainerView)
|
||||
rootContainerView?.resetAppTimeoutWhenViewFocusedOrChanged(this)
|
||||
|
||||
// Retrieve elements after an orientation change
|
||||
if (savedInstanceState != null) {
|
||||
@@ -591,13 +592,29 @@ class GroupActivity : LockingActivity(),
|
||||
finish()
|
||||
},
|
||||
{ searchInfo ->
|
||||
if (!mReadOnly
|
||||
&& searchInfo != null
|
||||
&& PreferencesUtil.isKeyboardSaveSearchInfoEnable(this@GroupActivity)) {
|
||||
updateEntryWithSearchInfo(entryVersioned, searchInfo)
|
||||
} else {
|
||||
entrySelectedForKeyboardSelection(entryVersioned)
|
||||
}
|
||||
// Recheck search, only to fix #783 because workflow allows to open multiple search elements
|
||||
SearchHelper.checkAutoSearchInfo(this,
|
||||
mDatabase!!,
|
||||
searchInfo,
|
||||
{ _ ->
|
||||
// Item in search, don't save
|
||||
entrySelectedForKeyboardSelection(entryVersioned)
|
||||
},
|
||||
{
|
||||
// Item not found, save it if required
|
||||
if (!mReadOnly
|
||||
&& searchInfo != null
|
||||
&& PreferencesUtil.isKeyboardSaveSearchInfoEnable(this@GroupActivity)) {
|
||||
updateEntryWithSearchInfo(entryVersioned, searchInfo)
|
||||
} else {
|
||||
entrySelectedForKeyboardSelection(entryVersioned)
|
||||
}
|
||||
},
|
||||
{
|
||||
// Normally not append
|
||||
finish()
|
||||
}
|
||||
)
|
||||
},
|
||||
{ searchInfo, _ ->
|
||||
if (!mReadOnly
|
||||
@@ -660,7 +677,7 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
private fun updateEntryWithSearchInfo(entry: Entry, searchInfo: SearchInfo) {
|
||||
val newEntry = Entry(entry)
|
||||
newEntry.setEntryInfo(mDatabase, newEntry.getEntryInfo(mDatabase).apply {
|
||||
newEntry.setEntryInfo(mDatabase, newEntry.getEntryInfo(mDatabase, true).apply {
|
||||
saveSearchInfo(mDatabase, searchInfo)
|
||||
})
|
||||
// In selection mode, it's forced read-only, so update not allowed
|
||||
@@ -1354,8 +1371,20 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
// Nothing with Save Info, only pass by search first
|
||||
{ searchInfo ->
|
||||
// Save info used with OTP
|
||||
if (!readOnly) {
|
||||
GroupActivity.launchForSaveResult(activity,
|
||||
searchInfo,
|
||||
false)
|
||||
onLaunchActivitySpecialMode()
|
||||
} else {
|
||||
Toast.makeText(activity.applicationContext,
|
||||
R.string.autofill_read_only_save,
|
||||
Toast.LENGTH_LONG)
|
||||
.show()
|
||||
onCancelSpecialMode()
|
||||
}
|
||||
},
|
||||
{ searchInfo ->
|
||||
SearchHelper.checkAutoSearchInfo(activity,
|
||||
|
||||
@@ -37,9 +37,8 @@ import android.widget.*
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.fragment.app.commit
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||
@@ -51,11 +50,11 @@ import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.selection.SpecialModeActivity
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager
|
||||
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockFragment
|
||||
import com.kunzisoft.keepass.database.action.ProgressDatabaseTaskProvider
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
@@ -69,14 +68,13 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.BACK_PREVIOUS_KEYBOARD_ACTION
|
||||
import com.kunzisoft.keepass.utils.MenuUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
import com.kunzisoft.keepass.view.KeyFileSelectionView
|
||||
import com.kunzisoft.keepass.view.asError
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
|
||||
import kotlinx.android.synthetic.main.activity_password.*
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
open class PasswordActivity : SpecialModeActivity() {
|
||||
open class PasswordActivity : SpecialModeActivity(), AdvancedUnlockFragment.BuilderListener {
|
||||
|
||||
// Views
|
||||
private var toolbar: Toolbar? = null
|
||||
@@ -86,12 +84,12 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
private var confirmButtonView: Button? = null
|
||||
private var checkboxPasswordView: CompoundButton? = null
|
||||
private var checkboxKeyFileView: CompoundButton? = null
|
||||
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
|
||||
private var advancedUnlockFragment: AdvancedUnlockFragment? = null
|
||||
private var infoContainerView: ViewGroup? = null
|
||||
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
|
||||
|
||||
private val databaseFileViewModel: DatabaseFileViewModel by viewModels()
|
||||
|
||||
private var mDefaultDatabase: Boolean = false
|
||||
private var mDatabaseFileUri: Uri? = null
|
||||
private var mDatabaseKeyFileUri: Uri? = null
|
||||
|
||||
@@ -113,7 +111,6 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
|
||||
private var mProgressDatabaseTaskProvider: ProgressDatabaseTaskProvider? = null
|
||||
|
||||
private var advancedUnlockedManager: AdvancedUnlockedManager? = null
|
||||
private var mAllowAutoOpenBiometricPrompt: Boolean = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -133,7 +130,6 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
keyFileSelectionView = findViewById(R.id.keyfile_selection)
|
||||
checkboxPasswordView = findViewById(R.id.password_checkbox)
|
||||
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
|
||||
advancedUnlockInfoView = findViewById(R.id.biometric_info)
|
||||
infoContainerView = findViewById(R.id.activity_password_info_container)
|
||||
|
||||
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
|
||||
@@ -160,10 +156,6 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
}
|
||||
})
|
||||
|
||||
enableButtonOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
enableOrNotTheConfirmationButton()
|
||||
}
|
||||
|
||||
// If is a view intent
|
||||
getUriFromIntent(intent)
|
||||
if (savedInstanceState?.containsKey(KEY_KEYFILE) == true) {
|
||||
@@ -173,8 +165,31 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
mAllowAutoOpenBiometricPrompt = savedInstanceState.getBoolean(ALLOW_AUTO_OPEN_BIOMETRIC_PROMPT)
|
||||
}
|
||||
|
||||
// Init Biometric elements
|
||||
advancedUnlockFragment = supportFragmentManager
|
||||
.findFragmentByTag(UNLOCK_FRAGMENT_TAG) as? AdvancedUnlockFragment?
|
||||
if (advancedUnlockFragment == null) {
|
||||
advancedUnlockFragment = AdvancedUnlockFragment()
|
||||
supportFragmentManager.commit {
|
||||
replace(R.id.fragment_advanced_unlock_container_view,
|
||||
advancedUnlockFragment!!,
|
||||
UNLOCK_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
// Listen password checkbox to init advanced unlock and confirmation button
|
||||
checkboxPasswordView?.setOnCheckedChangeListener { _, _ ->
|
||||
advancedUnlockFragment?.checkUnlockAvailability()
|
||||
enableOrNotTheConfirmationButton()
|
||||
}
|
||||
|
||||
// Observe if default database
|
||||
databaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
|
||||
mDefaultDatabase = isDefaultDatabase
|
||||
}
|
||||
|
||||
// Observe database file change
|
||||
databaseFileViewModel.databaseFileLoaded.observe(this, Observer { databaseFile ->
|
||||
databaseFileViewModel.databaseFileLoaded.observe(this) { databaseFile ->
|
||||
// Force read only if the file does not exists
|
||||
mForceReadOnly = databaseFile?.let {
|
||||
!it.databaseFileExists
|
||||
@@ -194,19 +209,14 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
filenameView?.text = databaseFile?.databaseAlias ?: ""
|
||||
|
||||
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri)
|
||||
})
|
||||
}
|
||||
|
||||
mProgressDatabaseTaskProvider = ProgressDatabaseTaskProvider(this).apply {
|
||||
onActionFinish = { actionTask, result ->
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_LOAD_TASK -> {
|
||||
// Recheck biometric if error
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) {
|
||||
// Stay with the same mode and init it
|
||||
advancedUnlockedManager?.initBiometricMode()
|
||||
}
|
||||
}
|
||||
// Recheck advanced unlock if error
|
||||
advancedUnlockFragment?.initAdvancedUnlockMode()
|
||||
|
||||
if (result.isSuccess) {
|
||||
mDatabaseKeyFileUri = null
|
||||
@@ -220,32 +230,40 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
if (resultException != null) {
|
||||
resultError = resultException.getLocalizedMessage(resources)
|
||||
|
||||
// Relaunch loading if we need to fix UUID
|
||||
if (resultException is DuplicateUuidDatabaseException) {
|
||||
showLoadDatabaseDuplicateUuidMessage {
|
||||
when (resultException) {
|
||||
is DuplicateUuidDatabaseException -> {
|
||||
// Relaunch loading if we need to fix UUID
|
||||
showLoadDatabaseDuplicateUuidMessage {
|
||||
|
||||
var databaseUri: Uri? = null
|
||||
var masterPassword: String? = null
|
||||
var keyFileUri: Uri? = null
|
||||
var readOnly = true
|
||||
var cipherEntity: CipherDatabaseEntity? = null
|
||||
var databaseUri: Uri? = null
|
||||
var masterPassword: String? = null
|
||||
var keyFileUri: Uri? = null
|
||||
var readOnly = true
|
||||
var cipherEntity: CipherDatabaseEntity? = null
|
||||
|
||||
result.data?.let { resultData ->
|
||||
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
|
||||
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
|
||||
keyFileUri = resultData.getParcelable(KEY_FILE_URI_KEY)
|
||||
readOnly = resultData.getBoolean(READ_ONLY_KEY)
|
||||
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
|
||||
result.data?.let { resultData ->
|
||||
databaseUri = resultData.getParcelable(DATABASE_URI_KEY)
|
||||
masterPassword = resultData.getString(MASTER_PASSWORD_KEY)
|
||||
keyFileUri = resultData.getParcelable(KEY_FILE_URI_KEY)
|
||||
readOnly = resultData.getBoolean(READ_ONLY_KEY)
|
||||
cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY)
|
||||
}
|
||||
|
||||
databaseUri?.let { databaseFileUri ->
|
||||
showProgressDialogAndLoadDatabase(
|
||||
databaseFileUri,
|
||||
masterPassword,
|
||||
keyFileUri,
|
||||
readOnly,
|
||||
cipherEntity,
|
||||
true)
|
||||
}
|
||||
}
|
||||
|
||||
databaseUri?.let { databaseFileUri ->
|
||||
showProgressDialogAndLoadDatabase(
|
||||
databaseFileUri,
|
||||
masterPassword,
|
||||
keyFileUri,
|
||||
readOnly,
|
||||
cipherEntity,
|
||||
true)
|
||||
}
|
||||
is FileNotFoundDatabaseException -> {
|
||||
// Remove this default database inaccessible
|
||||
if (mDefaultDatabase) {
|
||||
databaseFileViewModel.removeDefaultDatabase()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,6 +295,9 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
mDatabaseFileUri = intent?.getParcelableExtra(KEY_FILENAME)
|
||||
mDatabaseKeyFileUri = intent?.getParcelableExtra(KEY_KEYFILE)
|
||||
}
|
||||
mDatabaseFileUri?.let {
|
||||
databaseFileViewModel.checkIfIsDefaultDatabase(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
@@ -303,6 +324,33 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun retrieveCredentialForEncryption(): String {
|
||||
return passwordView?.text?.toString() ?: ""
|
||||
}
|
||||
|
||||
override fun conditionToStoreCredential(): Boolean {
|
||||
return checkboxPasswordView?.isChecked == true
|
||||
}
|
||||
|
||||
override fun onCredentialEncrypted(databaseUri: Uri,
|
||||
encryptedCredential: String,
|
||||
ivSpec: String) {
|
||||
// Load the database if password is registered with biometric
|
||||
verifyCheckboxesAndLoadDatabase(
|
||||
CipherDatabaseEntity(
|
||||
databaseUri.toString(),
|
||||
encryptedCredential,
|
||||
ivSpec)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCredentialDecrypted(databaseUri: Uri,
|
||||
decryptedCredential: String) {
|
||||
// Load the database if password is retrieve from biometric
|
||||
// Retrieve from biometric
|
||||
verifyKeyFileCheckboxAndLoadDatabase(decryptedCredential)
|
||||
}
|
||||
|
||||
private val onEditorActionListener = object : TextView.OnEditorActionListener {
|
||||
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||
if (actionId == IME_ACTION_DONE) {
|
||||
@@ -369,48 +417,9 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
verifyCheckboxesAndLoadDatabase(password, keyFileUri)
|
||||
} else {
|
||||
// Init Biometric elements
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(this)) {
|
||||
if (advancedUnlockedManager == null
|
||||
&& databaseFileUri != null) {
|
||||
advancedUnlockedManager = AdvancedUnlockedManager(this,
|
||||
databaseFileUri,
|
||||
advancedUnlockInfoView,
|
||||
checkboxPasswordView,
|
||||
enableButtonOnCheckedChangeListener,
|
||||
passwordView,
|
||||
{ passwordEncrypted, ivSpec ->
|
||||
// Load the database if password is registered with biometric
|
||||
if (passwordEncrypted != null && ivSpec != null) {
|
||||
verifyCheckboxesAndLoadDatabase(
|
||||
CipherDatabaseEntity(
|
||||
databaseFileUri.toString(),
|
||||
passwordEncrypted,
|
||||
ivSpec)
|
||||
)
|
||||
}
|
||||
},
|
||||
{ passwordDecrypted ->
|
||||
// Load the database if password is retrieve from biometric
|
||||
passwordDecrypted?.let {
|
||||
// Retrieve from biometric
|
||||
verifyKeyFileCheckboxAndLoadDatabase(it)
|
||||
}
|
||||
})
|
||||
}
|
||||
advancedUnlockedManager?.isBiometricPromptAutoOpenEnable =
|
||||
mAllowAutoOpenBiometricPrompt && mProgressDatabaseTaskProvider?.isBinded() != true
|
||||
advancedUnlockedManager?.checkBiometricAvailability()
|
||||
} else {
|
||||
advancedUnlockInfoView?.visibility = View.GONE
|
||||
advancedUnlockedManager?.destroy()
|
||||
advancedUnlockedManager = null
|
||||
}
|
||||
}
|
||||
if (advancedUnlockedManager == null) {
|
||||
checkboxPasswordView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||
}
|
||||
checkboxKeyFileView?.setOnCheckedChangeListener(enableButtonOnCheckedChangeListener)
|
||||
advancedUnlockFragment?.loadDatabase(databaseFileUri,
|
||||
mAllowAutoOpenBiometricPrompt
|
||||
&& mProgressDatabaseTaskProvider?.isBinded() != true)
|
||||
}
|
||||
|
||||
enableOrNotTheConfirmationButton()
|
||||
@@ -462,11 +471,6 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
override fun onPause() {
|
||||
mProgressDatabaseTaskProvider?.unregisterProgressTask()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
advancedUnlockedManager?.destroy()
|
||||
advancedUnlockedManager = null
|
||||
}
|
||||
|
||||
// Reinit locking activity UI variable
|
||||
LockingActivity.LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK = null
|
||||
mAllowAutoOpenBiometricPrompt = true
|
||||
@@ -575,11 +579,6 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
MenuUtil.defaultMenuInflater(inflater, menu)
|
||||
}
|
||||
|
||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// biometric menu
|
||||
advancedUnlockedManager?.inflateOptionsMenu(inflater, menu)
|
||||
}
|
||||
|
||||
super.onCreateOptionsMenu(menu)
|
||||
|
||||
launchEducation(menu)
|
||||
@@ -655,21 +654,14 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
})
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !readOnlyEducationPerformed) {
|
||||
val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(this)
|
||||
PreferencesUtil.isBiometricUnlockEnable(applicationContext)
|
||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.visibility == View.VISIBLE
|
||||
&& advancedUnlockInfoView?.unlockIconImageView != null
|
||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
})
|
||||
}
|
||||
advancedUnlockFragment?.performEducation(passwordActivityEducation,
|
||||
readOnlyEducationPerformed,
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
},
|
||||
{
|
||||
performedNextEducation(passwordActivityEducation, menu)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,10 +683,7 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
readOnly = !readOnly
|
||||
changeOpenFileReadIcon(item)
|
||||
}
|
||||
R.id.menu_biometric_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
advancedUnlockedManager?.deleteEntryKey()
|
||||
}
|
||||
else -> return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||
else -> MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
@@ -708,6 +697,9 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
|
||||
mAllowAutoOpenBiometricPrompt = false
|
||||
|
||||
// To get device credential unlock result
|
||||
advancedUnlockFragment?.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
// To get entry in result
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data)
|
||||
@@ -741,6 +733,8 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
|
||||
private val TAG = PasswordActivity::class.java.name
|
||||
|
||||
private const val UNLOCK_FRAGMENT_TAG = "UNLOCK_FRAGMENT_TAG"
|
||||
|
||||
private const val KEY_FILENAME = "fileName"
|
||||
private const val KEY_KEYFILE = "keyFile"
|
||||
private const val VIEW_INTENT = "android.intent.action.VIEW"
|
||||
@@ -795,6 +789,25 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
* Save Launch
|
||||
* -------------------------
|
||||
*/
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
fun launchForSaveResult(activity: Activity,
|
||||
databaseFile: Uri,
|
||||
keyFile: Uri?,
|
||||
searchInfo: SearchInfo) {
|
||||
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||
EntrySelectionHelper.startActivityForSaveModeResult(
|
||||
activity,
|
||||
intent,
|
||||
searchInfo)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* -------------------------
|
||||
* Keyboard Launch
|
||||
@@ -877,8 +890,11 @@ open class PasswordActivity : SpecialModeActivity() {
|
||||
searchInfo)
|
||||
onLaunchActivitySpecialMode()
|
||||
},
|
||||
{ // Save Action
|
||||
// Not directly used, a search is performed before
|
||||
{ searchInfo -> // Save Action
|
||||
PasswordActivity.launchForSaveResult(activity,
|
||||
databaseUri, keyFile,
|
||||
searchInfo)
|
||||
onLaunchActivitySpecialMode()
|
||||
},
|
||||
{ searchInfo -> // Keyboard Selection Action
|
||||
PasswordActivity.launchForKeyboardResult(activity,
|
||||
|
||||
@@ -90,12 +90,12 @@ class UnavailableFeatureDialogFragment : DialogFragment() {
|
||||
}
|
||||
}
|
||||
if (apiName.isEmpty()) {
|
||||
val mapper = arrayOf("ANDROID BASE", "ANDROID BASE 1.1", "CUPCAKE", "DONUT", "ECLAIR", "ECLAIR_0_1", "ECLAIR_MR1", "FROYO", "GINGERBREAD", "GINGERBREAD_MR1", "HONEYCOMB", "HONEYCOMB_MR1", "HONEYCOMB_MR2", "ICE_CREAM_SANDWICH", "ICE_CREAM_SANDWICH_MR1", "JELLY_BEAN", "JELLY_BEAN", "JELLY_BEAN", "KITKAT", "KITKAT", "LOLLIPOOP", "LOLLIPOOP_MR1", "MARSHMALLOW", "NOUGAT", "NOUGAT", "OREO", "OREO")
|
||||
val mapper = arrayOf("ANDROID BASE", "ANDROID BASE 1.1", "CUPCAKE", "DONUT", "ECLAIR", "ECLAIR_0_1", "ECLAIR_MR1", "FROYO", "GINGERBREAD", "GINGERBREAD_MR1", "HONEYCOMB", "HONEYCOMB_MR1", "HONEYCOMB_MR2", "ICE_CREAM_SANDWICH", "ICE_CREAM_SANDWICH_MR1", "JELLY_BEAN", "JELLY_BEAN", "JELLY_BEAN", "KITKAT", "KITKAT", "LOLLIPOOP", "LOLLIPOOP_MR1", "MARSHMALLOW", "NOUGAT", "NOUGAT", "OREO", "OREO", "PIE", "", "")
|
||||
val index = apiNumber - 1
|
||||
apiName = if (index < mapper.size) mapper[index] else "UNKNOWN_VERSION"
|
||||
}
|
||||
if (version.isEmpty()) {
|
||||
val versions = arrayOf("1.0", "1.1", "1.5", "1.6", "2.0", "2.0.1", "2.1", "2.2.X", "2.3", "2.3.3", "3.0", "3.1", "3.2.0", "4.0.1", "4.0.3", "4.1.0", "4.2.0", "4.3.0", "4.4", "4.4", "5.0", "5.1", "6.0", "7.0", "7.1", "8.0.0", "8.1.0")
|
||||
val versions = arrayOf("1.0", "1.1", "1.5", "1.6", "2.0", "2.0.1", "2.1", "2.2.X", "2.3", "2.3.3", "3.0", "3.1", "3.2.0", "4.0.1", "4.0.3", "4.1.0", "4.2.0", "4.3.0", "4.4", "4.4", "5.0", "5.1", "6.0", "7.0", "7.1", "8.0.0", "8.1.0", "9", "10", "11")
|
||||
val index = apiNumber - 1
|
||||
version = if (index < versions.size) versions[index] else "UNKNOWN_VERSION"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.activities.lock
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MotionEvent
|
||||
@@ -163,35 +164,6 @@ abstract class LockingActivity : SpecialModeActivity() {
|
||||
sendBroadcast(Intent(LOCK_ACTION))
|
||||
}
|
||||
|
||||
/**
|
||||
* To reset the app timeout when a view is focused or changed
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View?) {
|
||||
views.forEach {
|
||||
it?.setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// Log.d(TAG, "View touched, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
it?.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) {
|
||||
// Log.d(TAG, "View focused, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this)
|
||||
}
|
||||
}
|
||||
if (it is ViewGroup) {
|
||||
for (i in 0..it.childCount) {
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(it.getChildAt(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (mTimeoutEnable) {
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) {
|
||||
@@ -204,7 +176,7 @@ abstract class LockingActivity : SpecialModeActivity() {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "LockingActivity"
|
||||
const val TAG = "LockingActivity"
|
||||
|
||||
const val RESULT_EXIT_LOCK = 1450
|
||||
|
||||
@@ -215,3 +187,28 @@ abstract class LockingActivity : SpecialModeActivity() {
|
||||
var LOCKING_ACTIVITY_UI_VISIBLE_DURING_LOCK: Boolean? = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To reset the app timeout when a view is focused or changed
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
fun View.resetAppTimeoutWhenViewFocusedOrChanged(context: Context) {
|
||||
setOnTouchListener { _, event ->
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
//Log.d(LockingActivity.TAG, "View touched, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context)
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
setOnFocusChangeListener { _, _ ->
|
||||
//Log.d(LockingActivity.TAG, "View focused, try to reset app timeout")
|
||||
TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(context)
|
||||
}
|
||||
if (this is ViewGroup) {
|
||||
for (i in 0..childCount) {
|
||||
getChildAt(i)?.resetAppTimeoutWhenViewFocusedOrChanged(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ class SearchEntryCursorAdapter(private val context: Context,
|
||||
} else {
|
||||
""
|
||||
}
|
||||
visibility = if (text.isEmpty()) View.GONE else View.VISIBLE
|
||||
strikeOut(currentEntry.isCurrentlyExpires)
|
||||
}
|
||||
}
|
||||
@@ -160,8 +161,8 @@ class SearchEntryCursorAdapter(private val context: Context,
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
internal var imageViewIcon: ImageView? = null
|
||||
internal var textViewTitle: TextView? = null
|
||||
internal var textViewSubTitle: TextView? = null
|
||||
var imageViewIcon: ImageView? = null
|
||||
var textViewTitle: TextView? = null
|
||||
var textViewSubTitle: TextView? = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,27 +19,96 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.app.database
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.SingletonHolderParameter
|
||||
import java.util.*
|
||||
|
||||
class CipherDatabaseAction(applicationContext: Context) {
|
||||
class CipherDatabaseAction(context: Context) {
|
||||
|
||||
private val applicationContext = context.applicationContext
|
||||
private val cipherDatabaseDao =
|
||||
AppDatabase
|
||||
.getDatabase(applicationContext)
|
||||
.cipherDatabaseDao()
|
||||
|
||||
// Temp DAO to easily remove content if object no longer in memory
|
||||
private var useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
|
||||
|
||||
private val mIntentAdvancedUnlockService = Intent(applicationContext,
|
||||
AdvancedUnlockNotificationService::class.java)
|
||||
private var mBinder: AdvancedUnlockNotificationService.AdvancedUnlockBinder? = null
|
||||
private var mServiceConnection: ServiceConnection? = null
|
||||
|
||||
private var mDatabaseListeners = LinkedList<DatabaseListener>()
|
||||
|
||||
fun reloadPreferences() {
|
||||
useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun attachService(performedAction: () -> Unit) {
|
||||
// Check if a service is currently running else do nothing
|
||||
if (mBinder != null) {
|
||||
performedAction.invoke()
|
||||
} else if (mServiceConnection == null) {
|
||||
mServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||
mBinder = (serviceBinder as AdvancedUnlockNotificationService.AdvancedUnlockBinder)
|
||||
performedAction.invoke()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
mBinder = null
|
||||
mServiceConnection = null
|
||||
mDatabaseListeners.forEach {
|
||||
it.onDatabaseCleared()
|
||||
}
|
||||
}
|
||||
}
|
||||
applicationContext.bindService(mIntentAdvancedUnlockService,
|
||||
mServiceConnection!!,
|
||||
Context.BIND_ABOVE_CLIENT)
|
||||
if (mBinder == null) {
|
||||
applicationContext.startService(mIntentAdvancedUnlockService)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerDatabaseListener(listener: DatabaseListener) {
|
||||
mDatabaseListeners.add(listener)
|
||||
}
|
||||
|
||||
fun unregisterDatabaseListener(listener: DatabaseListener) {
|
||||
mDatabaseListeners.remove(listener)
|
||||
}
|
||||
|
||||
interface DatabaseListener {
|
||||
fun onDatabaseCleared()
|
||||
}
|
||||
|
||||
fun getCipherDatabase(databaseUri: Uri,
|
||||
cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) {
|
||||
IOActionTask(
|
||||
{
|
||||
cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
|
||||
},
|
||||
{
|
||||
cipherDatabaseResultListener.invoke(it)
|
||||
}
|
||||
).execute()
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
cipherDatabaseResultListener.invoke(mBinder?.getCipherDatabase(databaseUri))
|
||||
}
|
||||
} else {
|
||||
IOActionTask(
|
||||
{
|
||||
cipherDatabaseDao.getByDatabaseUri(databaseUri.toString())
|
||||
},
|
||||
{
|
||||
cipherDatabaseResultListener.invoke(it)
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun containsCipherDatabase(databaseUri: Uri,
|
||||
@@ -51,36 +120,52 @@ class CipherDatabaseAction(applicationContext: Context) {
|
||||
|
||||
fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity,
|
||||
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
||||
IOActionTask(
|
||||
{
|
||||
val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
|
||||
|
||||
// Update values if element not yet in the database
|
||||
if (cipherDatabaseRetrieve == null) {
|
||||
cipherDatabaseDao.add(cipherDatabaseEntity)
|
||||
} else {
|
||||
cipherDatabaseDao.update(cipherDatabaseEntity)
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
} else {
|
||||
IOActionTask(
|
||||
{
|
||||
val cipherDatabaseRetrieve = cipherDatabaseDao.getByDatabaseUri(cipherDatabaseEntity.databaseUri)
|
||||
// Update values if element not yet in the database
|
||||
if (cipherDatabaseRetrieve == null) {
|
||||
cipherDatabaseDao.add(cipherDatabaseEntity)
|
||||
} else {
|
||||
cipherDatabaseDao.update(cipherDatabaseEntity)
|
||||
}
|
||||
},
|
||||
{
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
},
|
||||
{
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
).execute()
|
||||
).execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteByDatabaseUri(databaseUri: Uri,
|
||||
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
||||
IOActionTask(
|
||||
{
|
||||
cipherDatabaseDao.deleteByDatabaseUri(databaseUri.toString())
|
||||
},
|
||||
{
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
).execute()
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
mBinder?.deleteByDatabaseUri(databaseUri)
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
} else {
|
||||
IOActionTask(
|
||||
{
|
||||
cipherDatabaseDao.deleteByDatabaseUri(databaseUri.toString())
|
||||
},
|
||||
{
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
attachService {
|
||||
mBinder?.deleteAll()
|
||||
}
|
||||
IOActionTask(
|
||||
{
|
||||
cipherDatabaseDao.deleteAll()
|
||||
|
||||
@@ -43,6 +43,11 @@ data class CipherDatabaseEntity(
|
||||
parcel.readString()!!,
|
||||
parcel.readString()!!)
|
||||
|
||||
fun replaceContent(copy: CipherDatabaseEntity) {
|
||||
this.encryptedValue = copy.encryptedValue
|
||||
this.specParameters = copy.specParameters
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(databaseUri)
|
||||
parcel.writeString(encryptedValue)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import javax.crypto.Cipher
|
||||
|
||||
data class AdvancedUnlockCryptoPrompt(var cipher: Cipher,
|
||||
@StringRes var promptTitleId: Int,
|
||||
@StringRes var promptDescriptionId: Int? = null,
|
||||
var isDeviceCredentialOperation: Boolean,
|
||||
var isBiometricOperation: Boolean)
|
||||
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
* 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.biometric
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import com.getkeepsafe.taptargetview.TapTargetView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.database.exception.IODatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
|
||||
class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
|
||||
|
||||
private var mBuilderListener: BuilderListener? = null
|
||||
|
||||
private var mAdvancedUnlockEnabled = false
|
||||
private var mAutoOpenPromptEnabled = false
|
||||
|
||||
private var advancedUnlockManager: AdvancedUnlockManager? = null
|
||||
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE
|
||||
private var mAdvancedUnlockInfoView: AdvancedUnlockInfoView? = null
|
||||
|
||||
var databaseFileUri: Uri? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Manage setting to auto open biometric prompt
|
||||
*/
|
||||
private var mAutoOpenPrompt: Boolean = false
|
||||
get() {
|
||||
return field && mAutoOpenPromptEnabled
|
||||
}
|
||||
|
||||
// Variable to check if the prompt can be open (if the right activity is currently shown)
|
||||
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
|
||||
private var allowOpenBiometricPrompt = false
|
||||
|
||||
private lateinit var cipherDatabaseAction : CipherDatabaseAction
|
||||
|
||||
private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null
|
||||
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
private var mAllowAdvancedUnlockMenu = false
|
||||
private var mAddBiometricMenuInProgress = false
|
||||
|
||||
// Only keep connection when we request a device credential activity
|
||||
private var keepConnection = false
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context)
|
||||
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mBuilderListener = context as BuilderListener
|
||||
}
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + BuilderListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
retainInstance = true
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
||||
val rootView = inflater.cloneInContext(contextThemed)
|
||||
.inflate(R.layout.fragment_advanced_unlock, container, false)
|
||||
|
||||
mAdvancedUnlockInfoView = rootView.findViewById(R.id.advanced_unlock_view)
|
||||
|
||||
return rootView
|
||||
}
|
||||
|
||||
private data class ActivityResult(var requestCode: Int, var resultCode: Int, var data: Intent?)
|
||||
private var activityResult: ActivityResult? = null
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
// To wait resume
|
||||
activityResult = ActivityResult(requestCode, resultCode, data)
|
||||
keepConnection = false
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(requireContext())
|
||||
keepConnection = false
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// biometric menu
|
||||
if (mAllowAdvancedUnlockMenu)
|
||||
inflater.inflate(R.menu.advanced_unlock, menu)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_keystore_remove_key -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
deleteEncryptedDatabaseKey()
|
||||
}
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
fun loadDatabase(databaseUri: Uri?, autoOpenPrompt: Boolean) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
// To get device credential unlock result, only if same database uri
|
||||
if (databaseUri != null
|
||||
&& mAdvancedUnlockEnabled) {
|
||||
activityResult?.let {
|
||||
if (databaseUri == databaseFileUri) {
|
||||
advancedUnlockManager?.onActivityResult(it.requestCode, it.resultCode)
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
} ?: run {
|
||||
connect(databaseUri)
|
||||
this.mAutoOpenPrompt = autoOpenPrompt
|
||||
}
|
||||
} else {
|
||||
disconnect()
|
||||
}
|
||||
activityResult = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check unlock availability and change the current mode depending of device's state
|
||||
*/
|
||||
fun checkUnlockAvailability() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
allowOpenBiometricPrompt = true
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(requireContext())) {
|
||||
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
|
||||
|
||||
// biometric not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
|
||||
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
|
||||
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
|
||||
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
|
||||
} else {
|
||||
// biometric is available but not configured, show icon but in disabled state with some information
|
||||
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
|
||||
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
|
||||
} else {
|
||||
selectMode()
|
||||
}
|
||||
}
|
||||
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(requireContext())) {
|
||||
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
|
||||
if (AdvancedUnlockManager.isDeviceSecure(requireContext())) {
|
||||
selectMode()
|
||||
} else {
|
||||
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun selectMode() {
|
||||
// Check if fingerprint well init (be called the first time the fingerprint is configured
|
||||
// and the activity still active)
|
||||
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
|
||||
advancedUnlockManager = AdvancedUnlockManager { requireActivity() }
|
||||
// callback for fingerprint findings
|
||||
advancedUnlockManager?.advancedUnlockCallback = this
|
||||
}
|
||||
// Recheck to change the mode
|
||||
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
|
||||
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
} else {
|
||||
if (mBuilderListener?.conditionToStoreCredential() == true) {
|
||||
// listen for encryption
|
||||
toggleMode(Mode.STORE_CREDENTIAL)
|
||||
} else {
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
|
||||
// biometric available but no stored password found yet for this DB so show info don't listen
|
||||
toggleMode(if (containsCipher) {
|
||||
// listen for decryption
|
||||
Mode.EXTRACT_CREDENTIAL
|
||||
} else {
|
||||
// wait for typing
|
||||
Mode.WAIT_CREDENTIAL
|
||||
})
|
||||
}
|
||||
} ?: throw IODatabaseException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun toggleMode(newBiometricMode: Mode) {
|
||||
if (newBiometricMode != biometricMode) {
|
||||
biometricMode = newBiometricMode
|
||||
initAdvancedUnlockMode()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initNotAvailable() {
|
||||
showViews(false)
|
||||
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun openBiometricSetting() {
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
|
||||
requireContext().startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initSecurityUpdateRequired() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
|
||||
|
||||
openBiometricSetting()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initNotConfigured() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.configure_biometric)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
openBiometricSetting()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initKeyManagerNotAvailable() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
|
||||
|
||||
openBiometricSetting()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initWaitData() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
|
||||
requireContext().getString(R.string.credential_before_click_advanced_unlock_button))
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
|
||||
requireActivity().runOnUiThread {
|
||||
if (allowOpenBiometricPrompt) {
|
||||
if (cryptoPrompt.isDeviceCredentialOperation)
|
||||
keepConnection = true
|
||||
try {
|
||||
advancedUnlockManager?.openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to open advanced unlock prompt", e)
|
||||
setAdvancedUnlockedTitleView(R.string.advanced_unlock_prompt_not_initialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initEncryptData() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_store_credential)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockManager?.initEncryptData { cryptoPrompt ->
|
||||
// Set listener to open the biometric dialog and save credential
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
}
|
||||
} ?: throw Exception("AdvancedUnlockHelper not initialized")
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun initDecryptData() {
|
||||
showViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.open_advanced_unlock_prompt_unlock_database)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockManager?.let { unlockHelper ->
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
|
||||
cipherDatabase?.let {
|
||||
unlockHelper.initDecryptData(it.specParameters) { cryptoPrompt ->
|
||||
|
||||
// Set listener to open the biometric dialog and check credential
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
}
|
||||
|
||||
// Auto open the biometric prompt
|
||||
if (mAutoOpenPrompt) {
|
||||
mAutoOpenPrompt = false
|
||||
openAdvancedUnlockPrompt(cryptoPrompt)
|
||||
}
|
||||
}
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: throw IODatabaseException()
|
||||
} ?: throw Exception("AdvancedUnlockHelper not initialized")
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun initAdvancedUnlockMode() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mAllowAdvancedUnlockMenu = false
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
|
||||
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
|
||||
Mode.WAIT_CREDENTIAL -> initWaitData()
|
||||
Mode.STORE_CREDENTIAL -> initEncryptData()
|
||||
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||
}
|
||||
invalidateBiometricMenu()
|
||||
}
|
||||
}
|
||||
|
||||
private fun invalidateBiometricMenu() {
|
||||
// Show fingerprint key deletion
|
||||
if (!mAddBiometricMenuInProgress) {
|
||||
mAddBiometricMenuInProgress = true
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseUri) { containsCipher ->
|
||||
mAllowAdvancedUnlockMenu = containsCipher
|
||||
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
|
||||
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
mAddBiometricMenuInProgress = false
|
||||
requireActivity().invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun connect(databaseUri: Uri) {
|
||||
showViews(true)
|
||||
this.databaseFileUri = databaseUri
|
||||
cipherDatabaseListener = object: CipherDatabaseAction.DatabaseListener {
|
||||
override fun onDatabaseCleared() {
|
||||
deleteEncryptedDatabaseKey()
|
||||
}
|
||||
}
|
||||
cipherDatabaseAction.apply {
|
||||
reloadPreferences()
|
||||
cipherDatabaseListener?.let {
|
||||
registerDatabaseListener(it)
|
||||
}
|
||||
}
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun disconnect(hideViews: Boolean = true,
|
||||
closePrompt: Boolean = true) {
|
||||
this.databaseFileUri = null
|
||||
// Close the biometric prompt
|
||||
allowOpenBiometricPrompt = false
|
||||
if (closePrompt)
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
cipherDatabaseListener?.let {
|
||||
cipherDatabaseAction.unregisterDatabaseListener(it)
|
||||
}
|
||||
biometricMode = Mode.BIOMETRIC_UNAVAILABLE
|
||||
if (hideViews) {
|
||||
showViews(false)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun deleteEncryptedDatabaseKey() {
|
||||
allowOpenBiometricPrompt = false
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
requireActivity().runOnUiThread {
|
||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||
setAdvancedUnlockedMessageView(errString.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onAuthenticationFailed() {
|
||||
requireActivity().runOnUiThread {
|
||||
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
||||
setAdvancedUnlockedMessageView(R.string.advanced_unlock_not_recognized)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onAuthenticationSucceeded() {
|
||||
requireActivity().runOnUiThread {
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> {
|
||||
}
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
|
||||
}
|
||||
Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED -> {
|
||||
}
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> {
|
||||
}
|
||||
Mode.WAIT_CREDENTIAL -> {
|
||||
}
|
||||
Mode.STORE_CREDENTIAL -> {
|
||||
// newly store the entered password in encrypted way
|
||||
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
|
||||
advancedUnlockManager?.encryptData(credential)
|
||||
}
|
||||
AdvancedUnlockNotificationService.startServiceForTimeout(requireContext())
|
||||
}
|
||||
Mode.EXTRACT_CREDENTIAL -> {
|
||||
// retrieve the encrypted value from preferences
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.getCipherDatabase(databaseUri) { cipherDatabase ->
|
||||
cipherDatabase?.encryptedValue?.let { value ->
|
||||
advancedUnlockManager?.decryptData(value)
|
||||
} ?: deleteEncryptedDatabaseKey()
|
||||
}
|
||||
} ?: throw IODatabaseException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
mBuilderListener?.onCredentialEncrypted(databaseUri, encryptedValue, ivSpec)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleDecryptedResult(decryptedValue: String) {
|
||||
// Load database directly with password retrieve
|
||||
databaseFileUri?.let {
|
||||
mBuilderListener?.onCredentialDecrypted(it, decryptedValue)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
setAdvancedUnlockedMessageView(R.string.advanced_unlock_invalid_key)
|
||||
}
|
||||
|
||||
override fun onGenericException(e: Exception) {
|
||||
val errorMessage = e.cause?.localizedMessage ?: e.localizedMessage ?: ""
|
||||
setAdvancedUnlockedMessageView(errorMessage)
|
||||
}
|
||||
|
||||
private fun showViews(show: Boolean) {
|
||||
requireActivity().runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.visibility = if (show)
|
||||
View.VISIBLE
|
||||
else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun setAdvancedUnlockedTitleView(textId: Int) {
|
||||
requireActivity().runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.setTitle(textId)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun setAdvancedUnlockedMessageView(textId: Int) {
|
||||
requireActivity().runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.setMessage(textId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
|
||||
requireActivity().runOnUiThread {
|
||||
mAdvancedUnlockInfoView?.message = text
|
||||
}
|
||||
}
|
||||
|
||||
fun performEducation(passwordActivityEducation: PasswordActivityEducation,
|
||||
readOnlyEducationPerformed: Boolean,
|
||||
onEducationViewClick: ((TapTargetView?) -> Unit)? = null,
|
||||
onOuterViewClick: ((TapTargetView?) -> Unit)? = null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& !readOnlyEducationPerformed) {
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
|
||||
PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
|
||||
&& mAdvancedUnlockInfoView != null && mAdvancedUnlockInfoView?.visibility == View.VISIBLE
|
||||
&& mAdvancedUnlockInfoView?.unlockIconImageView != null
|
||||
&& passwordActivityEducation.checkAndPerformedBiometricEducation(mAdvancedUnlockInfoView!!.unlockIconImageView!!,
|
||||
onEducationViewClick,
|
||||
onOuterViewClick)
|
||||
}
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
BIOMETRIC_UNAVAILABLE,
|
||||
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
|
||||
DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED,
|
||||
KEY_MANAGER_UNAVAILABLE,
|
||||
WAIT_CREDENTIAL,
|
||||
STORE_CREDENTIAL,
|
||||
EXTRACT_CREDENTIAL
|
||||
}
|
||||
|
||||
interface BuilderListener {
|
||||
fun retrieveCredentialForEncryption(): String
|
||||
fun conditionToStoreCredential(): Boolean
|
||||
fun onCredentialEncrypted(databaseUri: Uri, encryptedCredential: String, ivSpec: String)
|
||||
fun onCredentialDecrypted(databaseUri: Uri, decryptedCredential: String)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (!keepConnection) {
|
||||
// If close prompt, bug "user not authenticated in Android R"
|
||||
disconnect(false)
|
||||
advancedUnlockManager = null
|
||||
}
|
||||
}
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
mAdvancedUnlockInfoView = null
|
||||
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
disconnect()
|
||||
advancedUnlockManager = null
|
||||
mBuilderListener = null
|
||||
}
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mBuilderListener = null
|
||||
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = AdvancedUnlockFragment::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
* 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.biometric
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.*
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import java.security.KeyStore
|
||||
import java.security.UnrecoverableKeyException
|
||||
import java.util.concurrent.Executors
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class AdvancedUnlockManager(private var retrieveContext: () -> FragmentActivity) {
|
||||
|
||||
private var keyStore: KeyStore? = null
|
||||
private var keyGenerator: KeyGenerator? = null
|
||||
private var cipher: Cipher? = null
|
||||
|
||||
private var biometricPrompt: BiometricPrompt? = null
|
||||
private var authenticationCallback = object: BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
advancedUnlockCallback?.onAuthenticationSucceeded()
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
advancedUnlockCallback?.onAuthenticationFailed()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
advancedUnlockCallback?.onAuthenticationError(errorCode, errString)
|
||||
}
|
||||
}
|
||||
|
||||
var advancedUnlockCallback: AdvancedUnlockCallback? = null
|
||||
|
||||
private var isKeyManagerInit = false
|
||||
|
||||
private val biometricUnlockEnable = PreferencesUtil.isBiometricUnlockEnable(retrieveContext())
|
||||
private val deviceCredentialUnlockEnable = PreferencesUtil.isDeviceCredentialUnlockEnable(retrieveContext())
|
||||
|
||||
val isKeyManagerInitialized: Boolean
|
||||
get() {
|
||||
if (!isKeyManagerInit) {
|
||||
advancedUnlockCallback?.onGenericException(Exception("Biometric not initialized"))
|
||||
}
|
||||
return isKeyManagerInit
|
||||
}
|
||||
|
||||
private fun isBiometricOperation(): Boolean {
|
||||
return biometricUnlockEnable || isDeviceCredentialBiometricOperation()
|
||||
}
|
||||
|
||||
// Since Android 30, device credential is also a biometric operation
|
||||
private fun isDeviceCredentialOperation(): Boolean {
|
||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
||||
&& deviceCredentialUnlockEnable
|
||||
}
|
||||
|
||||
private fun isDeviceCredentialBiometricOperation(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& deviceCredentialUnlockEnable
|
||||
}
|
||||
|
||||
init {
|
||||
if (isDeviceSecure(retrieveContext())
|
||||
&& (biometricUnlockEnable || deviceCredentialUnlockEnable)) {
|
||||
try {
|
||||
this.keyStore = KeyStore.getInstance(ADVANCED_UNLOCK_KEYSTORE)
|
||||
this.keyGenerator = KeyGenerator.getInstance(ADVANCED_UNLOCK_KEY_ALGORITHM, ADVANCED_UNLOCK_KEYSTORE)
|
||||
this.cipher = Cipher.getInstance(
|
||||
ADVANCED_UNLOCK_KEY_ALGORITHM + "/"
|
||||
+ ADVANCED_UNLOCK_BLOCKS_MODES + "/"
|
||||
+ ADVANCED_UNLOCK_ENCRYPTION_PADDING)
|
||||
isKeyManagerInit = (keyStore != null
|
||||
&& keyGenerator != null
|
||||
&& cipher != null)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize the keystore", e)
|
||||
isKeyManagerInit = false
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
} else {
|
||||
// really not much to do when no fingerprint support found
|
||||
isKeyManagerInit = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSecretKey(): SecretKey? {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
// Create new key if needed
|
||||
keyStore?.let { keyStore ->
|
||||
keyStore.load(null)
|
||||
|
||||
try {
|
||||
if (!keyStore.containsAlias(ADVANCED_UNLOCK_KEYSTORE_KEY)) {
|
||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||
// and the constrains (purposes) in the constructor of the Builder
|
||||
keyGenerator?.init(
|
||||
KeyGenParameterSpec.Builder(
|
||||
ADVANCED_UNLOCK_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||
// Require the user to authenticate with a fingerprint to authorize every use
|
||||
// of the key, don't use it for device credential because it's the user authentication
|
||||
.apply {
|
||||
if (biometricUnlockEnable) {
|
||||
setUserAuthenticationRequired(true)
|
||||
}
|
||||
}
|
||||
.build())
|
||||
keyGenerator?.generateKey()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to create a key in keystore", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
|
||||
return keyStore.getKey(ADVANCED_UNLOCK_KEYSTORE_KEY, null) as SecretKey?
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve the key in keystore", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun initEncryptData(actionIfCypherInit
|
||||
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.let { cipher ->
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
|
||||
actionIfCypherInit.invoke(
|
||||
AdvancedUnlockCryptoPrompt(
|
||||
cipher,
|
||||
R.string.advanced_unlock_prompt_store_credential_title,
|
||||
R.string.advanced_unlock_prompt_store_credential_message,
|
||||
isDeviceCredentialOperation(), isBiometricOperation())
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
||||
advancedUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
|
||||
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun encryptData(value: String) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val encrypted = cipher?.doFinal(value.toByteArray())
|
||||
val encryptedBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
||||
|
||||
// passes updated iv spec on to callback so this can be stored for decryption
|
||||
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
|
||||
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
|
||||
advancedUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to encrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
|
||||
: (cryptoPrompt: AdvancedUnlockCryptoPrompt) -> Unit) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// important to restore spec here that was used for decryption
|
||||
val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP)
|
||||
val spec = IvParameterSpec(iv)
|
||||
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.let { cipher ->
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
||||
|
||||
actionIfCypherInit.invoke(
|
||||
AdvancedUnlockCryptoPrompt(
|
||||
cipher,
|
||||
R.string.advanced_unlock_prompt_extract_credential_title,
|
||||
null,
|
||||
isDeviceCredentialOperation(), isBiometricOperation())
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
|
||||
deleteKeystoreKey()
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
|
||||
advancedUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun decryptData(encryptedValue: String) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// actual decryption here
|
||||
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
|
||||
cipher?.doFinal(encrypted)?.let { decrypted ->
|
||||
advancedUnlockCallback?.handleDecryptedResult(String(decrypted))
|
||||
}
|
||||
} catch (badPaddingException: BadPaddingException) {
|
||||
Log.e(TAG, "Unable to decrypt data", badPaddingException)
|
||||
advancedUnlockCallback?.onInvalidKeyException(badPaddingException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to decrypt data", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteKeystoreKey() {
|
||||
try {
|
||||
keyStore?.load(null)
|
||||
keyStore?.deleteEntry(ADVANCED_UNLOCK_KEYSTORE_KEY)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to delete entry key in keystore", e)
|
||||
advancedUnlockCallback?.onGenericException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Synchronized
|
||||
fun openAdvancedUnlockPrompt(cryptoPrompt: AdvancedUnlockCryptoPrompt) {
|
||||
// Init advanced unlock prompt
|
||||
if (biometricPrompt == null) {
|
||||
biometricPrompt = BiometricPrompt(retrieveContext(),
|
||||
Executors.newSingleThreadExecutor(),
|
||||
authenticationCallback)
|
||||
}
|
||||
|
||||
val promptTitle = retrieveContext().getString(cryptoPrompt.promptTitleId)
|
||||
val promptDescription = cryptoPrompt.promptDescriptionId?.let { descriptionId ->
|
||||
retrieveContext().getString(descriptionId)
|
||||
} ?: ""
|
||||
|
||||
if (cryptoPrompt.isBiometricOperation) {
|
||||
val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(promptTitle)
|
||||
if (promptDescription.isNotEmpty())
|
||||
setDescription(promptDescription)
|
||||
setConfirmationRequired(false)
|
||||
if (isDeviceCredentialBiometricOperation()) {
|
||||
setAllowedAuthenticators(DEVICE_CREDENTIAL)
|
||||
} else {
|
||||
setNegativeButtonText(retrieveContext().getString(android.R.string.cancel))
|
||||
}
|
||||
}.build()
|
||||
biometricPrompt?.authenticate(
|
||||
promptInfoExtractCredential,
|
||||
BiometricPrompt.CryptoObject(cryptoPrompt.cipher))
|
||||
}
|
||||
else if (cryptoPrompt.isDeviceCredentialOperation) {
|
||||
val keyGuardManager = ContextCompat.getSystemService(retrieveContext(), KeyguardManager::class.java)
|
||||
retrieveContext().startActivityForResult(
|
||||
keyGuardManager?.createConfirmDeviceCredentialIntent(promptTitle, promptDescription),
|
||||
REQUEST_DEVICE_CREDENTIAL)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun onActivityResult(requestCode: Int, resultCode: Int) {
|
||||
if (requestCode == REQUEST_DEVICE_CREDENTIAL) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
advancedUnlockCallback?.onAuthenticationSucceeded()
|
||||
} else {
|
||||
advancedUnlockCallback?.onAuthenticationFailed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeBiometricPrompt() {
|
||||
biometricPrompt?.cancelAuthentication()
|
||||
}
|
||||
|
||||
interface AdvancedUnlockErrorCallback {
|
||||
fun onInvalidKeyException(e: Exception)
|
||||
fun onGenericException(e: Exception)
|
||||
}
|
||||
|
||||
interface AdvancedUnlockCallback : AdvancedUnlockErrorCallback {
|
||||
fun onAuthenticationSucceeded()
|
||||
fun onAuthenticationFailed()
|
||||
fun onAuthenticationError(errorCode: Int, errString: CharSequence)
|
||||
fun handleEncryptedResult(encryptedValue: String, ivSpec: String)
|
||||
fun handleDecryptedResult(decryptedValue: String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = AdvancedUnlockManager::class.java.name
|
||||
|
||||
private const val ADVANCED_UNLOCK_KEYSTORE = "AndroidKeyStore"
|
||||
private const val ADVANCED_UNLOCK_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
|
||||
private const val ADVANCED_UNLOCK_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
||||
private const val ADVANCED_UNLOCK_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
|
||||
private const val ADVANCED_UNLOCK_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||
|
||||
private const val REQUEST_DEVICE_CREDENTIAL = 556
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun canAuthenticate(context: Context): Int {
|
||||
return try {
|
||||
BiometricManager.from(context).canAuthenticate(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
|
||||
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
|
||||
} else {
|
||||
BIOMETRIC_STRONG
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with strong biometric.", e)
|
||||
try {
|
||||
BiometricManager.from(context).canAuthenticate(
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
&& PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
|
||||
BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
} else {
|
||||
BIOMETRIC_WEAK
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with weak biometric.", e)
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun isDeviceSecure(context: Context): Boolean {
|
||||
val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
|
||||
return keyguardManager?.isDeviceSecure ?: false
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun biometricUnlockSupported(context: Context): Boolean {
|
||||
val biometricCanAuthenticate = try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with strong biometric.", e)
|
||||
try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with weak biometric.", e)
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
}
|
||||
}
|
||||
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun deviceCredentialUnlockSupported(context: Context): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate(DEVICE_CREDENTIAL)
|
||||
return (biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ContextCompat.getSystemService(context, KeyguardManager::class.java)?.apply {
|
||||
return isDeviceSecure
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry key in keystore
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
fun deleteEntryKeyInKeystoreForBiometric(fragmentActivity: FragmentActivity,
|
||||
advancedCallback: AdvancedUnlockErrorCallback) {
|
||||
AdvancedUnlockManager{ fragmentActivity }.apply {
|
||||
advancedUnlockCallback = object : AdvancedUnlockCallback {
|
||||
override fun onAuthenticationSucceeded() {}
|
||||
|
||||
override fun onAuthenticationFailed() {}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {}
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
|
||||
|
||||
override fun handleDecryptedResult(decryptedValue: String) {}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
advancedCallback.onInvalidKeyException(e)
|
||||
}
|
||||
|
||||
override fun onGenericException(e: Exception) {
|
||||
advancedCallback.onGenericException(e)
|
||||
}
|
||||
}
|
||||
deleteKeystoreKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,404 +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.biometric
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class AdvancedUnlockedManager(var context: FragmentActivity,
|
||||
var databaseFileUri: Uri,
|
||||
private var advancedUnlockInfoView: AdvancedUnlockInfoView?,
|
||||
private var checkboxPasswordView: CompoundButton?,
|
||||
private var onCheckedPasswordChangeListener: CompoundButton.OnCheckedChangeListener? = null,
|
||||
var passwordView: TextView?,
|
||||
private var loadDatabaseAfterRegisterCredentials: (encryptedPassword: String?, ivSpec: String?) -> Unit,
|
||||
private var loadDatabaseAfterRetrieveCredentials: (decryptedPassword: String?) -> Unit)
|
||||
: BiometricUnlockDatabaseHelper.BiometricUnlockCallback {
|
||||
|
||||
private var biometricUnlockDatabaseHelper: BiometricUnlockDatabaseHelper? = null
|
||||
private var biometricMode: Mode = Mode.BIOMETRIC_UNAVAILABLE
|
||||
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
private var mAllowAdvancedUnlockMenu = false
|
||||
private var mAddBiometricMenuInProgress = false
|
||||
|
||||
/**
|
||||
* Manage setting to auto open biometric prompt
|
||||
*/
|
||||
private var biometricPromptAutoOpenPreference = PreferencesUtil.isBiometricPromptAutoOpenEnable(context)
|
||||
var isBiometricPromptAutoOpenEnable: Boolean = false
|
||||
get() {
|
||||
return field && biometricPromptAutoOpenPreference
|
||||
}
|
||||
|
||||
// Variable to check if the prompt can be open (if the right activity is currently shown)
|
||||
// checkBiometricAvailability() allows open biometric prompt and onDestroy() removes the authorization
|
||||
private var allowOpenBiometricPrompt = false
|
||||
|
||||
private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext)
|
||||
|
||||
init {
|
||||
// Add a check listener to change fingerprint mode
|
||||
checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked ->
|
||||
checkBiometricAvailability()
|
||||
// Add old listener to enable the button, only be call here because of onCheckedChange bug
|
||||
onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check biometric availability and change the current mode depending of device's state
|
||||
*/
|
||||
fun checkBiometricAvailability() {
|
||||
|
||||
// biometric not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
val biometricCanAuthenticate = BiometricUnlockDatabaseHelper.canAuthenticate(context)
|
||||
allowOpenBiometricPrompt = true
|
||||
|
||||
if (!PreferencesUtil.isBiometricUnlockEnable(context)
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
|
||||
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED){
|
||||
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
|
||||
} else {
|
||||
// biometric is available but not configured, show icon but in disabled state with some information
|
||||
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
|
||||
toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED)
|
||||
} else {
|
||||
// Check if fingerprint well init (be called the first time the fingerprint is configured
|
||||
// and the activity still active)
|
||||
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
|
||||
biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context)
|
||||
// callback for fingerprint findings
|
||||
biometricUnlockDatabaseHelper?.biometricUnlockCallback = this
|
||||
biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback
|
||||
}
|
||||
// Recheck to change the mode
|
||||
if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) {
|
||||
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
} else {
|
||||
if (checkboxPasswordView?.isChecked == true) {
|
||||
// listen for encryption
|
||||
toggleMode(Mode.STORE_CREDENTIAL)
|
||||
} else {
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
|
||||
// biometric available but no stored password found yet for this DB so show info don't listen
|
||||
toggleMode(if (containsCipher) {
|
||||
// listen for decryption
|
||||
Mode.EXTRACT_CREDENTIAL
|
||||
} else {
|
||||
// wait for typing
|
||||
Mode.WAIT_CREDENTIAL
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun toggleMode(newBiometricMode: Mode) {
|
||||
if (newBiometricMode != biometricMode) {
|
||||
biometricMode = newBiometricMode
|
||||
initBiometricMode()
|
||||
}
|
||||
}
|
||||
|
||||
private val biometricAuthenticationCallback = object : BiometricPrompt.AuthenticationCallback () {
|
||||
|
||||
override fun onAuthenticationError(
|
||||
errorCode: Int,
|
||||
errString: CharSequence) {
|
||||
context.runOnUiThread {
|
||||
Log.e(TAG, "Biometric authentication error. Code : $errorCode Error : $errString")
|
||||
setAdvancedUnlockedMessageView(errString.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuthenticationFailed() {
|
||||
context.runOnUiThread {
|
||||
Log.e(TAG, "Biometric authentication failed, biometric not recognized")
|
||||
setAdvancedUnlockedMessageView(R.string.biometric_not_recognized)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
context.runOnUiThread {
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> {
|
||||
}
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> {
|
||||
}
|
||||
Mode.BIOMETRIC_NOT_CONFIGURED -> {
|
||||
}
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> {
|
||||
}
|
||||
Mode.WAIT_CREDENTIAL -> {
|
||||
}
|
||||
Mode.STORE_CREDENTIAL -> {
|
||||
// newly store the entered password in encrypted way
|
||||
biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString())
|
||||
}
|
||||
Mode.EXTRACT_CREDENTIAL -> {
|
||||
// retrieve the encrypted value from preferences
|
||||
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
|
||||
it?.encryptedValue?.let { value ->
|
||||
biometricUnlockDatabaseHelper?.decryptData(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initNotAvailable() {
|
||||
showFingerPrintViews(false)
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||
}
|
||||
|
||||
private fun initSecurityUpdateRequired() {
|
||||
showFingerPrintViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.biometric_security_update_required)
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initNotConfigured() {
|
||||
showFingerPrintViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.configure_biometric)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initKeyManagerNotAvailable() {
|
||||
showFingerPrintViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.keystore_not_accessible)
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
private fun initWaitData() {
|
||||
showFingerPrintViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
biometricAuthenticationCallback.onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
|
||||
context.getString(R.string.credential_before_click_biometric_button))
|
||||
}
|
||||
}
|
||||
|
||||
private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?,
|
||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
||||
promptInfo: BiometricPrompt.PromptInfo) {
|
||||
context.runOnUiThread {
|
||||
if (allowOpenBiometricPrompt) {
|
||||
if (biometricPrompt != null) {
|
||||
if (cryptoObject != null) {
|
||||
biometricPrompt.authenticate(promptInfo, cryptoObject)
|
||||
} else {
|
||||
setAdvancedUnlockedTitleView(R.string.crypto_object_not_initialized)
|
||||
}
|
||||
} else {
|
||||
setAdvancedUnlockedTitleView(R.string.biometric_prompt_not_initialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initEncryptData() {
|
||||
showFingerPrintViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.open_biometric_prompt_store_credential)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
biometricUnlockDatabaseHelper?.initEncryptData { biometricPrompt, cryptoObject, promptInfo ->
|
||||
// Set listener to open the biometric dialog and save credential
|
||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDecryptData() {
|
||||
showFingerPrintViews(true)
|
||||
setAdvancedUnlockedTitleView(R.string.open_biometric_prompt_unlock_database)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
if (biometricUnlockDatabaseHelper != null) {
|
||||
cipherDatabaseAction.getCipherDatabase(databaseFileUri) {
|
||||
|
||||
it?.specParameters?.let { specs ->
|
||||
biometricUnlockDatabaseHelper?.initDecryptData(specs) { biometricPrompt, cryptoObject, promptInfo ->
|
||||
|
||||
// Set listener to open the biometric dialog and check credential
|
||||
advancedUnlockInfoView?.setIconViewClickListener { _ ->
|
||||
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
|
||||
}
|
||||
|
||||
// Auto open the biometric prompt
|
||||
if (isBiometricPromptAutoOpenEnable) {
|
||||
isBiometricPromptAutoOpenEnable = false
|
||||
openBiometricPrompt(biometricPrompt, cryptoObject, promptInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun initBiometricMode() {
|
||||
mAllowAdvancedUnlockMenu = false
|
||||
when (biometricMode) {
|
||||
Mode.BIOMETRIC_UNAVAILABLE -> initNotAvailable()
|
||||
Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED -> initSecurityUpdateRequired()
|
||||
Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured()
|
||||
Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable()
|
||||
Mode.WAIT_CREDENTIAL -> initWaitData()
|
||||
Mode.STORE_CREDENTIAL -> initEncryptData()
|
||||
Mode.EXTRACT_CREDENTIAL -> initDecryptData()
|
||||
}
|
||||
|
||||
invalidateBiometricMenu()
|
||||
}
|
||||
|
||||
private fun invalidateBiometricMenu() {
|
||||
// Show fingerprint key deletion
|
||||
if (!mAddBiometricMenuInProgress) {
|
||||
mAddBiometricMenuInProgress = true
|
||||
cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher ->
|
||||
mAllowAdvancedUnlockMenu = containsCipher
|
||||
&& (biometricMode != Mode.BIOMETRIC_UNAVAILABLE
|
||||
&& biometricMode != Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
mAddBiometricMenuInProgress = false
|
||||
context.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
// Close the biometric prompt
|
||||
allowOpenBiometricPrompt = false
|
||||
biometricUnlockDatabaseHelper?.closeBiometricPrompt()
|
||||
// Restore the checked listener
|
||||
checkboxPasswordView?.setOnCheckedChangeListener(onCheckedPasswordChangeListener)
|
||||
}
|
||||
|
||||
fun inflateOptionsMenu(menuInflater: MenuInflater, menu: Menu) {
|
||||
if (mAllowAdvancedUnlockMenu)
|
||||
menuInflater.inflate(R.menu.advanced_unlock, menu)
|
||||
}
|
||||
|
||||
fun deleteEntryKey() {
|
||||
allowOpenBiometricPrompt = false
|
||||
advancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||
biometricUnlockDatabaseHelper?.closeBiometricPrompt()
|
||||
biometricUnlockDatabaseHelper?.deleteEntryKey()
|
||||
cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri) {
|
||||
checkBiometricAvailability()
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {
|
||||
loadDatabaseAfterRegisterCredentials.invoke(encryptedValue, ivSpec)
|
||||
}
|
||||
|
||||
override fun handleDecryptedResult(decryptedValue: String) {
|
||||
// Load database directly with password retrieve
|
||||
loadDatabaseAfterRetrieveCredentials.invoke(decryptedValue)
|
||||
}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
setAdvancedUnlockedMessageView(R.string.biometric_invalid_key)
|
||||
}
|
||||
|
||||
override fun onBiometricException(e: Exception) {
|
||||
e.localizedMessage?.let {
|
||||
setAdvancedUnlockedMessageView(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showFingerPrintViews(show: Boolean) {
|
||||
context.runOnUiThread {
|
||||
advancedUnlockInfoView?.visibility = if (show) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAdvancedUnlockedTitleView(textId: Int) {
|
||||
context.runOnUiThread {
|
||||
advancedUnlockInfoView?.setTitle(textId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAdvancedUnlockedMessageView(textId: Int) {
|
||||
context.runOnUiThread {
|
||||
advancedUnlockInfoView?.setMessage(textId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAdvancedUnlockedMessageView(text: CharSequence) {
|
||||
context.runOnUiThread {
|
||||
advancedUnlockInfoView?.message = text
|
||||
}
|
||||
}
|
||||
|
||||
enum class Mode {
|
||||
BIOMETRIC_UNAVAILABLE,
|
||||
BIOMETRIC_SECURITY_UPDATE_REQUIRED,
|
||||
BIOMETRIC_NOT_CONFIGURED,
|
||||
KEY_MANAGER_UNAVAILABLE,
|
||||
WAIT_CREDENTIAL,
|
||||
STORE_CREDENTIAL,
|
||||
EXTRACT_CREDENTIAL
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = AdvancedUnlockedManager::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -1,355 +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.biometric
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.kunzisoft.keepass.R
|
||||
import java.security.KeyStore
|
||||
import java.security.UnrecoverableKeyException
|
||||
import java.util.concurrent.Executors
|
||||
import javax.crypto.BadPaddingException
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.KeyGenerator
|
||||
import javax.crypto.SecretKey
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) {
|
||||
|
||||
private var biometricPrompt: BiometricPrompt? = null
|
||||
|
||||
private var keyStore: KeyStore? = null
|
||||
private var keyGenerator: KeyGenerator? = null
|
||||
private var cipher: Cipher? = null
|
||||
private var keyguardManager: KeyguardManager? = null
|
||||
private var cryptoObject: BiometricPrompt.CryptoObject? = null
|
||||
|
||||
private var isKeyManagerInit = false
|
||||
var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null
|
||||
var biometricUnlockCallback: BiometricUnlockCallback? = null
|
||||
|
||||
private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(context.getString(R.string.biometric_prompt_store_credential_title))
|
||||
setDescription(context.getString(R.string.biometric_prompt_store_credential_message))
|
||||
setConfirmationRequired(true)
|
||||
// TODO device credential #102 #152
|
||||
/*
|
||||
if (keyguardManager?.isDeviceSecure == true)
|
||||
setDeviceCredentialAllowed(true)
|
||||
else
|
||||
*/
|
||||
setNegativeButtonText(context.getString(android.R.string.cancel))
|
||||
}.build()
|
||||
|
||||
private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply {
|
||||
setTitle(context.getString(R.string.biometric_prompt_extract_credential_title))
|
||||
//setDescription(context.getString(R.string.biometric_prompt_extract_credential_message))
|
||||
setConfirmationRequired(false)
|
||||
// TODO device credential #102 #152
|
||||
/*
|
||||
if (keyguardManager?.isDeviceSecure == true)
|
||||
setDeviceCredentialAllowed(true)
|
||||
else
|
||||
*/
|
||||
setNegativeButtonText(context.getString(android.R.string.cancel))
|
||||
}.build()
|
||||
|
||||
val isKeyManagerInitialized: Boolean
|
||||
get() {
|
||||
if (!isKeyManagerInit) {
|
||||
biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized"))
|
||||
}
|
||||
return isKeyManagerInit
|
||||
}
|
||||
|
||||
init {
|
||||
if (allowInitKeyStore(context)) {
|
||||
this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
|
||||
try {
|
||||
this.keyStore = KeyStore.getInstance(BIOMETRIC_KEYSTORE)
|
||||
this.keyGenerator = KeyGenerator.getInstance(BIOMETRIC_KEY_ALGORITHM, BIOMETRIC_KEYSTORE)
|
||||
this.cipher = Cipher.getInstance(
|
||||
BIOMETRIC_KEY_ALGORITHM + "/"
|
||||
+ BIOMETRIC_BLOCKS_MODES + "/"
|
||||
+ BIOMETRIC_ENCRYPTION_PADDING)
|
||||
this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!)
|
||||
isKeyManagerInit = (keyStore != null
|
||||
&& keyGenerator != null
|
||||
&& cipher != null)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize the keystore", e)
|
||||
isKeyManagerInit = false
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
} else {
|
||||
// really not much to do when no fingerprint support found
|
||||
isKeyManagerInit = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSecretKey(): SecretKey? {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return null
|
||||
}
|
||||
try {
|
||||
// Create new key if needed
|
||||
keyStore?.let { keyStore ->
|
||||
keyStore.load(null)
|
||||
|
||||
try {
|
||||
if (!keyStore.containsAlias(BIOMETRIC_KEYSTORE_KEY)) {
|
||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||
// and the constrains (purposes) in the constructor of the Builder
|
||||
keyGenerator?.init(
|
||||
KeyGenParameterSpec.Builder(
|
||||
BIOMETRIC_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||
// Require the user to authenticate with a fingerprint to authorize every use
|
||||
// of the key
|
||||
.setUserAuthenticationRequired(true)
|
||||
.build())
|
||||
keyGenerator?.generateKey()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to create a key in keystore", e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
|
||||
return keyStore.getKey(BIOMETRIC_KEYSTORE_KEY, null) as SecretKey?
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to retrieve the key in keystore", e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun initEncryptData(actionIfCypherInit
|
||||
: (biometricPrompt: BiometricPrompt?,
|
||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
||||
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
|
||||
initBiometricPrompt()
|
||||
actionIfCypherInit.invoke(biometricPrompt, cryptoObject, promptInfoStoreCredential)
|
||||
}
|
||||
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", unrecoverableKeyException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(unrecoverableKeyException)
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", invalidKeyException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize encrypt data", e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun encryptData(value: String) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
val encrypted = cipher?.doFinal(value.toByteArray())
|
||||
val encryptedBase64 = Base64.encodeToString(encrypted, Base64.NO_WRAP)
|
||||
|
||||
// passes updated iv spec on to callback so this can be stored for decryption
|
||||
cipher?.parameters?.getParameterSpec(IvParameterSpec::class.java)?.let{ spec ->
|
||||
val ivSpecValue = Base64.encodeToString(spec.iv, Base64.NO_WRAP)
|
||||
biometricUnlockCallback?.handleEncryptedResult(encryptedBase64, ivSpecValue)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to encrypt data", e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun initDecryptData(ivSpecValue: String, actionIfCypherInit
|
||||
: (biometricPrompt: BiometricPrompt?,
|
||||
cryptoObject: BiometricPrompt.CryptoObject?,
|
||||
promptInfo: BiometricPrompt.PromptInfo) -> Unit) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// important to restore spec here that was used for decryption
|
||||
val iv = Base64.decode(ivSpecValue, Base64.NO_WRAP)
|
||||
val spec = IvParameterSpec(iv)
|
||||
|
||||
getSecretKey()?.let { secretKey ->
|
||||
cipher?.init(Cipher.DECRYPT_MODE, secretKey, spec)
|
||||
|
||||
initBiometricPrompt()
|
||||
actionIfCypherInit.invoke(biometricPrompt, cryptoObject, promptInfoExtractCredential)
|
||||
}
|
||||
|
||||
} catch (unrecoverableKeyException: UnrecoverableKeyException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", unrecoverableKeyException)
|
||||
deleteEntryKey()
|
||||
} catch (invalidKeyException: KeyPermanentlyInvalidatedException) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", invalidKeyException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(invalidKeyException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to initialize decrypt data", e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun decryptData(encryptedValue: String) {
|
||||
if (!isKeyManagerInitialized) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
// actual decryption here
|
||||
val encrypted = Base64.decode(encryptedValue, Base64.NO_WRAP)
|
||||
cipher?.doFinal(encrypted)?.let { decrypted ->
|
||||
biometricUnlockCallback?.handleDecryptedResult(String(decrypted))
|
||||
}
|
||||
} catch (badPaddingException: BadPaddingException) {
|
||||
Log.e(TAG, "Unable to decrypt data", badPaddingException)
|
||||
biometricUnlockCallback?.onInvalidKeyException(badPaddingException)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to decrypt data", e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteEntryKey() {
|
||||
try {
|
||||
keyStore?.load(null)
|
||||
keyStore?.deleteEntry(BIOMETRIC_KEYSTORE_KEY)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to delete entry key in keystore", e)
|
||||
biometricUnlockCallback?.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun initBiometricPrompt() {
|
||||
if (biometricPrompt == null) {
|
||||
authenticationCallback?.let {
|
||||
biometricPrompt = BiometricPrompt(context, Executors.newSingleThreadExecutor(), it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun closeBiometricPrompt() {
|
||||
biometricPrompt?.cancelAuthentication()
|
||||
}
|
||||
|
||||
interface BiometricUnlockErrorCallback {
|
||||
fun onInvalidKeyException(e: Exception)
|
||||
fun onBiometricException(e: Exception)
|
||||
}
|
||||
|
||||
interface BiometricUnlockCallback : BiometricUnlockErrorCallback {
|
||||
fun handleEncryptedResult(encryptedValue: String, ivSpec: String)
|
||||
fun handleDecryptedResult(decryptedValue: String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = BiometricUnlockDatabaseHelper::class.java.name
|
||||
|
||||
private const val BIOMETRIC_KEYSTORE = "AndroidKeyStore"
|
||||
private const val BIOMETRIC_KEYSTORE_KEY = "com.kunzisoft.keepass.biometric.key"
|
||||
private const val BIOMETRIC_KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
||||
private const val BIOMETRIC_BLOCKS_MODES = KeyProperties.BLOCK_MODE_CBC
|
||||
private const val BIOMETRIC_ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||
|
||||
fun canAuthenticate(context: Context): Int {
|
||||
return try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_STRONG)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with strong biometric.", e)
|
||||
try {
|
||||
BiometricManager.from(context).canAuthenticate(BIOMETRIC_WEAK)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to authenticate with weak biometric.", e)
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun allowInitKeyStore(context: Context): Boolean {
|
||||
val biometricCanAuthenticate = canAuthenticate(context)
|
||||
return ( biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
)
|
||||
}
|
||||
|
||||
fun unlockSupported(context: Context): Boolean {
|
||||
val biometricCanAuthenticate = canAuthenticate(context)
|
||||
return ( biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_STATUS_UNKNOWN
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry key in keystore
|
||||
*/
|
||||
fun deleteEntryKeyInKeystoreForBiometric(context: FragmentActivity,
|
||||
biometricCallback: BiometricUnlockErrorCallback) {
|
||||
BiometricUnlockDatabaseHelper(context).apply {
|
||||
biometricUnlockCallback = object : BiometricUnlockCallback {
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: String, ivSpec: String) {}
|
||||
|
||||
override fun handleDecryptedResult(decryptedValue: String) {}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
biometricCallback.onInvalidKeyException(e)
|
||||
}
|
||||
|
||||
override fun onBiometricException(e: Exception) {
|
||||
biometricCallback.onBiometricException(e)
|
||||
}
|
||||
}
|
||||
deleteEntryKey()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class AesKdf : KdfEngine() {
|
||||
}
|
||||
}
|
||||
|
||||
override val defaultKeyRounds: Long = 6000L
|
||||
override val defaultKeyRounds: Long = 500000L
|
||||
|
||||
override fun getName(resources: Resources): String {
|
||||
return resources.getString(R.string.kdf_AES)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.crypto.keyDerivation
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.StringRes
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.stream.bytes16ToUuid
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
@@ -27,7 +28,11 @@ import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
class Argon2Kdf(private val type: Type) : KdfEngine() {
|
||||
|
||||
init {
|
||||
uuid = type.CIPHER_UUID
|
||||
}
|
||||
|
||||
override val defaultParameters: KdfParameters
|
||||
get() {
|
||||
@@ -45,12 +50,8 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
override val defaultKeyRounds: Long
|
||||
get() = DEFAULT_ITERATIONS
|
||||
|
||||
init {
|
||||
uuid = CIPHER_UUID
|
||||
}
|
||||
|
||||
override fun getName(resources: Resources): String {
|
||||
return resources.getString(R.string.kdf_Argon2)
|
||||
return resources.getString(type.nameId)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -72,7 +73,9 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
|
||||
val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
|
||||
|
||||
return Argon2Native.transformKey(masterKey,
|
||||
return Argon2Native.transformKey(
|
||||
type,
|
||||
masterKey,
|
||||
salt,
|
||||
parallelism,
|
||||
memory,
|
||||
@@ -141,9 +144,8 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
override val maxParallelism: Long
|
||||
get() = MAX_PARALLELISM
|
||||
|
||||
companion object {
|
||||
|
||||
val CIPHER_UUID: UUID = bytes16ToUuid(
|
||||
enum class Type(val CIPHER_UUID: UUID, @StringRes val nameId: Int) {
|
||||
ARGON2_D(bytes16ToUuid(
|
||||
byteArrayOf(0xEF.toByte(),
|
||||
0x63.toByte(),
|
||||
0x6D.toByte(),
|
||||
@@ -159,7 +161,27 @@ class Argon2Kdf internal constructor() : KdfEngine() {
|
||||
0x03.toByte(),
|
||||
0xE3.toByte(),
|
||||
0x0A.toByte(),
|
||||
0x0C.toByte()))
|
||||
0x0C.toByte())), R.string.kdf_Argon2d),
|
||||
ARGON2_ID(bytes16ToUuid(
|
||||
byteArrayOf(0x9E.toByte(),
|
||||
0x29.toByte(),
|
||||
0x8B.toByte(),
|
||||
0x19.toByte(),
|
||||
0x56.toByte(),
|
||||
0xDB.toByte(),
|
||||
0x47.toByte(),
|
||||
0x73.toByte(),
|
||||
0xB2.toByte(),
|
||||
0x3D.toByte(),
|
||||
0xFC.toByte(),
|
||||
0x3E.toByte(),
|
||||
0xC6.toByte(),
|
||||
0xF0.toByte(),
|
||||
0xA1.toByte(),
|
||||
0xE6.toByte())), R.string.kdf_Argon2id);
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val PARAM_SALT = "S" // byte[]
|
||||
private const val PARAM_PARALLELISM = "P" // UInt32
|
||||
|
||||
@@ -26,12 +26,29 @@ import java.io.IOException;
|
||||
|
||||
public class Argon2Native {
|
||||
|
||||
public static byte[] transformKey(byte[] password, byte[] salt, UnsignedInt parallelism,
|
||||
enum CType {
|
||||
ARGON2_D(0),
|
||||
ARGON2_I(1),
|
||||
ARGON2_ID(2);
|
||||
|
||||
int cValue = 0;
|
||||
|
||||
CType(int i) {
|
||||
cValue = i;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] transformKey(Argon2Kdf.Type type, byte[] password, byte[] salt, UnsignedInt parallelism,
|
||||
UnsignedInt memory, UnsignedInt iterations, byte[] secretKey,
|
||||
byte[] associatedData, UnsignedInt version) throws IOException {
|
||||
NativeLib.INSTANCE.init();
|
||||
|
||||
CType cType = CType.ARGON2_D;
|
||||
if (type.equals(Argon2Kdf.Type.ARGON2_ID))
|
||||
cType = CType.ARGON2_ID;
|
||||
|
||||
return nTransformMasterKey(
|
||||
cType.cValue,
|
||||
password,
|
||||
salt,
|
||||
parallelism.toKotlinInt(),
|
||||
@@ -42,7 +59,7 @@ public class Argon2Native {
|
||||
version.toKotlinInt());
|
||||
}
|
||||
|
||||
private static native byte[] nTransformMasterKey(byte[] password, byte[] salt, int parallelism,
|
||||
private static native byte[] nTransformMasterKey(int type, byte[] password, byte[] salt, int parallelism,
|
||||
int memory, int iterations, byte[] secretKey,
|
||||
byte[] associatedData, int version) throws IOException;
|
||||
}
|
||||
|
||||
@@ -21,5 +21,6 @@ package com.kunzisoft.keepass.crypto.keyDerivation
|
||||
|
||||
object KdfFactory {
|
||||
var aesKdf = AesKdf()
|
||||
var argon2Kdf = Argon2Kdf()
|
||||
var argon2dKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_D)
|
||||
var argon2idKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_ID)
|
||||
}
|
||||
|
||||
@@ -431,6 +431,7 @@ class Database {
|
||||
searchInPasswords = false
|
||||
searchInUrls = true
|
||||
searchInNotes = true
|
||||
searchInOTP = false
|
||||
searchInOther = true
|
||||
searchInUUIDs = false
|
||||
searchInTags = false
|
||||
|
||||
@@ -411,8 +411,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
*/
|
||||
|
||||
/**
|
||||
* Retrieve generated entry info,
|
||||
* Remove parameter fields and add auto generated elements in auto custom fields
|
||||
* Retrieve generated entry info.
|
||||
* If are not [raw] data, remove parameter fields and add auto generated elements in auto custom fields
|
||||
*/
|
||||
fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo {
|
||||
val entryInfo = EntryInfo()
|
||||
@@ -500,5 +500,12 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
}
|
||||
|
||||
const val PMS_TAN_ENTRY = "<TAN>"
|
||||
|
||||
/**
|
||||
* True if [field] name is not a standard field name
|
||||
*/
|
||||
fun newExtraFieldNameAllowed(field: Field): Boolean {
|
||||
return EntryKDBX.newCustomNameAllowed(field.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
|
||||
init {
|
||||
kdfList.add(KdfFactory.aesKdf)
|
||||
kdfList.add(KdfFactory.argon2Kdf)
|
||||
kdfList.add(KdfFactory.argon2dKdf)
|
||||
kdfList.add(KdfFactory.argon2idKdf)
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
@@ -369,6 +369,14 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
const val STR_URL = "URL"
|
||||
const val STR_NOTES = "Notes"
|
||||
|
||||
fun newCustomNameAllowed(name: String): Boolean {
|
||||
return !(name.equals(STR_TITLE, true)
|
||||
|| name.equals(STR_USERNAME, true)
|
||||
|| name.equals(STR_PASSWORD, true)
|
||||
|| name.equals(STR_URL, true)
|
||||
|| name.equals(STR_NOTES, true))
|
||||
}
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<EntryKDBX> = object : Parcelable.Creator<EntryKDBX> {
|
||||
override fun createFromParcel(parcel: Parcel): EntryKDBX {
|
||||
|
||||
@@ -34,7 +34,7 @@ import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
class SearchHelper {
|
||||
|
||||
companion object {
|
||||
const val MAX_SEARCH_ENTRY = 6
|
||||
const val MAX_SEARCH_ENTRY = 10
|
||||
|
||||
/**
|
||||
* Utility method to perform actions if item is found or not after an auto search in [database]
|
||||
|
||||
@@ -33,6 +33,7 @@ class SearchParameters {
|
||||
var searchInUrls = true
|
||||
var searchInGroupNames = false
|
||||
var searchInNotes = true
|
||||
var searchInOTP = false
|
||||
var searchInOther = true
|
||||
var searchInUUIDs = false
|
||||
var searchInTags = true
|
||||
@@ -54,6 +55,7 @@ class SearchParameters {
|
||||
this.searchInUrls = source.searchInUrls
|
||||
this.searchInGroupNames = source.searchInGroupNames
|
||||
this.searchInNotes = source.searchInNotes
|
||||
this.searchInOTP = source.searchInOTP
|
||||
this.searchInOther = source.searchInOther
|
||||
this.searchInUUIDs = source.searchInUUIDs
|
||||
this.searchInTags = source.searchInTags
|
||||
@@ -69,6 +71,7 @@ class SearchParameters {
|
||||
searchInUrls = false
|
||||
searchInGroupNames = false
|
||||
searchInNotes = false
|
||||
searchInOTP = false
|
||||
searchInOther = false
|
||||
searchInUUIDs = false
|
||||
searchInTags = false
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.database.search.iterator
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import java.util.*
|
||||
import kotlin.collections.Map.Entry
|
||||
|
||||
@@ -75,6 +76,7 @@ class EntrySearchStringIteratorKDBX(
|
||||
EntryKDBX.STR_PASSWORD -> mSearchParameters.searchInPasswords
|
||||
EntryKDBX.STR_URL -> mSearchParameters.searchInUrls
|
||||
EntryKDBX.STR_NOTES -> mSearchParameters.searchInNotes
|
||||
OtpEntryFields.OTP_FIELD -> mSearchParameters.searchInOTP
|
||||
else -> mSearchParameters.searchInOther
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ class PasswordActivityEducation(activity: Activity)
|
||||
onOuterViewClick: ((TapTargetView?) -> Unit)? = null): Boolean {
|
||||
return checkAndPerformedEducation(!isEducationBiometricPerformed(activity),
|
||||
TapTarget.forView(educationView,
|
||||
activity.getString(R.string.education_biometric_title),
|
||||
activity.getString(R.string.education_biometric_summary))
|
||||
activity.getString(R.string.education_advanced_unlock_title),
|
||||
activity.getString(R.string.education_advanced_unlock_summary))
|
||||
.textColorInt(Color.WHITE)
|
||||
.tintTarget(false)
|
||||
.cancelable(true),
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
|
||||
@@ -243,6 +244,14 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
if (entryInfoKey != null) {
|
||||
currentInputConnection.commitText(entryInfoKey!!.password, 1)
|
||||
}
|
||||
val otpFieldExists = entryInfoKey?.containsCustomField(OTP_TOKEN_FIELD) ?: false
|
||||
actionGoAutomatically(!otpFieldExists)
|
||||
}
|
||||
KEY_OTP -> {
|
||||
if (entryInfoKey != null) {
|
||||
currentInputConnection.commitText(
|
||||
entryInfoKey!!.getGeneratedFieldValue(OTP_TOKEN_FIELD), 1)
|
||||
}
|
||||
actionGoAutomatically()
|
||||
}
|
||||
KEY_URL -> {
|
||||
@@ -254,7 +263,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
KEY_FIELDS -> {
|
||||
if (entryInfoKey != null) {
|
||||
fieldsAdapter?.apply {
|
||||
setFields(entryInfoKey!!.customFields)
|
||||
setFields(entryInfoKey!!.customFields.filter { it.name != OTP_TOKEN_FIELD})
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -272,10 +281,11 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
currentInputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB))
|
||||
}
|
||||
|
||||
private fun actionGoAutomatically() {
|
||||
private fun actionGoAutomatically(switchToPreviousKeyboardIfAllowed: Boolean = true) {
|
||||
if (PreferencesUtil.isAutoGoActionEnable(this)) {
|
||||
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
|
||||
if (PreferencesUtil.isKeyboardPreviousFillInEnable(this)) {
|
||||
if (switchToPreviousKeyboardIfAllowed
|
||||
&& PreferencesUtil.isKeyboardPreviousFillInEnable(this)) {
|
||||
switchToPreviousKeyboard()
|
||||
}
|
||||
}
|
||||
@@ -326,6 +336,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
|
||||
private const val KEY_ENTRY = 620
|
||||
private const val KEY_USERNAME = 500
|
||||
private const val KEY_PASSWORD = 510
|
||||
private const val KEY_OTP = 515
|
||||
private const val KEY_URL = 520
|
||||
private const val KEY_FIELDS = 530
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@@ -90,13 +91,17 @@ class EntryInfo : Parcelable {
|
||||
return customFields.any { !it.protectedValue.isProtected }
|
||||
}
|
||||
|
||||
fun containsCustomField(label: String): Boolean {
|
||||
return customFields.lastOrNull { it.name == label } != null
|
||||
}
|
||||
|
||||
fun isAutoGeneratedField(field: Field): Boolean {
|
||||
return field.name == OTP_TOKEN_FIELD
|
||||
}
|
||||
|
||||
fun getGeneratedFieldValue(label: String): String {
|
||||
otpModel?.let {
|
||||
if (label == OTP_TOKEN_FIELD) {
|
||||
if (label == OTP_TOKEN_FIELD) {
|
||||
otpModel?.let {
|
||||
return OtpElement(it).token
|
||||
}
|
||||
}
|
||||
@@ -104,37 +109,55 @@ class EntryInfo : Parcelable {
|
||||
}
|
||||
|
||||
private fun addUniqueField(field: Field, number: Int = 0) {
|
||||
var exists = false
|
||||
var sameData = false
|
||||
val suffix = if (number > 0) number.toString() else ""
|
||||
var sameName = false
|
||||
var sameValue = false
|
||||
val suffix = if (number > 0) "_$number" else ""
|
||||
customFields.forEach { currentField ->
|
||||
// Not write the same data again
|
||||
if (currentField.protectedValue.stringValue == field.protectedValue.stringValue) {
|
||||
sameValue = true
|
||||
return
|
||||
}
|
||||
// Same name but new value, create a new suffix
|
||||
if (currentField.name == field.name + suffix) {
|
||||
exists = true
|
||||
// Not write the same value again
|
||||
if (currentField.protectedValue.stringValue == field.protectedValue.stringValue) {
|
||||
sameData = true
|
||||
} else {
|
||||
addUniqueField(field, number + 1)
|
||||
}
|
||||
sameName = true
|
||||
addUniqueField(field, number + 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (!exists && !sameData)
|
||||
if (!sameName && !sameValue)
|
||||
(customFields as ArrayList<Field>).add(Field(field.name + suffix, field.protectedValue))
|
||||
}
|
||||
|
||||
fun saveSearchInfo(database: Database?, searchInfo: SearchInfo) {
|
||||
searchInfo.webDomain?.let { webDomain ->
|
||||
searchInfo.otpString?.let { otpString ->
|
||||
// Replace the OTP field
|
||||
OtpEntryFields.parseOTPUri(otpString)?.let { otpElement ->
|
||||
if (title.isEmpty())
|
||||
title = otpElement.issuer
|
||||
if (username.isEmpty())
|
||||
username = otpElement.name
|
||||
// Add OTP field
|
||||
val mutableCustomFields = customFields as ArrayList<Field>
|
||||
val otpField = OtpEntryFields.buildOtpField(otpElement, null, null)
|
||||
if (mutableCustomFields.contains(otpField)) {
|
||||
mutableCustomFields.remove(otpField)
|
||||
}
|
||||
mutableCustomFields.add(otpField)
|
||||
}
|
||||
} ?: searchInfo.webDomain?.let { webDomain ->
|
||||
// If unable to save web domain in custom field or URL not populated, save in URL
|
||||
val scheme = searchInfo.webScheme
|
||||
val webScheme = if (scheme.isNullOrEmpty()) "http" else scheme
|
||||
val webDomainToStore = "$webScheme://$webDomain"
|
||||
if (database?.allowEntryCustomFields() != true || url.isEmpty()) {
|
||||
url = webDomainToStore
|
||||
} else {
|
||||
}
|
||||
else if (url != webDomainToStore){
|
||||
// Save web domain in custom field
|
||||
addUniqueField(Field(WEB_DOMAIN_FIELD_NAME,
|
||||
ProtectedString(false, webDomainToStore))
|
||||
ProtectedString(false, webDomainToStore)),
|
||||
1 // Start to one because URL is a standard field name
|
||||
)
|
||||
}
|
||||
} ?: run {
|
||||
@@ -151,8 +174,8 @@ class EntryInfo : Parcelable {
|
||||
|
||||
companion object {
|
||||
|
||||
const val WEB_DOMAIN_FIELD_NAME = "WebDomain"
|
||||
const val APPLICATION_ID_FIELD_NAME = "ApplicationId"
|
||||
const val WEB_DOMAIN_FIELD_NAME = "URL"
|
||||
const val APPLICATION_ID_FIELD_NAME = "AndroidApp"
|
||||
|
||||
@JvmField
|
||||
val CREATOR: Parcelable.Creator<EntryInfo> = object : Parcelable.Creator<EntryInfo> {
|
||||
|
||||
@@ -2,8 +2,10 @@ package com.kunzisoft.keepass.model
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.ObjectNameResource
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -35,6 +37,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
get() {
|
||||
return if (webDomain == null) null else field
|
||||
}
|
||||
var otpString: String? = null
|
||||
|
||||
constructor()
|
||||
|
||||
@@ -42,6 +45,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
applicationId = toCopy?.applicationId
|
||||
webDomain = toCopy?.webDomain
|
||||
webScheme = toCopy?.webScheme
|
||||
otpString = toCopy?.otpString
|
||||
}
|
||||
|
||||
private constructor(parcel: Parcel) {
|
||||
@@ -51,6 +55,8 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
webDomain = if (readDomain.isNullOrEmpty()) null else readDomain
|
||||
val readScheme = parcel.readString()
|
||||
webScheme = if (readScheme.isNullOrEmpty()) null else readScheme
|
||||
val readOtp = parcel.readString()
|
||||
otpString = if (readOtp.isNullOrEmpty()) null else readOtp
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
@@ -61,14 +67,23 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
parcel.writeString(applicationId ?: "")
|
||||
parcel.writeString(webDomain ?: "")
|
||||
parcel.writeString(webScheme ?: "")
|
||||
parcel.writeString(otpString ?: "")
|
||||
}
|
||||
|
||||
override fun getName(resources: Resources): String {
|
||||
otpString?.let { otpString ->
|
||||
OtpEntryFields.parseOTPUri(otpString)?.let { otpElement ->
|
||||
return "${otpElement.type} (${Uri.decode(otpElement.issuer)}:${Uri.decode(otpElement.name)})"
|
||||
}
|
||||
}
|
||||
return toString()
|
||||
}
|
||||
|
||||
fun containsOnlyNullValues(): Boolean {
|
||||
return applicationId == null && webDomain == null && webScheme == null
|
||||
return applicationId == null
|
||||
&& webDomain == null
|
||||
&& webScheme == null
|
||||
&& otpString == null
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -80,6 +95,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
if (applicationId != other.applicationId) return false
|
||||
if (webDomain != other.webDomain) return false
|
||||
if (webScheme != other.webScheme) return false
|
||||
if (otpString != other.otpString) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -88,11 +104,12 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
||||
var result = applicationId?.hashCode() ?: 0
|
||||
result = 31 * result + (webDomain?.hashCode() ?: 0)
|
||||
result = 31 * result + (webScheme?.hashCode() ?: 0)
|
||||
result = 31 * result + (otpString?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return webDomain ?: applicationId ?: ""
|
||||
return otpString ?: webDomain ?: applicationId ?: ""
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.kunzisoft.keepass.notifications
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class AdvancedUnlockNotificationService : NotificationService() {
|
||||
|
||||
private lateinit var mTempCipherDao: ArrayList<CipherDatabaseEntity>
|
||||
|
||||
private var mActionTaskBinder = AdvancedUnlockBinder()
|
||||
|
||||
private var notificationTimeoutMilliSecs: Long = 0
|
||||
private var mTimerJob: Job? = null
|
||||
|
||||
inner class AdvancedUnlockBinder: Binder() {
|
||||
fun getCipherDatabase(databaseUri: Uri): CipherDatabaseEntity? {
|
||||
return mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString()}
|
||||
}
|
||||
fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity) {
|
||||
val cipherDatabaseRetrieve = mTempCipherDao.firstOrNull { it.databaseUri == cipherDatabaseEntity.databaseUri }
|
||||
cipherDatabaseRetrieve?.replaceContent(cipherDatabaseEntity)
|
||||
?: mTempCipherDao.add(cipherDatabaseEntity)
|
||||
}
|
||||
fun deleteByDatabaseUri(databaseUri: Uri) {
|
||||
mTempCipherDao.firstOrNull { it.databaseUri == databaseUri.toString() }?.let {
|
||||
mTempCipherDao.remove(it)
|
||||
}
|
||||
}
|
||||
fun deleteAll() {
|
||||
mTempCipherDao.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override val notificationId: Int = 593
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_ADVANCED_UNLOCK_ID
|
||||
}
|
||||
|
||||
override fun retrieveChannelName(): String {
|
||||
return getString(R.string.advanced_unlock)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return mActionTaskBinder
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
|
||||
val deleteIntent = Intent(this, AdvancedUnlockNotificationService::class.java).apply {
|
||||
action = ACTION_REMOVE_KEYS
|
||||
}
|
||||
val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val biometricUnlockEnabled = PreferencesUtil.isBiometricUnlockEnable(this)
|
||||
val notificationBuilder = buildNewNotification().apply {
|
||||
setSmallIcon(if (biometricUnlockEnabled) {
|
||||
R.drawable.notification_ic_fingerprint_unlock_24dp
|
||||
} else {
|
||||
R.drawable.notification_ic_device_unlock_24dp
|
||||
})
|
||||
intent?.let {
|
||||
setContentTitle(getString(R.string.advanced_unlock))
|
||||
}
|
||||
setContentText(getString(R.string.advanced_unlock_tap_delete))
|
||||
setContentIntent(pendingDeleteIntent)
|
||||
// Unfortunately swipe is disabled in lollipop+
|
||||
setDeleteIntent(pendingDeleteIntent)
|
||||
}
|
||||
|
||||
when (intent?.action) {
|
||||
ACTION_TIMEOUT -> {
|
||||
notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
|
||||
// Not necessarily a foreground service
|
||||
if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
|
||||
mTimerJob = CoroutineScope(Dispatchers.Main).launch {
|
||||
val maxPos = 100
|
||||
val posDurationMills = notificationTimeoutMilliSecs / maxPos
|
||||
for (pos in maxPos downTo 0) {
|
||||
notificationBuilder.setProgress(maxPos, pos, false)
|
||||
startForeground(notificationId, notificationBuilder.build())
|
||||
delay(posDurationMills)
|
||||
if (pos <= 0) {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
notificationManager?.cancel(notificationId)
|
||||
mTimerJob = null
|
||||
cancel()
|
||||
}
|
||||
} else {
|
||||
startForeground(notificationId, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
ACTION_REMOVE_KEYS -> {
|
||||
stopSelf()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mTempCipherDao = ArrayList()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
mTempCipherDao.clear()
|
||||
mTimerJob?.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ADVANCED_UNLOCK_ID = "com.kunzisoft.keepass.notification.channel.unlock"
|
||||
|
||||
private const val ACTION_TIMEOUT = "ACTION_TIMEOUT"
|
||||
private const val ACTION_REMOVE_KEYS = "ACTION_REMOVE_KEYS"
|
||||
|
||||
fun startServiceForTimeout(context: Context) {
|
||||
if (PreferencesUtil.isTempAdvancedUnlockEnable(context)) {
|
||||
context.startService(Intent(context, AdvancedUnlockNotificationService::class.java).apply {
|
||||
action = ACTION_TIMEOUT
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun stopService(context: Context) {
|
||||
context.stopService(Intent(context, AdvancedUnlockNotificationService::class.java))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,14 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_ATTACHMENT_ID
|
||||
}
|
||||
|
||||
override fun retrieveChannelName(): String {
|
||||
return getString(R.string.entry_attachments)
|
||||
}
|
||||
|
||||
inner class ActionTaskBinder: Binder() {
|
||||
|
||||
fun getService(): AttachmentFileNotificationService = this@AttachmentFileNotificationService
|
||||
@@ -430,6 +438,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
companion object {
|
||||
private val TAG = AttachmentFileNotificationService::javaClass.name
|
||||
|
||||
private const val CHANNEL_ATTACHMENT_ID = "com.kunzisoft.keepass.notification.channel.attachment"
|
||||
|
||||
const val ACTION_ATTACHMENT_FILE_START_UPLOAD = "ACTION_ATTACHMENT_FILE_START_UPLOAD"
|
||||
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
|
||||
const val ACTION_ATTACHMENT_REMOVE = "ACTION_ATTACHMENT_REMOVE"
|
||||
|
||||
@@ -39,6 +39,14 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
private var notificationTimeoutMilliSecs: Long = 0
|
||||
private var cleanCopyNotificationTimerTask: Thread? = null
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_CLIPBOARD_ID
|
||||
}
|
||||
|
||||
override fun retrieveChannelName(): String {
|
||||
return getString(R.string.clipboard)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@@ -230,6 +238,8 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
|
||||
private val TAG = ClipboardEntryNotificationService::class.java.name
|
||||
|
||||
private const val CHANNEL_CLIPBOARD_ID = "com.kunzisoft.keepass.notification.channel.clipboard"
|
||||
|
||||
const val ACTION_NEW_NOTIFICATION = "ACTION_NEW_NOTIFICATION"
|
||||
const val EXTRA_ENTRY_INFO = "EXTRA_ENTRY_INFO"
|
||||
const val EXTRA_CLIPBOARD_FIELDS = "EXTRA_CLIPBOARD_FIELDS"
|
||||
|
||||
@@ -23,11 +23,11 @@ import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
|
||||
import com.kunzisoft.keepass.database.action.*
|
||||
import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable
|
||||
@@ -70,6 +70,14 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
private var mMessageId: Int? = null
|
||||
private var mWarningId: Int? = null
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_DATABASE_ID
|
||||
}
|
||||
|
||||
override fun retrieveChannelName(): String {
|
||||
return getString(R.string.database)
|
||||
}
|
||||
|
||||
inner class ActionTaskBinder: Binder() {
|
||||
|
||||
fun getService(): DatabaseTaskNotificationService = this@DatabaseTaskNotificationService
|
||||
@@ -274,14 +282,12 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
// Database is normally open
|
||||
if (mDatabase.loaded) {
|
||||
// Build Intents for notification action
|
||||
var pendingDatabaseFlag = 0
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
pendingDatabaseFlag = PendingIntent.FLAG_IMMUTABLE
|
||||
}
|
||||
val pendingDatabaseIntent = PendingIntent.getActivity(this,
|
||||
0,
|
||||
Intent(this, GroupActivity::class.java),
|
||||
pendingDatabaseFlag)
|
||||
Intent(this, GroupActivity::class.java).apply {
|
||||
ReadOnlyHelper.putReadOnlyInIntent(this, mDatabase.isReadOnly)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val deleteIntent = Intent(this, DatabaseTaskNotificationService::class.java).apply {
|
||||
action = ACTION_DATABASE_CLOSE
|
||||
}
|
||||
@@ -760,6 +766,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
|
||||
private val TAG = DatabaseTaskNotificationService::class.java.name
|
||||
|
||||
private const val CHANNEL_DATABASE_ID = "com.kunzisoft.keepass.notification.channel.database"
|
||||
|
||||
const val ACTION_DATABASE_CREATE_TASK = "ACTION_DATABASE_CREATE_TASK"
|
||||
const val ACTION_DATABASE_LOAD_TASK = "ACTION_DATABASE_LOAD_TASK"
|
||||
const val ACTION_DATABASE_ASSIGN_PASSWORD_TASK = "ACTION_DATABASE_ASSIGN_PASSWORD_TASK"
|
||||
|
||||
@@ -40,6 +40,14 @@ class KeyboardEntryNotificationService : LockNotificationService() {
|
||||
|
||||
private var pendingDeleteIntent: PendingIntent? = null
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_MAGIKEYBOARD_ID
|
||||
}
|
||||
|
||||
override fun retrieveChannelName(): String {
|
||||
return getString(R.string.magic_keyboard_title)
|
||||
}
|
||||
|
||||
private fun stopNotificationAndSendLockIfNeeded() {
|
||||
// Clear the entry if define in preferences
|
||||
if (PreferencesUtil.isClearKeyboardNotificationEnable(this)) {
|
||||
@@ -145,8 +153,9 @@ class KeyboardEntryNotificationService : LockNotificationService() {
|
||||
|
||||
private const val TAG = "KeyboardEntryNotifSrv"
|
||||
|
||||
const val ENTRY_INFO_KEY = "ENTRY_INFO_KEY"
|
||||
private const val CHANNEL_MAGIKEYBOARD_ID = "com.kunzisoft.keepass.notification.channel.magikeyboard"
|
||||
|
||||
const val ENTRY_INFO_KEY = "ENTRY_INFO_KEY"
|
||||
const val ACTION_CLEAN_KEYBOARD_ENTRY = "ACTION_CLEAN_KEYBOARD_ENTRY"
|
||||
|
||||
fun launchNotificationIfAllowed(context: Context, entry: EntryInfo, toast: Boolean) {
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.kunzisoft.keepass.notifications
|
||||
|
||||
import android.content.Intent
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.LockReceiver
|
||||
import com.kunzisoft.keepass.utils.registerLockReceiver
|
||||
import com.kunzisoft.keepass.utils.unregisterLockReceiver
|
||||
|
||||
@@ -24,6 +24,14 @@ abstract class NotificationService : Service() {
|
||||
return null
|
||||
}
|
||||
|
||||
open fun retrieveChannelId(): String {
|
||||
return CHANNEL_ID
|
||||
}
|
||||
|
||||
open fun retrieveChannelName(): String {
|
||||
return CHANNEL_NAME
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@@ -31,9 +39,9 @@ abstract class NotificationService : Service() {
|
||||
|
||||
// Create notification channel for Oreo+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (notificationManager?.getNotificationChannel(CHANNEL_ID_KEEPASS) == null) {
|
||||
val channel = NotificationChannel(CHANNEL_ID_KEEPASS,
|
||||
CHANNEL_NAME_KEEPASS,
|
||||
if (notificationManager?.getNotificationChannel(retrieveChannelId()) == null) {
|
||||
val channel = NotificationChannel(retrieveChannelId(),
|
||||
retrieveChannelName(),
|
||||
NotificationManager.IMPORTANCE_DEFAULT).apply {
|
||||
enableVibration(false)
|
||||
setSound(null, null)
|
||||
@@ -51,7 +59,7 @@ abstract class NotificationService : Service() {
|
||||
}
|
||||
|
||||
protected fun buildNewNotification(): NotificationCompat.Builder {
|
||||
return NotificationCompat.Builder(this, CHANNEL_ID_KEEPASS)
|
||||
return NotificationCompat.Builder(this, retrieveChannelId())
|
||||
.setColor(colorNotificationAccent)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||
@@ -70,7 +78,7 @@ abstract class NotificationService : Service() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHANNEL_ID_KEEPASS = "com.kunzisoft.keepass.notification.channel"
|
||||
const val CHANNEL_NAME_KEEPASS = "KeePassDX notification"
|
||||
private const val CHANNEL_ID = "com.kunzisoft.keepass.notification.channel"
|
||||
private const val CHANNEL_NAME = "KeePassDX notification"
|
||||
}
|
||||
}
|
||||
@@ -216,13 +216,17 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
|
||||
return secret.isNotEmpty() && checkBase64Secret(secret)
|
||||
}
|
||||
|
||||
fun replaceSpaceChars(parameter: String): String {
|
||||
fun removeLineChars(parameter: String): String {
|
||||
return parameter.replace("[\\r|\\n|\\t|\\u00A0]+".toRegex(), "")
|
||||
}
|
||||
|
||||
fun removeSpaceChars(parameter: String): String {
|
||||
return parameter.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
|
||||
}
|
||||
|
||||
fun replaceBase32Chars(parameter: String): String {
|
||||
// Add 'A' at end if not Base32 length
|
||||
var parameterNewSize = replaceSpaceChars(parameter.toUpperCase(Locale.ENGLISH))
|
||||
var parameterNewSize = removeSpaceChars(parameter.toUpperCase(Locale.ENGLISH))
|
||||
while (parameterNewSize.length % 8 != 0) {
|
||||
parameterNewSize += 'A'
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.model.Field
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.replaceSpaceChars
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.removeLineChars
|
||||
import com.kunzisoft.keepass.otp.OtpElement.Companion.removeSpaceChars
|
||||
import com.kunzisoft.keepass.otp.TokenCalculator.*
|
||||
import java.net.URLEncoder
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@@ -35,7 +35,7 @@ object OtpEntryFields {
|
||||
private val TAG = OtpEntryFields::class.java.name
|
||||
|
||||
// Field from KeePassXC
|
||||
private const val OTP_FIELD = "otp"
|
||||
const val OTP_FIELD = "otp"
|
||||
|
||||
// URL parameters (https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
|
||||
private const val OTP_SCHEME = "otpauth"
|
||||
@@ -49,6 +49,9 @@ object OtpEntryFields {
|
||||
private const val ENCODER_URL_PARAM = "encoder"
|
||||
private const val COUNTER_URL_PARAM = "counter"
|
||||
|
||||
// OTPauth URI
|
||||
private const val REGEX_OTP_AUTH = "^(?:otpauth://([ht]otp)/)(?:(?:([^:?#]*): *)?([^:?#]*))(?:\\?([^#]+))$"
|
||||
|
||||
// Key-values (maybe from plugin or old KeePassXC)
|
||||
private const val SEED_KEY = "key"
|
||||
private const val DIGITS_KEY = "size"
|
||||
@@ -91,7 +94,25 @@ object OtpEntryFields {
|
||||
// HOTP fields from KeePass 2
|
||||
if (parseHOTPFromField(getField, otpElement))
|
||||
return otpElement
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell if [otpUri] is a valid Otp URI
|
||||
*/
|
||||
fun isOTPUri(otpUri: String): Boolean {
|
||||
if (Pattern.matches(REGEX_OTP_AUTH, otpUri))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OtpElement from [otpUri]
|
||||
*/
|
||||
fun parseOTPUri(otpUri: String): OtpElement? {
|
||||
val otpElement = OtpElement()
|
||||
if (parseOTPUri({ key -> if (key == OTP_FIELD) otpUri else null }, otpElement))
|
||||
return otpElement
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -104,8 +125,8 @@ object OtpEntryFields {
|
||||
*/
|
||||
private fun parseOTPUri(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
|
||||
val otpPlainText = getField(OTP_FIELD)
|
||||
if (otpPlainText != null && otpPlainText.isNotEmpty()) {
|
||||
val uri = Uri.parse(replaceSpaceChars(otpPlainText))
|
||||
if (otpPlainText != null && otpPlainText.isNotEmpty() && isOTPUri(otpPlainText)) {
|
||||
val uri = Uri.parse(removeSpaceChars(otpPlainText))
|
||||
|
||||
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) {
|
||||
Log.e(TAG, "Invalid or missing scheme in uri")
|
||||
@@ -135,12 +156,19 @@ object OtpEntryFields {
|
||||
}
|
||||
|
||||
val nameParam = validateAndGetNameInPath(uri.path)
|
||||
if (nameParam != null && nameParam.isNotEmpty())
|
||||
otpElement.name = nameParam
|
||||
if (nameParam != null && nameParam.isNotEmpty()) {
|
||||
val userIdArray = nameParam.split(":", "%3A")
|
||||
if (userIdArray.size > 1) {
|
||||
otpElement.issuer = removeLineChars(userIdArray[0])
|
||||
otpElement.name = removeLineChars(userIdArray[1])
|
||||
} else {
|
||||
otpElement.name = removeLineChars(nameParam)
|
||||
}
|
||||
}
|
||||
|
||||
val issuerParam = uri.getQueryParameter(ISSUER_URL_PARAM)
|
||||
if (issuerParam != null && issuerParam.isNotEmpty())
|
||||
otpElement.issuer = issuerParam
|
||||
otpElement.issuer = removeLineChars(issuerParam)
|
||||
|
||||
val secretParam = uri.getQueryParameter(SECRET_URL_PARAM)
|
||||
if (secretParam != null && secretParam.isNotEmpty()) {
|
||||
@@ -211,15 +239,15 @@ object OtpEntryFields {
|
||||
}
|
||||
val issuer =
|
||||
if (title != null && title.isNotEmpty())
|
||||
replaceCharsForUrl(title)
|
||||
encodeParameter(title)
|
||||
else
|
||||
replaceCharsForUrl(otpElement.issuer)
|
||||
encodeParameter(otpElement.issuer)
|
||||
val accountName =
|
||||
if (username != null && username.isNotEmpty())
|
||||
replaceCharsForUrl(username)
|
||||
encodeParameter(username)
|
||||
else
|
||||
replaceCharsForUrl(otpElement.name)
|
||||
val uriString = StringBuilder("otpauth://$otpAuthority/$issuer:$accountName" +
|
||||
encodeParameter(otpElement.name)
|
||||
val uriString = StringBuilder("otpauth://$otpAuthority/$issuer%3A$accountName" +
|
||||
"?$SECRET_URL_PARAM=${otpElement.getBase32Secret()}" +
|
||||
"&$counterOrPeriodLabel=$counterOrPeriodValue" +
|
||||
"&$DIGITS_URL_PARAM=${otpElement.digits}" +
|
||||
@@ -233,8 +261,8 @@ object OtpEntryFields {
|
||||
return Uri.parse(uriString.toString())
|
||||
}
|
||||
|
||||
private fun replaceCharsForUrl(parameter: String): String {
|
||||
return URLEncoder.encode(replaceSpaceChars(parameter), "UTF-8")
|
||||
private fun encodeParameter(parameter: String): String {
|
||||
return Uri.encode(OtpElement.removeLineChars(parameter))
|
||||
}
|
||||
|
||||
private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean {
|
||||
@@ -321,7 +349,7 @@ object OtpEntryFields {
|
||||
// path is "/name", so remove leading "/", and trailing white spaces
|
||||
val name = path.substring(1).trim { it <= ' ' }
|
||||
return if (name.isEmpty()) {
|
||||
null // only white spaces.
|
||||
null
|
||||
} else name
|
||||
}
|
||||
|
||||
@@ -338,7 +366,7 @@ object OtpEntryFields {
|
||||
/**
|
||||
* Build Otp field from an OtpElement
|
||||
*/
|
||||
fun buildOtpField(otpElement: OtpElement, title: String?, username: String?): Field {
|
||||
fun buildOtpField(otpElement: OtpElement, title: String? = null, username: String? = null): Field {
|
||||
return Field(OTP_FIELD, ProtectedString(true,
|
||||
buildOtpUri(otpElement, title, username).toString()))
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import android.view.autofill.AutofillManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.SwitchPreference
|
||||
@@ -41,15 +41,18 @@ import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment
|
||||
import com.kunzisoft.keepass.activities.stylish.Stylish
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.notifications.AdvancedUnlockNotificationService
|
||||
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
|
||||
class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
|
||||
private var deleteKeysAlertDialog: AlertDialog? = null
|
||||
|
||||
override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
||||
// Load the preferences from an XML resource
|
||||
@@ -208,15 +211,18 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
setPreferencesFromResource(R.xml.preferences_advanced_unlock, rootKey)
|
||||
|
||||
activity?.let { activity ->
|
||||
|
||||
val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key))
|
||||
val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key))
|
||||
// < M solve verifyError exception
|
||||
val deviceCredentialUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.device_credential_unlock_enable_key))
|
||||
val autoOpenPromptPreference: SwitchPreference? = findPreference(getString(R.string.biometric_auto_open_prompt_key))
|
||||
val tempAdvancedUnlockPreference: SwitchPreference? = findPreference(getString(R.string.temp_advanced_unlock_enable_key))
|
||||
|
||||
val biometricUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
BiometricUnlockDatabaseHelper.unlockSupported(activity)
|
||||
AdvancedUnlockManager.biometricUnlockSupported(activity)
|
||||
} else false
|
||||
if (!biometricUnlockSupported) {
|
||||
biometricUnlockEnablePreference?.apply {
|
||||
// False if under Marshmallow
|
||||
biometricUnlockEnablePreference?.apply {
|
||||
if (!biometricUnlockSupported) {
|
||||
isChecked = false
|
||||
setOnPreferenceClickListener { preference ->
|
||||
(preference as SwitchPreference).isChecked = false
|
||||
@@ -224,42 +230,98 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
.show(parentFragmentManager, "unavailableFeatureDialog")
|
||||
false
|
||||
}
|
||||
}
|
||||
deleteKeysFingerprints?.isEnabled = false
|
||||
} else {
|
||||
deleteKeysFingerprints?.setOnPreferenceClickListener {
|
||||
context?.let { context ->
|
||||
AlertDialog.Builder(context)
|
||||
.setMessage(resources.getString(R.string.biometric_delete_all_key_warning))
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(resources.getString(android.R.string.ok)
|
||||
) { _, _ ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric(
|
||||
activity,
|
||||
object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback {
|
||||
fun showException(e: Exception) {
|
||||
Toast.makeText(context,
|
||||
getString(R.string.biometric_scanning_error, e.localizedMessage),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
showException(e)
|
||||
}
|
||||
|
||||
override fun onBiometricException(e: Exception) {
|
||||
showException(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
CipherDatabaseAction.getInstance(context.applicationContext).deleteAll()
|
||||
} else {
|
||||
setOnPreferenceClickListener {
|
||||
val biometricChecked = biometricUnlockEnablePreference.isChecked
|
||||
val deviceCredentialChecked = deviceCredentialUnlockEnablePreference?.isChecked ?: false
|
||||
if (!biometricChecked) {
|
||||
biometricUnlockEnablePreference.isChecked = true
|
||||
deleteKeysMessage(activity) {
|
||||
biometricUnlockEnablePreference.isChecked = false
|
||||
autoOpenPromptPreference?.isEnabled = deviceCredentialChecked
|
||||
tempAdvancedUnlockPreference?.isEnabled = deviceCredentialChecked
|
||||
}
|
||||
} else {
|
||||
if (deviceCredentialChecked) {
|
||||
biometricUnlockEnablePreference.isChecked = false
|
||||
deleteKeysMessage(activity) {
|
||||
biometricUnlockEnablePreference.isChecked = true
|
||||
deviceCredentialUnlockEnablePreference?.isChecked = false
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.cancel))
|
||||
{ _, _ -> }.show()
|
||||
} else {
|
||||
autoOpenPromptPreference?.isEnabled = true
|
||||
tempAdvancedUnlockPreference?.isEnabled = true
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val deviceCredentialUnlockSupported = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
AdvancedUnlockManager.deviceCredentialUnlockSupported(activity)
|
||||
} else false
|
||||
deviceCredentialUnlockEnablePreference?.apply {
|
||||
// Biometric unlock already checked
|
||||
if (biometricUnlockEnablePreference?.isChecked == true)
|
||||
isChecked = false
|
||||
if (!deviceCredentialUnlockSupported) {
|
||||
isChecked = false
|
||||
setOnPreferenceClickListener { preference ->
|
||||
(preference as SwitchPreference).isChecked = false
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
||||
.show(parentFragmentManager, "unavailableFeatureDialog")
|
||||
false
|
||||
}
|
||||
} else {
|
||||
setOnPreferenceClickListener {
|
||||
val deviceCredentialChecked = deviceCredentialUnlockEnablePreference.isChecked
|
||||
val biometricChecked = biometricUnlockEnablePreference?.isChecked ?: false
|
||||
if (!deviceCredentialChecked) {
|
||||
deviceCredentialUnlockEnablePreference.isChecked = true
|
||||
deleteKeysMessage(activity) {
|
||||
deviceCredentialUnlockEnablePreference.isChecked = false
|
||||
autoOpenPromptPreference?.isEnabled = biometricChecked
|
||||
tempAdvancedUnlockPreference?.isEnabled = biometricChecked
|
||||
}
|
||||
} else {
|
||||
if (biometricChecked) {
|
||||
deviceCredentialUnlockEnablePreference.isChecked = false
|
||||
deleteKeysMessage(activity) {
|
||||
deviceCredentialUnlockEnablePreference.isChecked = true
|
||||
biometricUnlockEnablePreference?.isChecked = false
|
||||
}
|
||||
} else {
|
||||
autoOpenPromptPreference?.isEnabled = true
|
||||
tempAdvancedUnlockPreference?.isEnabled = true
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
autoOpenPromptPreference?.isEnabled = biometricUnlockEnablePreference?.isChecked == true
|
||||
|| deviceCredentialUnlockEnablePreference?.isChecked == true
|
||||
tempAdvancedUnlockPreference?.isEnabled = biometricUnlockEnablePreference?.isChecked == true
|
||||
|| deviceCredentialUnlockEnablePreference?.isChecked == true
|
||||
|
||||
tempAdvancedUnlockPreference?.setOnPreferenceClickListener {
|
||||
tempAdvancedUnlockPreference.isChecked = !tempAdvancedUnlockPreference.isChecked
|
||||
deleteKeysMessage(activity) {
|
||||
tempAdvancedUnlockPreference.isChecked = !tempAdvancedUnlockPreference.isChecked
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key))
|
||||
if (biometricUnlockSupported || deviceCredentialUnlockSupported) {
|
||||
deleteKeysFingerprints?.setOnPreferenceClickListener {
|
||||
deleteKeysMessage(activity)
|
||||
false
|
||||
}
|
||||
} else {
|
||||
deleteKeysFingerprints?.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,6 +331,42 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteKeysMessage(activity: FragmentActivity, validate: (()->Unit)? = null) {
|
||||
deleteKeysAlertDialog = AlertDialog.Builder(activity)
|
||||
.setMessage(resources.getString(R.string.advanced_unlock_delete_all_key_warning))
|
||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||
.setPositiveButton(resources.getString(android.R.string.ok)
|
||||
) { _, _ ->
|
||||
validate?.invoke()
|
||||
deleteKeysAlertDialog?.setOnDismissListener(null)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
AdvancedUnlockManager.deleteEntryKeyInKeystoreForBiometric(
|
||||
activity,
|
||||
object : AdvancedUnlockManager.AdvancedUnlockErrorCallback {
|
||||
fun showException(e: Exception) {
|
||||
Toast.makeText(context,
|
||||
getString(R.string.advanced_unlock_scanning_error, e.localizedMessage),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onInvalidKeyException(e: Exception) {
|
||||
showException(e)
|
||||
}
|
||||
|
||||
override fun onGenericException(e: Exception) {
|
||||
showException(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
AdvancedUnlockNotificationService.stopService(activity.applicationContext)
|
||||
CipherDatabaseAction.getInstance(activity.applicationContext).deleteAll()
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.cancel)
|
||||
) { _, _ ->}
|
||||
.create()
|
||||
deleteKeysAlertDialog?.show()
|
||||
}
|
||||
|
||||
private fun onCreateAppearancePreferences(rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences_appearance, rootKey)
|
||||
|
||||
@@ -328,7 +426,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
activity?.let { activity ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
findPreference<SwitchPreference?>(getString(R.string.settings_autofill_enable_key))?.let { autoFillEnablePreference ->
|
||||
@@ -340,6 +437,11 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
deleteKeysAlertDialog?.dismiss()
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
private var mCount = 0
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.settings
|
||||
|
||||
import android.app.backup.BackupManager
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.database.element.SortNodeEnum
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import java.util.*
|
||||
@@ -43,6 +45,7 @@ object PreferencesUtil {
|
||||
}
|
||||
apply()
|
||||
}
|
||||
BackupManager(context).dataChanged()
|
||||
}
|
||||
|
||||
fun getDefaultDatabasePath(context: Context): String? {
|
||||
@@ -201,6 +204,13 @@ object PreferencesUtil {
|
||||
?: TimeoutHelper.DEFAULT_TIMEOUT
|
||||
}
|
||||
|
||||
fun getAdvancedUnlockTimeout(context: Context): Long {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getString(context.getString(R.string.temp_advanced_unlock_timeout_key),
|
||||
context.getString(R.string.temp_advanced_unlock_timeout_default))?.toLong()
|
||||
?: TimeoutHelper.DEFAULT_TIMEOUT
|
||||
}
|
||||
|
||||
fun isLockDatabaseWhenScreenShutOffEnable(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.lock_database_screen_off_key),
|
||||
@@ -225,13 +235,38 @@ object PreferencesUtil {
|
||||
context.resources.getBoolean(R.bool.enable_auto_save_database_default))
|
||||
}
|
||||
|
||||
fun isBiometricUnlockEnable(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key),
|
||||
context.resources.getBoolean(R.bool.biometric_unlock_enable_default))
|
||||
fun isAdvancedUnlockEnable(context: Context): Boolean {
|
||||
return isBiometricUnlockEnable(context) || isDeviceCredentialUnlockEnable(context)
|
||||
}
|
||||
|
||||
fun isBiometricPromptAutoOpenEnable(context: Context): Boolean {
|
||||
fun isBiometricUnlockEnable(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val biometricSupported = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
AdvancedUnlockManager.biometricUnlockSupported(context)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return prefs.getBoolean(context.getString(R.string.biometric_unlock_enable_key),
|
||||
context.resources.getBoolean(R.bool.biometric_unlock_enable_default))
|
||||
&& biometricSupported
|
||||
}
|
||||
|
||||
fun isDeviceCredentialUnlockEnable(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
// Priority to biometric unlock
|
||||
val biometricAlreadySupported = isBiometricUnlockEnable(context)
|
||||
return prefs.getBoolean(context.getString(R.string.device_credential_unlock_enable_key),
|
||||
context.resources.getBoolean(R.bool.device_credential_unlock_enable_default))
|
||||
&& !biometricAlreadySupported
|
||||
}
|
||||
|
||||
fun isTempAdvancedUnlockEnable(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.temp_advanced_unlock_enable_key),
|
||||
context.resources.getBoolean(R.bool.temp_advanced_unlock_enable_default))
|
||||
}
|
||||
|
||||
fun isAdvancedUnlockPromptAutoOpenEnable(context: Context): Boolean {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
return prefs.getBoolean(context.getString(R.string.biometric_auto_open_prompt_key),
|
||||
context.resources.getBoolean(R.bool.biometric_auto_open_prompt_default))
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
|
||||
import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment
|
||||
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
|
||||
import com.kunzisoft.keepass.activities.lock.LockingActivity
|
||||
import com.kunzisoft.keepass.activities.lock.resetAppTimeoutWhenViewFocusedOrChanged
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.view.showActionError
|
||||
@@ -81,7 +82,7 @@ open class SettingsActivity
|
||||
}
|
||||
|
||||
// Focus view to reinitialize timeout
|
||||
resetAppTimeoutWhenViewFocusedOrChanged(coordinatorLayout)
|
||||
coordinatorLayout?.resetAppTimeoutWhenViewFocusedOrChanged(this)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
|
||||
@@ -77,13 +77,15 @@ class LockReceiver(var lockAction: () -> Unit) : BroadcastReceiver() {
|
||||
cancelLockPendingIntent(context)
|
||||
}
|
||||
}
|
||||
LOCK_ACTION,
|
||||
REMOVE_ENTRY_MAGIKEYBOARD_ACTION -> {
|
||||
LOCK_ACTION -> {
|
||||
lockAction.invoke()
|
||||
if (PreferencesUtil.isKeyboardPreviousLockEnable(context)) {
|
||||
backToPreviousKeyboardAction?.invoke()
|
||||
} else {}
|
||||
}
|
||||
REMOVE_ENTRY_MAGIKEYBOARD_ACTION -> {
|
||||
lockAction.invoke()
|
||||
}
|
||||
BACK_PREVIOUS_KEYBOARD_ACTION -> {
|
||||
backToPreviousKeyboardAction?.invoke()
|
||||
}
|
||||
|
||||
@@ -53,23 +53,19 @@ object MenuUtil {
|
||||
fun onDefaultMenuOptionsItemSelected(activity: Activity,
|
||||
item: MenuItem,
|
||||
readOnly: Boolean = READ_ONLY_DEFAULT,
|
||||
timeoutEnable: Boolean = false): Boolean {
|
||||
timeoutEnable: Boolean = false) {
|
||||
when (item.itemId) {
|
||||
R.id.menu_contribute -> {
|
||||
onContributionItemSelected(activity)
|
||||
return true
|
||||
}
|
||||
R.id.menu_app_settings -> {
|
||||
// To avoid flickering when launch settings in a LockingActivity
|
||||
SettingsActivity.launch(activity, readOnly, timeoutEnable)
|
||||
return true
|
||||
}
|
||||
R.id.menu_about -> {
|
||||
val intent = Intent(activity, AboutActivity::class.java)
|
||||
activity.startActivity(intent)
|
||||
return true
|
||||
}
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,12 @@ import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.biometric.FingerPrintAnimatedVector
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0)
|
||||
@@ -48,25 +50,25 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context,
|
||||
inflater?.inflate(R.layout.view_advanced_unlock, this)
|
||||
|
||||
unlockContainerView = findViewById(R.id.fingerprint_container)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
unlockTitleTextView = findViewById(R.id.biometric_title)
|
||||
unlockMessageTextView = findViewById(R.id.biometric_message)
|
||||
unlockIconImageView = findViewById(R.id.biometric_image)
|
||||
// Init the fingerprint animation
|
||||
unlockAnimatedVector = FingerPrintAnimatedVector(context, unlockIconImageView!!)
|
||||
}
|
||||
unlockTitleTextView = findViewById(R.id.biometric_title)
|
||||
unlockMessageTextView = findViewById(R.id.biometric_message)
|
||||
unlockIconImageView = findViewById(R.id.biometric_image)
|
||||
}
|
||||
|
||||
fun startIconViewAnimation() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
unlockAnimatedVector?.startScan()
|
||||
}
|
||||
private fun startIconViewAnimation() {
|
||||
unlockAnimatedVector?.startScan()
|
||||
}
|
||||
|
||||
fun stopIconViewAnimation() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
unlockAnimatedVector?.stopScan()
|
||||
private fun stopIconViewAnimation() {
|
||||
unlockAnimatedVector?.stopScan()
|
||||
}
|
||||
|
||||
fun setIconResource(iconId: Int) {
|
||||
unlockIconImageView?.setImageResource(iconId)
|
||||
// Init the fingerprint animation
|
||||
unlockAnimatedVector = when (iconId) {
|
||||
R.drawable.fingerprint -> FingerPrintAnimatedVector(context, unlockIconImageView!!)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,12 @@ import android.app.Application
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.kunzisoft.keepass.app.App
|
||||
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.app.database.IOActionTask
|
||||
import com.kunzisoft.keepass.model.DatabaseFile
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
|
||||
class DatabaseFileViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
@@ -15,6 +19,33 @@ class DatabaseFileViewModel(application: Application) : AndroidViewModel(applica
|
||||
mFileDatabaseHistoryAction = FileDatabaseHistoryAction.getInstance(application.applicationContext)
|
||||
}
|
||||
|
||||
val isDefaultDatabase: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>()
|
||||
}
|
||||
|
||||
fun checkIfIsDefaultDatabase(databaseUri: Uri) {
|
||||
IOActionTask(
|
||||
{
|
||||
(UriUtil.parse(PreferencesUtil.getDefaultDatabasePath(getApplication<App>().applicationContext))
|
||||
== databaseUri)
|
||||
},
|
||||
{
|
||||
isDefaultDatabase.value = it
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
|
||||
fun removeDefaultDatabase() {
|
||||
IOActionTask(
|
||||
{
|
||||
PreferencesUtil.saveDefaultDatabasePath(getApplication<App>().applicationContext,
|
||||
null)
|
||||
},
|
||||
{
|
||||
}
|
||||
).execute()
|
||||
}
|
||||
|
||||
val databaseFileLoaded: MutableLiveData<DatabaseFile> by lazy {
|
||||
MutableLiveData<DatabaseFile>()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import android.app.backup.BackupManager
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
@@ -42,11 +41,8 @@ class DatabaseFilesViewModel(application: Application) : AndroidViewModel(applic
|
||||
fun setDefaultDatabase(databaseFile: DatabaseFile?) {
|
||||
IOActionTask(
|
||||
{
|
||||
val context = getApplication<App>().applicationContext
|
||||
UriUtil.parse(PreferencesUtil.getDefaultDatabasePath(context))
|
||||
PreferencesUtil.saveDefaultDatabasePath(context, databaseFile?.databaseUri)
|
||||
val backupManager = BackupManager(context)
|
||||
backupManager.dataChanged()
|
||||
PreferencesUtil.saveDefaultDatabasePath(getApplication<App>().applicationContext,
|
||||
databaseFile?.databaseUri)
|
||||
},
|
||||
{
|
||||
checkDefaultDatabase()
|
||||
|
||||
@@ -129,7 +129,7 @@ void throwExceptionF(JNIEnv *env, jclass exception, const char *format, ...) {
|
||||
|
||||
JNIEXPORT jbyteArray
|
||||
JNICALL Java_com_kunzisoft_keepass_crypto_keyDerivation_Argon2Native_nTransformMasterKey(JNIEnv *env,
|
||||
jobject this, jbyteArray password, jbyteArray salt, jint parallelism, jint memory,
|
||||
jobject this, jint type, jbyteArray password, jbyteArray salt, jint parallelism, jint memory,
|
||||
jint iterations, jbyteArray secretKey, jbyteArray associatedData, jint version) {
|
||||
|
||||
argon2_context context;
|
||||
@@ -169,7 +169,7 @@ JNICALL Java_com_kunzisoft_keepass_crypto_keyDerivation_Argon2Native_nTransformM
|
||||
context.flags = ARGON2_DEFAULT_FLAGS;
|
||||
context.version = (uint32_t) version;
|
||||
|
||||
int argonResult = argon2_ctx(&context, Argon2_d);
|
||||
int argonResult = argon2_ctx(&context, (argon2_type) type);
|
||||
|
||||
jbyteArray result;
|
||||
if (argonResult != ARGON2_OK) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
const char *argon2_type2string(argon2_type type, int uppercase) {
|
||||
switch (type) {
|
||||
default:
|
||||
case Argon2_d:
|
||||
return uppercase ? "Argon2d" : "argon2d";
|
||||
case Argon2_i:
|
||||
|
||||
@@ -473,7 +473,7 @@ JNIEXPORT jbyteArray JNICALL Java_com_kunzisoft_keepass_crypto_finalkey_NativeAE
|
||||
(*env)->GetByteArrayRegion(env, seed, 0, MASTER_KEY_SIZE, (jbyte *)mk.c_seed);
|
||||
(*env)->GetByteArrayRegion(env, key, 0, MASTER_KEY_SIZE, (jbyte *)mk.key1);
|
||||
|
||||
// step 2: encrypt the hash "rounds" (default: 6000) times
|
||||
// step 2: encrypt the hash "rounds"
|
||||
iret = pthread_create( &t1, NULL, (void*)generate_key_material, (void*)&mk );
|
||||
if( iret != 0 ) {
|
||||
(*env)->ThrowNew(env, bad_arg, "TransformMasterKey: failed to launch thread 1"); // FIXME: get a better exception class for this...
|
||||
|
||||
13
app/src/main/res/drawable-v23/bolt.xml
Normal file
13
app/src/main/res/drawable-v23/bolt.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/advanced_unlock_size"
|
||||
android:height="@dimen/advanced_unlock_size"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group>
|
||||
<path
|
||||
android:fillColor="#fffbfb"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 13 4 L 8 13 L 11.777344 13 L 11 20 L 16 11 L 12.222656 11 L 13 4 z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -15,8 +15,8 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/fingerprint_width"
|
||||
android:height="@dimen/fingerprint_height"
|
||||
android:width="@dimen/advanced_unlock_size"
|
||||
android:height="@dimen/advanced_unlock_size"
|
||||
android:viewportWidth="@integer/fingerprint_viewport_width"
|
||||
android:viewportHeight="@integer/fingerprint_viewport_height">
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="@dimen/fingerprint_width"
|
||||
android:height="@dimen/fingerprint_height"
|
||||
android:width="@dimen/advanced_unlock_size"
|
||||
android:height="@dimen/advanced_unlock_size"
|
||||
android:viewportWidth="@integer/fingerprint_viewport_width"
|
||||
android:viewportHeight="@integer/fingerprint_viewport_height">
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<path
|
||||
android:fillColor="#f5f0f0"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 12.099609 2 C 9.9496186 2 7.9107813 2.5192969 6.0507812 3.5292969 C 5.8107813 3.6592969 5.7196538 3.9709375 5.8496094 4.2109375 C 5.9896094 4.4509375 6.2892969 4.5401474 6.5292969 4.4101562 C 8.239288 3.4701563 10.119609 3 12.099609 3 C 13.817565 3 15.298631 3.3380125 16.904297 4.0605469 L 17.658203 3.3066406 C 15.801889 2.4224054 14.062357 2 12.099609 2 z M 20.630859 2.7929688 C 20.353595 2.7923499 20.076963 2.8957023 19.869141 3.1035156 L 3.6054688 19.367188 C 3.1898243 19.782837 3.1898065 20.478878 3.6054688 20.894531 C 4.0211132 21.310172 4.7171681 21.310181 5.1328125 20.894531 L 21.396484 4.6308594 C 21.812129 4.2152078 21.809482 3.5211096 21.394531 3.1054688 C 21.1867 2.8976421 20.908124 2.7935876 20.630859 2.7929688 z M 12.082031 4.4394531 C 10.290782 4.4419476 8.4996875 4.8501572 6.9296875 5.6601562 C 5.4296875 6.4301563 4.1696964 7.5296875 3.1796875 8.9296875 C 3.0196875 9.1596875 3.0707902 9.4708594 3.3007812 9.6308594 C 3.3907901 9.6908772 3.4897549 9.7207031 3.5898438 9.7207031 C 3.7498437 9.7207031 3.9004687 9.6497656 3.9804688 9.5097656 C 4.8804687 8.2497656 6.0208594 7.2507813 7.3808594 6.5507812 C 9.702879 5.3492128 12.551839 5.1405673 15.080078 5.8847656 L 15.886719 5.078125 C 14.672134 4.6548553 13.378189 4.4376481 12.082031 4.4394531 z M 12.050781 6.8203125 C 8.8207726 6.8203125 5.8690714 8.6299217 4.5390625 11.419922 C 4.0890714 12.369922 3.859375 13.460156 3.859375 14.660156 C 3.859375 15.410156 3.9419325 16.143623 4.0917969 16.873047 L 4.9453125 16.019531 C 4.8716574 15.480855 4.8496094 15.017564 4.8496094 14.660156 C 4.8496094 13.620156 5.049462 12.669375 5.4394531 11.859375 C 6.609462 9.409375 9.2107815 7.8300781 12.050781 7.8300781 C 12.394543 7.8300781 12.729451 7.8605876 13.060547 7.9042969 L 13.931641 7.0332031 C 13.326222 6.8985547 12.698935 6.8203125 12.050781 6.8203125 z M 20.158203 7.8769531 L 19.451172 8.5859375 C 19.705703 8.8667188 19.944922 9.1654687 20.169922 9.4804688 C 20.329922 9.7104775 20.639132 9.7596094 20.869141 9.5996094 C 21.099185 9.4396094 21.150234 9.1203906 20.990234 8.9003906 C 20.732029 8.5378633 20.452607 8.1983122 20.158203 7.8769531 z M 11.673828 9.2910156 C 8.7939582 9.4920753 6.5090655 11.751686 6.4375 14.527344 L 7.6914062 13.273438 C 8.128702 12.047606 9.1093977 11.060938 10.378906 10.585938 L 11.673828 9.2910156 z M 18.388672 9.6464844 L 17.675781 10.359375 C 18.68916 11.534094 19.300781 13.029838 19.300781 14.660156 C 19.300781 15.730156 18.370712 16.599609 17.220703 16.599609 C 16.070712 16.599609 15.140625 15.730156 15.140625 14.660156 C 15.140625 14.156975 14.994276 13.690121 14.759766 13.275391 L 14.017578 14.017578 C 14.091568 14.216693 14.140625 14.427975 14.140625 14.650391 C 14.140625 16.270391 15.520703 17.589844 17.220703 17.589844 C 18.920703 17.589844 20.300781 16.270391 20.300781 14.650391 C 20.300781 12.747816 19.580533 11.0042 18.388672 9.6464844 z M 16.607422 11.427734 L 15.894531 12.140625 C 16.429255 12.855101 16.75 13.721687 16.75 14.660156 C 16.75 14.940156 16.97 15.160156 17.25 15.160156 C 17.53 15.160156 17.75 14.940156 17.75 14.660156 C 17.75 13.447471 17.320928 12.329733 16.607422 11.427734 z M 12.609375 15.425781 L 11.791016 16.244141 C 12.154172 17.361118 12.886314 18.356772 13.910156 19.050781 C 14.770156 19.640781 15.819757 19.939453 17.009766 19.939453 C 17.149766 19.939453 17.650712 19.930321 18.220703 19.820312 C 18.500756 19.770313 18.680815 19.510234 18.630859 19.240234 C 18.58085 18.960243 18.320772 18.780087 18.050781 18.830078 C 17.650781 18.900087 17.249766 18.929687 17.009766 18.929688 C 16.009766 18.929688 15.180694 18.700703 14.470703 18.220703 C 13.475364 17.551704 12.819631 16.540198 12.609375 15.425781 z M 10.615234 17.419922 L 9.8652344 18.169922 C 10.203152 18.79111 10.634087 19.368244 11.150391 19.880859 C 12.240382 20.950859 13.279132 21.540469 14.869141 21.980469 C 14.909141 21.990513 14.96 22 15 22 C 15.210009 22 15.420748 21.849141 15.470703 21.619141 C 15.540694 21.359141 15.38915 21.079757 15.119141 21.009766 C 13.709149 20.619766 12.7996 20.100156 11.849609 19.160156 C 11.332157 18.646984 10.922915 18.056717 10.615234 17.419922 z M 8.796875 19.238281 L 8.0703125 19.964844 C 8.484727 20.580638 8.8928845 21.043281 9.4902344 21.640625 C 9.5802255 21.740643 9.7098882 21.789062 9.8398438 21.789062 C 9.9698525 21.789062 10.100893 21.740625 10.210938 21.640625 C 10.400928 21.440625 10.400928 21.129687 10.210938 20.929688 C 9.6271185 20.338294 9.2322832 19.910321 8.796875 19.238281 z" />
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/ic_keystore_remove_white_24dp.xml
Normal file
13
app/src/main/res/drawable/ic_keystore_remove_white_24dp.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 20.630859 2.7929688 C 20.353595 2.7923499 20.076963 2.8957023 19.869141 3.1035156 L 3.6054688 19.367188 C 3.1898243 19.782836 3.1898065 20.478878 3.6054688 20.894531 C 4.0211131 21.310172 4.7171681 21.310181 5.1328125 20.894531 L 21.396484 4.6308594 C 21.812129 4.2152078 21.809529 3.5211095 21.394531 3.1054688 C 21.1867 2.8976421 20.908124 2.7935875 20.630859 2.7929688 z M 7.3476562 7.0878906 C 5.9187852 7.0878906 4.6995509 7.5934125 3.6894531 8.6035156 C 2.6793376 9.613617 2.1738281 10.832842 2.1738281 12.261719 C 2.1738281 13.690597 2.6793376 14.909822 3.6894531 15.919922 C 3.9044307 16.134901 4.1332804 16.315077 4.3671875 16.484375 L 6.9179688 13.933594 C 6.6374532 13.856562 6.382535 13.708063 6.15625 13.472656 C 5.8188811 13.121739 5.6484375 12.715365 5.6484375 12.257812 C 5.6484375 11.798322 5.8188633 11.395839 6.15625 11.044922 C 6.4935833 10.694003 6.8901097 10.519531 7.3476562 10.519531 C 7.8071585 10.519531 8.2115844 10.69399 8.5625 11.044922 C 8.7983688 11.280792 8.9465641 11.543693 9.0234375 11.828125 L 11.59375 9.2578125 C 11.268148 8.7936982 10.868281 8.3824011 10.380859 8.0351562 C 9.4909753 7.4011759 8.4799229 7.0878906 7.3476562 7.0878906 z M 17.626953 10.521484 L 14.148438 14 L 16 14 L 16 17.433594 L 19.435547 17.433594 L 19.435547 13.998047 L 21.173828 13.998047 L 21.173828 10.521484 L 17.626953 10.521484 z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<group>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 13 2 L 8 11 L 11.777344 11 L 11.427734 14.140625 C 11.493169 14.182534 11.563348 14.214452 11.626953 14.259766 C 11.988993 14.517689 12.304 14.824694 12.576172 15.164062 L 16 9 L 12.222656 9 L 13 2 z M 8.8125 14.375 C 7.7596432 14.375 6.861473 14.747902 6.1171875 15.492188 C 5.372902 16.236473 5 17.134643 5 18.1875 C 5 19.240357 5.372902 20.138527 6.1171875 20.882812 C 6.861473 21.627098 7.7596432 22 8.8125 22 C 9.6467855 22 10.391161 21.767924 11.046875 21.300781 C 11.702589 20.835067 12.159397 20.223035 12.417969 19.46875 L 15.1875 19.46875 L 15.1875 21.998047 L 17.71875 21.998047 L 17.71875 19.466797 L 19 19.466797 L 19 16.90625 L 12.417969 16.90625 C 12.159397 16.151965 11.702589 15.541361 11.046875 15.074219 C 10.391161 14.607076 9.6467855 14.375 8.8125 14.375 z M 8.8125 16.904297 C 9.1510713 16.904297 9.4484599 17.032444 9.7070312 17.291016 C 9.9656026 17.549587 10.09375 17.848404 10.09375 18.185547 C 10.09375 18.524118 9.9656026 18.821507 9.7070312 19.080078 C 9.4484599 19.338649 9.1496428 19.466797 8.8125 19.466797 C 8.4753572 19.466797 8.1821651 19.338649 7.9335938 19.080078 C 7.6850224 18.821507 7.5605469 18.52269 7.5605469 18.185547 C 7.5605469 17.846976 7.6850224 17.549587 7.9335938 17.291016 C 8.1821651 17.032444 8.4753572 16.904297 8.8125 16.904297 z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<group>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:strokeWidth="4"
|
||||
android:pathData="M 12.013672 2 C 9.8636722 2 7.8228902 2.5192969 5.9628906 3.5292969 C 5.7228906 3.6592969 5.6336719 3.9709375 5.7636719 4.2109375 C 5.9036719 4.4509375 6.2033598 4.5401564 6.4433594 4.4101562 C 8.1533594 3.4701563 10.033672 3 12.013672 3 C 14.003672 3 15.663984 3.4201562 17.583984 4.4101562 C 17.653984 4.4501563 17.7325 4.4707031 17.8125 4.4707031 C 17.9925 4.4707031 18.163906 4.3694531 18.253906 4.1894531 C 18.383906 3.9494531 18.292966 3.6495314 18.042969 3.5195312 C 16.002969 2.4695312 14.143672 2 12.013672 2 z M 11.994141 4.4394531 C 10.202891 4.4419531 8.41375 4.8501563 6.84375 5.6601562 C 5.3437504 6.4301562 4.08375 7.5296875 3.09375 8.9296875 C 2.93375 9.1596875 2.9828906 9.4708594 3.2128906 9.6308594 C 3.3028906 9.6908594 3.4039062 9.7207031 3.5039062 9.7207031 C 3.6639063 9.7207031 3.8125781 9.6497661 3.8925781 9.5097656 C 4.7925781 8.2497656 5.9329691 7.2507813 7.2929688 6.5507812 C 10.152968 5.0707812 13.823359 5.0690628 16.693359 6.5390625 C 18.043359 7.2290625 19.183984 8.2204686 20.083984 9.4804688 C 20.243984 9.7104688 20.553203 9.7596094 20.783203 9.5996094 C 21.013203 9.4396094 21.062344 9.1203906 20.902344 8.9003906 C 19.912344 7.5103906 18.652344 6.4203906 17.152344 5.6503906 C 15.577344 4.8403906 13.785391 4.4369531 11.994141 4.4394531 z M 11.962891 6.8203125 C 8.7328906 6.8203125 5.783125 8.6299218 4.453125 11.419922 C 4.003125 12.369922 3.7734375 13.460156 3.7734375 14.660156 C 3.7734375 15.178762 3.8211765 15.688064 3.8925781 16.195312 C 4.1040664 15.673217 4.3977156 15.185736 4.765625 14.751953 C 4.7654412 14.724103 4.7636719 14.686946 4.7636719 14.660156 C 4.7636719 13.620156 4.9635156 12.669375 5.3535156 11.859375 C 6.5235152 9.409375 9.1228906 7.8300781 11.962891 7.8300781 C 15.962891 7.8300781 19.212891 10.890156 19.212891 14.660156 C 19.212891 14.924852 19.155811 15.176176 19.052734 15.40625 L 20.097656 15.40625 C 20.165726 15.163649 20.212891 14.913124 20.212891 14.650391 C 20.212891 10.330391 16.512891 6.8203125 11.962891 6.8203125 z M 12.003906 9.2695312 C 9.3367169 9.2695312 7.098881 11.041343 6.5039062 13.410156 C 6.8827574 13.227092 7.280825 13.087302 7.6933594 12.998047 C 8.3905548 11.400109 10.058802 10.269531 12.003906 10.269531 C 14.573906 10.269531 16.664062 12.240156 16.664062 14.660156 C 16.664062 14.940156 16.884062 15.160156 17.164062 15.160156 C 17.444063 15.160156 17.664062 14.940156 17.664062 14.660156 C 17.664062 11.690156 15.123906 9.2695312 12.003906 9.2695312 z M 11.972656 11.720703 C 10.954724 11.720703 10.058942 12.198213 9.4980469 12.925781 C 9.880498 12.973891 10.253824 13.064358 10.615234 13.191406 C 10.980214 12.895424 11.45052 12.710937 11.972656 12.710938 C 13.122656 12.710938 14.052734 13.580391 14.052734 14.650391 C 14.052734 14.913124 14.099894 15.163649 14.167969 15.40625 L 15.214844 15.40625 C 15.111767 15.176176 15.052734 14.924852 15.052734 14.660156 C 15.052734 13.040156 13.672656 11.720703 11.972656 11.720703 z M 8.8125 14.375 C 7.7596432 14.375 6.861473 14.747902 6.1171875 15.492188 C 5.372902 16.236473 5 17.134643 5 18.1875 C 5 19.240357 5.372902 20.138527 6.1171875 20.882812 C 6.861473 21.627098 7.7596432 22 8.8125 22 C 9.6467855 22 10.391161 21.767924 11.046875 21.300781 C 11.702589 20.835067 12.159397 20.223035 12.417969 19.46875 L 15.1875 19.46875 L 15.1875 21.998047 L 17.71875 21.998047 L 17.71875 19.466797 L 19 19.466797 L 19 16.90625 L 12.417969 16.90625 C 12.159397 16.151965 11.702589 15.541361 11.046875 15.074219 C 10.391161 14.607076 9.6467855 14.375 8.8125 14.375 z M 8.8125 16.904297 C 9.1510713 16.904297 9.4484599 17.032444 9.7070312 17.291016 C 9.9656026 17.549587 10.09375 17.848404 10.09375 18.185547 C 10.09375 18.524118 9.9656026 18.821507 9.7070312 19.080078 C 9.4484599 19.338649 9.1496428 19.466797 8.8125 19.466797 C 8.4753572 19.466797 8.1821651 19.338649 7.9335938 19.080078 C 7.6850224 18.821507 7.5605469 18.52269 7.5605469 18.185547 C 7.5605469 17.846976 7.6850224 17.549587 7.9335938 17.291016 C 8.1821651 17.032444 8.4753572 16.904297 8.8125 16.904297 z" />
|
||||
</group>
|
||||
</vector>
|
||||
13
app/src/main/res/drawable/prefs_bolt_24dp.xml
Normal file
13
app/src/main/res/drawable/prefs_bolt_24dp.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group>
|
||||
<path
|
||||
android:fillColor="#7D7D7D"
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M 13 4 L 8 13 L 11.777344 13 L 11 20 L 16 11 L 12.222656 11 L 13 4 z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#7D7D7D"
|
||||
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z" />
|
||||
</vector>
|
||||
@@ -51,7 +51,7 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@+id/biometric_message"
|
||||
tools:text="@string/biometric_prompt_store_credential_title"
|
||||
tools:text="@string/advanced_unlock_prompt_store_credential_title"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center" />
|
||||
|
||||
@@ -119,7 +119,8 @@
|
||||
android:layout_marginStart="@dimen/image_list_margin"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/root"
|
||||
android:maxLines="1"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -68,17 +68,10 @@
|
||||
android:padding="0dp"
|
||||
android:contentDescription="@string/about"
|
||||
android:src="@drawable/ic_launcher_foreground"/>
|
||||
<FrameLayout
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_advanced_unlock_container_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
android:id="@+id/biometric_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:visibility="gone"/>
|
||||
</FrameLayout>
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
|
||||
13
app/src/main/res/layout/fragment_advanced_unlock.xml
Normal file
13
app/src/main/res/layout/fragment_advanced_unlock.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
android:id="@+id/advanced_unlock_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:visibility="gone"/>
|
||||
</FrameLayout>
|
||||
@@ -67,6 +67,8 @@
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:contentDescription="@string/menu_copy"
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:paddingRight="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingLeft="0dp"
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:elevation="8dp">
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
@@ -110,7 +110,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/file_alias_save"
|
||||
android:textColor="?attr/textColorInverse"
|
||||
android:textColor="?android:attr/textColor"
|
||||
tools:text="DatabaseAlias" />
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/file_alias_save"
|
||||
@@ -124,7 +124,7 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:src="@drawable/ic_save_white_24dp"
|
||||
android:contentDescription="@string/content_description_file_information"
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple.Secondary"/>
|
||||
style="@style/KeepassDXStyle.ImageButton.Simple"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ViewSwitcher>
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/entry_history_username"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
|
||||
android:maxLines="6"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
@@ -58,6 +60,8 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/entry_history_url"
|
||||
style="@style/KeepassDXStyle.TextAppearance.TextEntryItem"
|
||||
android:maxLines="6"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
@@ -71,8 +71,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
tools:text="Node Title"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Entry.Title" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_subtext"
|
||||
|
||||
@@ -87,8 +87,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
tools:text="Node Title"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Group.Title" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/node_subtext"
|
||||
|
||||
@@ -22,17 +22,20 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:minHeight="32dp"
|
||||
android:background="?attr/colorPrimary" >
|
||||
<androidx.appcompat.widget.AppCompatImageView android:id="@+id/entry_icon"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/entry_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginLeft="@dimen/default_margin"
|
||||
android:layout_marginStart="@dimen/default_margin"
|
||||
android:layout_marginRight="@dimen/default_margin"
|
||||
android:layout_marginEnd="@dimen/default_margin"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
@@ -41,25 +44,31 @@
|
||||
android:id="@+id/entry_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary"
|
||||
android:textStyle="bold"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@+id/entry_icon"
|
||||
android:layout_toEndOf="@+id/entry_icon" />
|
||||
android:layout_toEndOf="@+id/entry_icon"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true" />
|
||||
<TextView
|
||||
android:id="@+id/entry_subtext"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
android:layout_marginLeft="6dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginLeft="-6dp"
|
||||
android:layout_marginStart="-6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
style="@style/KeepassDXStyle.TextAppearance.Secondary.TextOnPrimary"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_below="@+id/entry_text"
|
||||
android:layout_toRightOf="@+id/entry_icon"
|
||||
android:layout_toEndOf="@+id/entry_icon"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_toRightOf="@+id/entry_text"
|
||||
android:layout_toEndOf="@+id/entry_text" />
|
||||
android:layout_alignParentEnd="true" />
|
||||
</RelativeLayout>
|
||||
@@ -19,9 +19,9 @@
|
||||
-->
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/menu_biometric_remove_key"
|
||||
android:icon="@drawable/ic_fingerprint_remove_white_24dp"
|
||||
android:title="@string/menu_biometric_remove_key"
|
||||
<item android:id="@+id/menu_keystore_remove_key"
|
||||
android:icon="@drawable/ic_keystore_remove_white_24dp"
|
||||
android:title="@string/menu_keystore_remove_key"
|
||||
android:orderInCategory="85"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -73,7 +73,6 @@
|
||||
<string name="menu_lock">اقفل قاعدة البيانات</string>
|
||||
<string name="menu_open">فتح</string>
|
||||
<string name="menu_search">البحث</string>
|
||||
<string name="menu_biometric_remove_key">إزالة بصمة المفتاح</string>
|
||||
<string name="minus">ناقص</string>
|
||||
<string name="never">أبداً</string>
|
||||
<string name="no_results">لا توجد نتائج للبحث</string>
|
||||
@@ -104,7 +103,6 @@
|
||||
<string name="warning_no_encryption_key">هل أنت متأكد من أنك لا تريد استخدام أي مفتاح تشفير ؟</string>
|
||||
<string name="version_label">الإصدار %1$s</string>
|
||||
<string name="education_new_node_title">أضف عناصر جديدة إلى قاعدتك</string>
|
||||
<string name="education_biometric_title">قم بفتح قاعدة بياناتك ببصمتك</string>
|
||||
<string name="education_entry_new_field_title">إضافة حقول مخصصة</string>
|
||||
<string name="education_field_copy_title">نسخ حقل</string>
|
||||
<string name="education_lock_title">تأمين قاعدة البيانات</string>
|
||||
@@ -115,7 +113,7 @@
|
||||
<string name="key_derivation_function">وظيفة اشتقاق المفتاح</string>
|
||||
<string name="app_timeout">مهلة التطبيق</string>
|
||||
<string name="app_timeout_summary">مدة الانتظار قبل إقفال قاعدة البيانات</string>
|
||||
<string name="file_manager_install_description">تصفَّح الملفات بتثبيت مدير الملفات OpenIntents</string>
|
||||
<string name="file_manager_install_description">المحرر الذي يقبل هاذا الفعل ACTION_CREATE_DOCUMENT و ACTION_OPEN_DOCUMENT ضروري لانتاج, فتح وحفض ملفات قاعدة البيانات.</string>
|
||||
<string name="clipboard_error">بعض الأجهزة لا تسمح للتطبيقات باستعمال الحافظة.</string>
|
||||
<string name="clipboard_timeout">مهلة الحافظة</string>
|
||||
<string name="clipboard_timeout_summary">مدة التخزين في الحافظة(إذا كان جهازك يدعمها)</string>
|
||||
@@ -189,11 +187,7 @@
|
||||
<string name="unavailable_feature_version">نسخة الاندرويد %1$s لا تحقق ادنى متطلبات السنخة %2$s.</string>
|
||||
<string name="file_name">اسم الملف</string>
|
||||
<string name="path">مسار</string>
|
||||
<string name="open_biometric_prompt_unlock_database">فحص البصمة</string>
|
||||
<string name="biometric_invalid_key">لا يمكن قراءة مفتاح البصمة.
|
||||
\nاستعد كلمة السر.</string>
|
||||
<string name="biometric_not_recognized">لم يتعرّف على البصمة</string>
|
||||
<string name="open_biometric_prompt_store_credential">استخدم البصمة لحفظ كلمة السر</string>
|
||||
|
||||
<string name="database_history">تأريخ</string>
|
||||
<string name="clipboard_notifications_summary">مكن اشعارات الحافظة لنسخ الحقول</string>
|
||||
<string name="advanced_unlock">البصمة</string>
|
||||
@@ -291,7 +285,7 @@
|
||||
<string name="otp_algorithm">الخوارزمية</string>
|
||||
<string name="otp_digits">أرقام</string>
|
||||
<string name="otp_counter">العداد</string>
|
||||
<string name="entry_setup_otp">عين كلمة المرور للمرة الواحدة</string>
|
||||
<string name="entry_setup_otp">كلمة المرور للمرة الواحدة</string>
|
||||
<string name="entry_UUID">UUID</string>
|
||||
<string name="html_about_contribution">من أجل <strong>حماية خصوصيتا</strong>٫<strong> إصلاح العلل</strong>٫ <strong>إضافة مميزات</strong> <strong>وجعلنا نشطاء دائما</strong>٫ نحن نعتمد على <strong>مساهمتك</strong>.</string>
|
||||
<string name="content_description_keyfile_checkbox">خانة تأشير الملف المفتاحي</string>
|
||||
@@ -326,10 +320,6 @@
|
||||
<string name="contribution">مساهمة</string>
|
||||
<string name="contact">"تواصل معنا "</string>
|
||||
<string name="biometric">البصمة</string>
|
||||
<string name="credential_before_click_biometric_button">اكتب كلمة السر ، ثم انقر زر \"البصمة\".</string>
|
||||
<string name="biometric_scanning_error">خطأ بالبصمة: %1$s</string>
|
||||
<string name="biometric_prompt_extract_credential_title">افتح قاعدة البيانات بالبصمة</string>
|
||||
<string name="biometric_prompt_store_credential_title">احفظ البصمة</string>
|
||||
<string name="warning_empty_keyfile_explanation">يجب ألا تغير محتوى ملف المفتاح، في أحسن الحالات يجب أن يحتوي بيانات مولدة عشوائيا.</string>
|
||||
<string name="warning_empty_keyfile">من غير المستحسن اضافة ملف مفتاح فارغ.</string>
|
||||
<string name="warning_sure_remove_data">أزل هذه البيانات عل أي حال؟</string>
|
||||
@@ -350,7 +340,6 @@
|
||||
<string name="database_data_compression_title">ضغط البيانات</string>
|
||||
<string name="data">البيانات</string>
|
||||
<string name="unavailable_feature_hardware">تعذر العثور على ماسح البصمة.</string>
|
||||
<string name="biometric_delete_all_key_warning">هل تريد حذف كل مفاتيح التشفير المرتبطة بالبصمة؟</string>
|
||||
<string name="biometric_delete_all_key_summary">احذف كل مفاتيح التشفير المرتبطة بالبصمة</string>
|
||||
<string name="advanced_unlock_explanation_summary">استخدم إلغاء القفل المتقدم لفتح قاعدة البيانات بسهولة</string>
|
||||
<string name="lock_database_show_button_summary">يعرض زر القَفل في الواجهة</string>
|
||||
@@ -361,7 +350,7 @@
|
||||
<string name="database_opened">قاعدة البيانات مفتوحة</string>
|
||||
<string name="autofill_preference_title">إعدادات الملء التلقائي</string>
|
||||
<string name="education_entry_edit_title">حرر المدخلة</string>
|
||||
<string name="education_biometric_summary">لفتح قاعدة البيانات بسرعة اربط كلمة المرور بالبصمة.</string>
|
||||
<string name="education_advanced_unlock_summary">لفتح قاعدة البيانات بسرعة اربط كلمة المرور بالبصمة.</string>
|
||||
<string name="education_search_summary">لإيجاد كلمة المرور، أدخل العنوان أو اسم المستخدم أو محتوى أحد الحقول.</string>
|
||||
<string name="education_new_node_summary">المدخلات لإدارة معرفاتك الرقمية.
|
||||
\n
|
||||
@@ -394,8 +383,6 @@
|
||||
<string name="magic_keyboard_explanation_summary">نشِّط لوحة مفاتيح مخصصة لملأ كلمة السر وحقول معرّفك</string>
|
||||
<string name="biometric_auto_open_prompt_summary">اطلب فحص البصمة ان كانت قاعدة البيانات معدّة لذلك</string>
|
||||
<string name="biometric_auto_open_prompt_title">افتح محث البصمة تلقائيا</string>
|
||||
<string name="biometric_prompt_extract_credential_message">استخرج بيانات الاعتماد لقاعدة البيانات بالبصمة</string>
|
||||
<string name="biometric_prompt_store_credential_message">تحذير: مازلت بحاجة لتذكر المفتاح الرئيسي عند استخدامك للبصمة.</string>
|
||||
<string name="keystore_not_accessible">لم يُهيأ مخزن المفاتيح بشكل صحيح.</string>
|
||||
<string name="warning_remove_unlinked_attachment">حذف البيانات سيقلل من حجم قاعدة البيانات لكن احذر أن تكون إحدى هذه البيانات ملحقة لكي-باس.</string>
|
||||
<string name="subdomain_search_summary">ابحث عن النطاقات في النطاقات الفرعية</string>
|
||||
|
||||
268
app/src/main/res/values-b+sr+Latn/strings.xml
Normal file
268
app/src/main/res/values-b+sr+Latn/strings.xml
Normal file
@@ -0,0 +1,268 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="file_manager_install_description">Za kreiranje, otvaranje i čuvanje datoteka baze podataka potreban je menadžer datoteka koji prihvata Intent akcije ACTION_CREATE_DOCUMENT i ACTION_OPEN_DOCUMENT.</string>
|
||||
<string name="extended_ASCII">Prošireni ASCII</string>
|
||||
<string name="brackets">Zagrade</string>
|
||||
<string name="application">Aplikacija</string>
|
||||
<string name="app_timeout_summary">Neaktivno vreme pre zaključavanja baze podataka</string>
|
||||
<string name="key_derivation_function">Funkcija za generisanje ključa</string>
|
||||
<string name="encryption_algorithm">Algoritam šifrovanja</string>
|
||||
<string name="encryption">Šifrovanje</string>
|
||||
<string name="security">Sigurnost</string>
|
||||
<string name="master_key">Glavni ključ</string>
|
||||
<string name="homepage">Početna strana</string>
|
||||
<string name="feedback">Povratne informacije</string>
|
||||
<string name="contribution">Doprinos</string>
|
||||
<string name="add_group">Dodaj grupu</string>
|
||||
<string name="accept">Prihvati</string>
|
||||
<string name="about_description">Android implementacija KeePass menadžera lozinki</string>
|
||||
<string name="never">Nikada</string>
|
||||
<string name="minus">Minus</string>
|
||||
<string name="menu_delete_entry_history">Izbriši istoriju</string>
|
||||
<string name="menu_restore_entry_history">Vrati istoriju u prethodno stanje</string>
|
||||
<string name="menu_empty_recycle_bin">Isprazni korpu za otpatke</string>
|
||||
<string name="menu_open_file_read_and_write">Izmenjivo</string>
|
||||
<string name="menu_file_selection_read_only">Zaštićeno od upisivanja</string>
|
||||
<string name="menu_url">Idi na URL adresu</string>
|
||||
<string name="menu_showpass">Prikaži lozinku</string>
|
||||
<string name="menu_search">Traži</string>
|
||||
<string name="menu_open">Otvori</string>
|
||||
<string name="menu_save_database">Sačuvaj bazu podataka</string>
|
||||
<string name="menu_lock">Zaključaj bazu podataka</string>
|
||||
<string name="menu_hide_password">Sakrij lozinku</string>
|
||||
<string name="menu_cancel">Otkaži</string>
|
||||
<string name="menu_delete">Izbriši</string>
|
||||
<string name="menu_paste">Nalepi</string>
|
||||
<string name="menu_move">Premesti</string>
|
||||
<string name="menu_copy">Kopiraj</string>
|
||||
<string name="menu_edit">Izmeni</string>
|
||||
<string name="menu_donate">Doniraj</string>
|
||||
<string name="menu_master_key_settings">Postavke glavnog ključa</string>
|
||||
<string name="menu_security_settings">Bezbednosne postavke</string>
|
||||
<string name="menu_database_settings">Postavke baze podataka</string>
|
||||
<string name="menu_advanced_unlock_settings">Napredno otključavanje</string>
|
||||
<string name="menu_form_filling_settings">Popunjavanje obrasca</string>
|
||||
<string name="menu_app_settings">Postavke aplikacije</string>
|
||||
<string name="settings">Postavke</string>
|
||||
<string name="copy_field">Kopija od %1$s</string>
|
||||
<string name="menu_change_key_settings">Promeni glavni ključ</string>
|
||||
<string name="about">O aplikaciji</string>
|
||||
<string name="hide_password_summary">Podrazumevaj maskiranje lozinki sa (***)</string>
|
||||
<string name="hide_password_title">Sakrij lozinke</string>
|
||||
<string name="lowercase">Mala slova</string>
|
||||
<string name="loading_database">Učitavanje baze podataka…</string>
|
||||
<string name="creating_database">Kreiranje baze podataka…</string>
|
||||
<string name="list_size_summary">Veličina teksta u listi elemenata</string>
|
||||
<string name="list_size_title">Veličina stavki liste</string>
|
||||
<string name="list_groups_show_number_entries_summary">Prikaži broj unosa u grupi</string>
|
||||
<string name="list_groups_show_number_entries_title">Prikaži broj unosa</string>
|
||||
<string name="list_entries_show_username_summary">Prikaži korisnička imena u listama unosa</string>
|
||||
<string name="list_entries_show_username_title">Prikaži korisnička imena</string>
|
||||
<string name="length">Dužina</string>
|
||||
<string name="invalid_db_sig">Nije moguće prepoznati format baze podataka.</string>
|
||||
<string name="invalid_db_same_uuid">%1$s sa istim UUID%2$s već postoji.</string>
|
||||
<string name="invalid_algorithm">Pogrešan algoritam.</string>
|
||||
<string name="invalid_credentials">Nije moguće pročitati podatke za prijavljivanje.</string>
|
||||
<string name="password">Lozinka</string>
|
||||
<string name="hint_pass">Lozinka</string>
|
||||
<string name="hint_length">Dužina</string>
|
||||
<string name="hint_group_name">Naziv grupe</string>
|
||||
<string name="hint_generated_password">Generisana lozinka</string>
|
||||
<string name="hint_conf_pass">Potvrdi lozinku</string>
|
||||
<string name="generate_password">Generiši lozinku</string>
|
||||
<string name="file_browser">Upravljač datotekama</string>
|
||||
<string name="file_not_found_content">Nije moguće pronaći datoteku. Pokušajte ponovo da je otvorite iz svog pregledača datoteka.</string>
|
||||
<string name="field_value">Vrednost polja</string>
|
||||
<string name="field_name">Naziv polja</string>
|
||||
<string name="error_registration_read_only">Čuvanje nove stavke nije dozvoljeno u bazi podataka koja je samo za čitanje</string>
|
||||
<string name="error_string_type">Ovaj tekst se ne podudara sa traženom stavkom.</string>
|
||||
<string name="error_otp_period">Broj sekundi perioda mora biti u opsegu od %1$d do %2$d.</string>
|
||||
<string name="error_otp_digits">Broj cifara tokena mora biti u opsegu od %1$d do %2$d.</string>
|
||||
<string name="error_otp_counter">Brojač mora biti između %1$d i %2$d.</string>
|
||||
<string name="error_otp_secret_key">Tajni ključ mora biti u Base32 formatu.</string>
|
||||
<string name="error_save_database">Nije moguće sačuvati bazu podataka.</string>
|
||||
<string name="error_create_database">Nije moguće kreirati datoteku baze podataka.</string>
|
||||
<string name="error_copy_group_here">Ovde ne možete kopirati grupu.</string>
|
||||
<string name="error_copy_entry_here">Ovde ne možete kopirati unos.</string>
|
||||
<string name="error_move_entry_here">Ovde ne možete premestiti unos.</string>
|
||||
<string name="error_move_folder_in_itself">Ne možete premestiti grupu u samu sebe.</string>
|
||||
<string name="error_autofill_enable_service">Nije moguće omogućiti uslugu automatskog popunjavanja.</string>
|
||||
<string name="error_wrong_length">Unesite pozitivan ceo broj u polje \"Dužina\".</string>
|
||||
<string name="error_label_exists">Ova oznaka već postoji.</string>
|
||||
<string name="error_string_key">Svaka niska mora imati ime polja.</string>
|
||||
<string name="error_rounds_too_large">Vrednost transformacijskih prolaza je previsoka. Postavlja se na 2147483648.</string>
|
||||
<string name="error_pass_match">Lozinke se ne podudaraju.</string>
|
||||
<string name="error_disallow_no_credentials">Najmanje jedan podatak za prijavu mora biti unet.</string>
|
||||
<string name="error_pass_gen_type">Mora biti izabran najmanje jedan tip generisanja lozinke.</string>
|
||||
<string name="error_load_database_KDF_memory">Nije moguće učitati ključ. Pokušajte da smanjite korišćenje KDF memorije.</string>
|
||||
<string name="error_load_database">Nije moguće učitati bazu podataka.</string>
|
||||
<string name="error_out_of_memory">Nema dovoljno memorije za učitavanje cele baze podataka.</string>
|
||||
<string name="error_no_name">Unesite ime.</string>
|
||||
<string name="error_invalid_OTP">Nevažeća OTP tajna.</string>
|
||||
<string name="error_invalid_path">Proverite da li je putanja ispravna.</string>
|
||||
<string name="error_invalid_db">Nije moguće pročitati bazu podataka.</string>
|
||||
<string name="error_file_not_create">Nije moguće kreirati datoteku</string>
|
||||
<string name="error_can_not_handle_uri">Nije moguće obraditi ovaj URI u keePassDX-u.</string>
|
||||
<string name="error_arc4">Archfour šifrovanje nije podržano.</string>
|
||||
<string name="entry_user_name">Korisničko ime</string>
|
||||
<string name="entry_url">URL</string>
|
||||
<string name="entry_otp">OTP</string>
|
||||
<string name="otp_algorithm">Algoritam</string>
|
||||
<string name="otp_digits">Cifre</string>
|
||||
<string name="otp_counter">Brojač</string>
|
||||
<string name="otp_period">Period (u sekundama)</string>
|
||||
<string name="otp_secret">Tajna</string>
|
||||
<string name="otp_type">OTP tip</string>
|
||||
<string name="entry_setup_otp">Podesi jednokratnu lozinku</string>
|
||||
<string name="entry_title">Naslov</string>
|
||||
<string name="save">Sačuvaj</string>
|
||||
<string name="entry_password">Lozinka</string>
|
||||
<string name="entry_not_found">Nije moguće pronaći podatke o unosu.</string>
|
||||
<string name="content_description_node_children">Pod-čvor</string>
|
||||
<string name="app_timeout">Vreme isteka aplikacije</string>
|
||||
<string name="keyboard_auto_go_action_summary">Radnja tastera \"Go\" nakon pritiska na taster \"Polje\"</string>
|
||||
<string name="keyboard_auto_go_action_title">Automatska radnja tastera</string>
|
||||
<string name="keyboard_keys_category">Tasteri</string>
|
||||
<string name="keyboard_theme_title">Tema tastature</string>
|
||||
<string name="keyboard_appearance_category">Izgled</string>
|
||||
<string name="keyboard_notification_entry_content_title">%1$s je na Magikeyboard-u</string>
|
||||
<string name="keyboard_notification_entry_content_text">%1$s</string>
|
||||
<string name="keyboard_notification_entry_content_title_text">Unos</string>
|
||||
<string name="keyboard_entry_timeout_summary">Vremenski period nakon koga se briše unos sa tastature</string>
|
||||
<string name="keyboard_entry_timeout_title">Vreme isteka</string>
|
||||
<string name="keyboard_notification_entry_clear_close_summary">Zatvori bazu podataka prilikom zatvaranja obaveštenja</string>
|
||||
<string name="keyboard_notification_entry_clear_close_title">Isprazni pri zatvaranju</string>
|
||||
<string name="keyboard_save_search_info_summary">Pokušaj da sačuvaš podeljene informacije prilikom ručnog izbora unosa</string>
|
||||
<string name="keyboard_search_share_summary">Automatski тражите заједничке информације да бисте попунили тастатуру</string>
|
||||
<string name="keyboard_search_share_title">Traži podeljene informacije</string>
|
||||
<string name="keyboard_notification_entry_summary">Prikaži obaveštenje kada je unos dostupn</string>
|
||||
<string name="keyboard_notification_entry_title">Obaveštenje</string>
|
||||
<string name="keyboard_selection_entry_summary">Prikaži polja za unos podataka u Magikeyboard-u prilikom prikazivanja unosa</string>
|
||||
<string name="keyboard_selection_entry_title">Izbor unosa</string>
|
||||
<string name="keyboard_entry_category">Unos</string>
|
||||
<string name="keyboard_setting_label">Magikeyboard postavke</string>
|
||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||
<string name="keyboard_name">Magikeyboard</string>
|
||||
<string name="device_keyboard_setting_title">Postavke tastature uređaja</string>
|
||||
<string name="magic_keyboard_explanation_summary">Aktivirajte tastaturu koja će ispunjavati polja formi za unos sa Vašim lozinkama i podacima o identitetu</string>
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
<string name="keyboard">Tastatura</string>
|
||||
<string name="recycle_bin">Korpa za otpatke</string>
|
||||
<string name="compression_gzip">Gzip</string>
|
||||
<string name="compression_none">Bez kompresije</string>
|
||||
<string name="compression">Kompresija</string>
|
||||
<string name="other">Ostalo</string>
|
||||
<string name="application_appearance">Aplikacija</string>
|
||||
<string name="text_appearance">Tekst</string>
|
||||
<string name="database_version_title">Verzija baze podataka</string>
|
||||
<string name="database_custom_color_title">Prilagođena boja baze podataka</string>
|
||||
<string name="database_default_username_title">Podrazumevano korisničko ime</string>
|
||||
<string name="database_description_title">Opis baze podataka</string>
|
||||
<string name="database_name_title">Naziv baze podataka</string>
|
||||
<string name="clear_clipboard_notification_summary">Zaključaj bazu podataka kada istekne trajanje međuspremnika ili kada se obaveštenje zatvori nakon što počnete da je koristite</string>
|
||||
<string name="clear_clipboard_notification_title">Isprazni pri zatvaranju</string>
|
||||
<string name="notification">Obaveštenje</string>
|
||||
<string name="disable">Onemogući</string>
|
||||
<string name="enable">Omogući</string>
|
||||
<string name="allow_copy_password_warning">Upozorenje: Međuspremnik se deli sa svim aplikacijama. Ako se kopiraju osetljivi podaci, drugi softver ih može videti.</string>
|
||||
<string name="allow_copy_password_summary">Dozvoli kopiranje lozinke za unos i zaštićenih polja u međuspremnik</string>
|
||||
<string name="allow_copy_password_title">Pouzdanost međuspremnika</string>
|
||||
<string name="monospace_font_fields_enable_summary">Promeni font korišćen u poljima zbog bolje vidljivosti znakova</string>
|
||||
<string name="monospace_font_fields_enable_title">Font polja</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">Zahtevaj promenu glavnog ključa sledeći put (jednom)</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_title">Sledećeg puta forsiraj obnovu</string>
|
||||
<string name="settings_database_force_changing_master_key_summary">Zahtevaj promene glavnog ključa (u danima)</string>
|
||||
<string name="settings_database_force_changing_master_key_title">Forsiraj obnovu</string>
|
||||
<string name="settings_database_recommend_changing_master_key_summary">Preporučiti promenu glavnog ključa (u danima)</string>
|
||||
<string name="settings_database_recommend_changing_master_key_title">Preporučiti obnavljanje</string>
|
||||
<string name="max_history_size_summary">Ograniči veličinu istorije (u bajtovima) po unosu</string>
|
||||
<string name="max_history_size_title">Maksimalna veličina</string>
|
||||
<string name="max_history_items_summary">Ograniči broja stavki istorije po unosu</string>
|
||||
<string name="max_history_items_title">Maksimalan broj</string>
|
||||
<string name="recycle_bin_group_title">Grupa korpe za otpatke</string>
|
||||
<string name="recycle_bin_summary">Premešta grupe i stavke u grupu \"Korpa za otpatke\" pre brisanja</string>
|
||||
<string name="recycle_bin_title">Korišćenje korpe za otpatke</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Uklanja priloge koji su sadržani u bazi podataka, ali nisu povezani sa unosom</string>
|
||||
<string name="database_data_remove_unlinked_attachments_title">Obriši podatake koji nisu povezani</string>
|
||||
<string name="database_data_compression_summary">Kompresija podataka smanjuje veličinu baze podataka</string>
|
||||
<string name="database_data_compression_title">Kompresija podataka</string>
|
||||
<string name="data">Podaci</string>
|
||||
<string name="assign_master_key">Dodeli glavni ključ</string>
|
||||
<string name="path">Putanja</string>
|
||||
<string name="file_name">Naziv datoteke</string>
|
||||
<string name="unavailable_feature_hardware">Nije moguće pronaći odgovarajući hardver.</string>
|
||||
<string name="unavailable_feature_version">Uređaj koristi Android verziju %1$s, ali potrebana verzija je %2$s ili novija.</string>
|
||||
<string name="unavailable_feature_text">Nije moguće pokrenuti ovu funkciju.</string>
|
||||
<string name="biometric_delete_all_key_summary">Izbriši sve ključeve za šifrovanje koji se odnose na biometrijsko prepoznavanje</string>
|
||||
<string name="biometric_delete_all_key_title">Brisanje ključeva za šifrovanje</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatski zatraži biometriju ako je baza podataka podešena da je koristi</string>
|
||||
<string name="biometric_auto_open_prompt_title">Automatski otvori biometrijski upit</string>
|
||||
<string name="biometric_unlock_enable_summary">Omogućava Vam da skenirate svoju biometriju da biste otvorili bazu podataka</string>
|
||||
<string name="biometric_unlock_enable_title">Biometrijsko otključavanje</string>
|
||||
<string name="advanced_unlock_explanation_summary">Koristite napredno otključavanje kako bi ste lakše otvorili bazu podataka</string>
|
||||
<string name="advanced_unlock">Napredno otključavanje</string>
|
||||
<string name="education_unlock_summary">Unesite lozinku i/ili datoteku ključa da bi ste otključali bazu podataka.
|
||||
\n
|
||||
\nNapravite rezervnu kopiju datoteke baze podataka na bezbednom mestu nakon svake promene.</string>
|
||||
<string name="warning_empty_keyfile_explanation">Sadržaj datoteke ključa nikada ne bi trebalo menjati, a u najboljem slučaju bi trebao da sadrži nasumično generisane podatke.</string>
|
||||
<string name="warning_empty_keyfile">Nije preporučljivo dodavanje prazne datoteke ključa.</string>
|
||||
<string name="remember_keyfile_locations_summary">Prati gde su datoteke ključeva uskladištene</string>
|
||||
<string name="remember_keyfile_locations_title">Zapamti lokacije datoteka ključeva</string>
|
||||
<string name="keyfile_is_empty">Datoteka ključa je prazna.</string>
|
||||
<string name="hint_keyfile">Datoteka ključa</string>
|
||||
<string name="error_create_database_file">Nije moguće kreirati bazu podataka sa datom lozinkom i datotekom ključa.</string>
|
||||
<string name="error_nokeyfile">Izaberite datoteku ključa.</string>
|
||||
<string name="content_description_keyfile_checkbox">Polje za potvrdu datoteke ključa</string>
|
||||
<string name="entry_modified">Izmenjeno</string>
|
||||
<string name="entry_keyfile">Datoteka ključa</string>
|
||||
<string name="entry_attachments">Prilozi</string>
|
||||
<string name="entry_history">Istorija</string>
|
||||
<string name="entry_UUID">Jedinstveni indentifikator korisnika ( UUID )</string>
|
||||
<string name="entry_expires">Ističe</string>
|
||||
<string name="entry_created">Kreirano</string>
|
||||
<string name="entry_confpassword">Potvrdi lozinku</string>
|
||||
<string name="entry_notes">Napomene</string>
|
||||
<string name="entry_cancel">Otkaži</string>
|
||||
<string name="entry_accessed">Pristupljeno</string>
|
||||
<string name="html_about_contribution">Kako bismo <strong>zadržali našu slobodu</strong>, <strong>ispravljali greške</strong>, <strong>dodavali nove opcije</strong> i <strong>uvek bili aktivni</strong>, računamo na Vaš <strong>doprinos</strong>.</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft je <strong>otvorenog koda</strong> i <strong>ne sadrži reklame</strong>.
|
||||
\nPonuđen je onakav kakav jeste, pod <strong>GPLv3</strong> licencom, bez ikakvih garancija.</string>
|
||||
<string name="digits">Cifre</string>
|
||||
<string name="default_checkbox">Koristi kao podrazumevanu bazu podataka</string>
|
||||
<string name="decrypting_db">Dešifrovanje sadržaja baze podataka…</string>
|
||||
<string name="database">Baza podataka</string>
|
||||
<string name="retrieving_db_key">Dohvatanje ključa baze podataka…</string>
|
||||
<string name="select_to_copy">Izabarite kako bi ste kopirali %1$s u međuspremnik</string>
|
||||
<string name="content_description_keyboard_close_fields">Zatvori polja</string>
|
||||
<string name="content_description_remove_from_list">Obriši</string>
|
||||
<string name="content_description_update_from_list">Ažuriraj</string>
|
||||
<string name="content_description_remove_field">Obriši polje</string>
|
||||
<string name="entry_add_attachment">Dodaj prilog</string>
|
||||
<string name="entry_add_field">Dodaj polje</string>
|
||||
<string name="content_description_password_length">Dužina lozinke</string>
|
||||
<string name="entry_password_generator">Generator lozinki</string>
|
||||
<string name="discard">Odbaci</string>
|
||||
<string name="discard_changes">Odbaci izmene\?</string>
|
||||
<string name="validate">Provera valjanosti</string>
|
||||
<string name="content_description_entry_icon">Ikonica unosa</string>
|
||||
<string name="content_description_repeat_toggle_password_visibility">Ponovi promenu vidljivosti lozinke</string>
|
||||
<string name="content_description_password_checkbox">Polje za potvrdu lozinke</string>
|
||||
<string name="content_description_credentials_information">Informacije o podacima prijave</string>
|
||||
<string name="content_description_file_information">Informacije o datoteci</string>
|
||||
<string name="content_description_add_item">Dodaj stavku</string>
|
||||
<string name="content_description_add_entry">Dodaj unos</string>
|
||||
<string name="content_description_add_group">Dodaj grupu</string>
|
||||
<string name="content_description_add_node">Dodaj čvor</string>
|
||||
<string name="content_description_open_file">Otvori datoteku</string>
|
||||
<string name="content_description_background">Pozadina</string>
|
||||
<string name="clipboard_timeout_summary">Trajanje pohrane u međuspremniku ( ako je podržano od strane uređaja )</string>
|
||||
<string name="clipboard_timeout">Vreme isteka međuspremnika</string>
|
||||
<string name="clipboard_error_clear">Nije moguće očistiti međuspremnik</string>
|
||||
<string name="clipboard_error">Neki uređaji neće dozvoliti aplikacijama da koriste međuspremnik.</string>
|
||||
<string name="clipboard_cleared">Međuspremnik je očišćena</string>
|
||||
<string name="clipboard_error_title">Greška međuspremnika</string>
|
||||
<string name="allow">Dozvoli</string>
|
||||
<string name="edit_entry">Izmeni stavku</string>
|
||||
<string name="add_entry">Dodaj stavku</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
</resources>
|
||||
@@ -193,10 +193,7 @@
|
||||
<string name="biometric">Biomètric</string>
|
||||
<string name="menu_appearance_settings">Apariència</string>
|
||||
<string name="database_history">Història</string>
|
||||
<string name="credential_before_click_biometric_button">Escriviu la contrasenya abans de fer clic en el botó biomètric.</string>
|
||||
<string name="encrypted_value_stored">Contrasenya xifrada desada</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Obri la base de dades amb reconeixement biomètric</string>
|
||||
<string name="biometric_prompt_store_credential_message">Alerta: Heu de recordar la vostra contrasenya mestra encara que feu servir el reconeixement biomètrica.</string>
|
||||
<string name="warning_permanently_delete_nodes">Voleu suprimir definitivament els nodes seleccionats\?</string>
|
||||
<string name="warning_no_encryption_key">Voleu continuar sense contrasenya de xifrat\?</string>
|
||||
<string name="warning_database_link_revoked">L\'accés al fitxer ha estat revocat pel gestor de fitxers</string>
|
||||
@@ -232,7 +229,6 @@
|
||||
<string name="menu_empty_recycle_bin">Buida la paperera</string>
|
||||
<string name="menu_open_file_read_and_write">Modificable</string>
|
||||
<string name="menu_file_selection_read_only">Protegit contra escriptura</string>
|
||||
<string name="menu_biometric_remove_key">Suprimeix la clau biomètrica desada</string>
|
||||
<string name="menu_save_database">Desa la base de dades</string>
|
||||
<string name="menu_cancel">Cancel·la</string>
|
||||
<string name="menu_paste">Enganxa</string>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<string name="error_pass_match">Zadaná hesla se neshodují.</string>
|
||||
<string name="error_rounds_too_large">Příliš vysoký „Počet průchodů“. Nastavuji na 2147483648.</string>
|
||||
<string name="error_string_key">Je třeba, aby každý řetězec měl název kolonky.</string>
|
||||
<string name="error_wrong_length">Do nastavení „Délka“ zadejte celé kladné číslo.</string>
|
||||
<string name="error_wrong_length">Do pole „Délka“ zadejte celé kladné číslo.</string>
|
||||
<string name="field_name">Název pole</string>
|
||||
<string name="field_value">Hodnota pole</string>
|
||||
<string name="file_browser">Správce souborů</string>
|
||||
@@ -169,7 +169,6 @@
|
||||
<string name="menu_move">Přesunout</string>
|
||||
<string name="menu_paste">Vložit</string>
|
||||
<string name="menu_cancel">Storno</string>
|
||||
<string name="menu_biometric_remove_key">Smazat uložený biometrický klíč</string>
|
||||
<string name="menu_file_selection_read_only">Chráněno před zápisem</string>
|
||||
<string name="menu_open_file_read_and_write">Čtení a zápis</string>
|
||||
<string name="read_only">Chráněno před zápisem</string>
|
||||
@@ -192,12 +191,7 @@
|
||||
<string name="warning_password_encoding">Nepoužívejte v hesle pro databázový soubor znaky mimo znakovou sadu Latin-1 (nepoužívejte znaky s diakritikou).</string>
|
||||
<string name="warning_empty_password">Pokračovat bez ochrany odemknutím heslem\?</string>
|
||||
<string name="warning_no_encryption_key">Pokračovat bez šifrovacího klíče\?</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Otevřít biometrickou pobídku k otevření databáze</string>
|
||||
<string name="encrypted_value_stored">Šifrované heslo uloženo</string>
|
||||
<string name="biometric_invalid_key">Nelze načíst biometrický klíč. Prosím, smažte jej a opakujte proceduru biometrického rozpoznání.</string>
|
||||
<string name="biometric_not_recognized">Biometrický prvek nerozpoznán</string>
|
||||
<string name="biometric_scanning_error">Chyba s biometrickým prvkem: %1$s</string>
|
||||
<string name="open_biometric_prompt_store_credential">Otevři biometrickou pobídku k uložení hesel</string>
|
||||
<string name="no_credentials_stored">Tato databáze zatím nemá uložené heslo.</string>
|
||||
<string name="database_history">Historie</string>
|
||||
<string name="menu_appearance_settings">Vzhled</string>
|
||||
@@ -222,8 +216,7 @@
|
||||
<string name="biometric_unlock_enable_title">Biometrické odemčení</string>
|
||||
<string name="biometric_unlock_enable_summary">Nechá otevřít databázi snímáním biometrického údaje</string>
|
||||
<string name="biometric_delete_all_key_title">Smazat šifrovací klíče</string>
|
||||
<string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s biometrickým rozlišením</string>
|
||||
<string name="biometric_delete_all_key_warning">Smazat všechny šifrovací klíče související s biometrickým rozlišením\?</string>
|
||||
<string name="biometric_delete_all_key_summary">Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí</string>
|
||||
<string name="unavailable_feature_text">Tuto funkci se nedaří spustit.</string>
|
||||
<string name="unavailable_feature_version">V zařízení je instalován Android %1$s, ale potřebná je verze %2$s a novější.</string>
|
||||
<string name="unavailable_feature_hardware">Hardware nebyl rozpoznán.</string>
|
||||
@@ -242,7 +235,7 @@
|
||||
<string name="database_description_title">Popis databáze</string>
|
||||
<string name="database_version_title">Verze databáze</string>
|
||||
<string name="text_appearance">Text</string>
|
||||
<string name="application_appearance">Aplikace</string>
|
||||
<string name="application_appearance">Rozhraní</string>
|
||||
<string name="other">Ostatní</string>
|
||||
<string name="keyboard">Klávesnice</string>
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
@@ -266,8 +259,6 @@
|
||||
\nSkupiny (ekvivalent složek) organizují záznamy v databázi.</string>
|
||||
<string name="education_search_title">Hledejte v položkách</string>
|
||||
<string name="education_search_summary">Zadejte název, uživatelské jméno nebo jiné položky k nalezení svých hesel.</string>
|
||||
<string name="education_biometric_title">Odemknutí databáze biometricky</string>
|
||||
<string name="education_biometric_summary">Propojte své heslo s načtenou biometrikou pro rychlé odemknutí databáze.</string>
|
||||
<string name="education_entry_edit_title">Upravit položku</string>
|
||||
<string name="education_entry_edit_summary">Přidejte ke své položce vlastní kolonky. Společná data mohou být sdílena mezi více různými kolonkami.</string>
|
||||
<string name="education_generate_password_title">Vytvořit silné heslo</string>
|
||||
@@ -276,10 +267,10 @@
|
||||
<string name="education_entry_new_field_summary">Registrovat další kolonku, zadat hodnotu a volitelně ji ochránit.</string>
|
||||
<string name="education_unlock_title">Odemknout databázi</string>
|
||||
<string name="education_read_only_title">Ochraňte svou databázi před zápisem</string>
|
||||
<string name="education_read_only_summary">Změnit režim otevírání pro dané sezení.
|
||||
\n
|
||||
\nV režimu pouze pro čtení zabráníte nechtěným změnám do databáze.
|
||||
\n V režimu zápisu je možné přidávat, mazat nebo měnit všechny prvky dle libosti.</string>
|
||||
<string name="education_read_only_summary">Změnit režim otevírání pro dané sezení.
|
||||
\n
|
||||
\nV režimu \"pouze pro čtení\" zabráníte nechtěným změnám do databáze.
|
||||
\nV režimu \"zápisu\" je možné přidávat, mazat nebo měnit všechny prvky podle libosti.</string>
|
||||
<string name="education_field_copy_title">Zkopírujte kolonku</string>
|
||||
<string name="education_field_copy_summary">Zkopírované kolonky lze vkládat kam chcete
|
||||
\n
|
||||
@@ -300,12 +291,11 @@
|
||||
<string name="html_text_dev_feature_encourage">povzbudíte vývojáře k přidávání <strong>nových funkcí</strong> a <strong>opravování chyb</strong> dle vašich připomínek.</string>
|
||||
<string name="html_text_dev_feature_thanks">Mnohé díky za vaše přispění.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Tvrdě pracujeme na brzkém vydání této funkce.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Nezapomeňte aplikaci aktualizovat instalováním nových verzí.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Pamatujte na aktualizaci aplikace instalováním nových verzí.</string>
|
||||
<string name="download">Stáhnout</string>
|
||||
<string name="contribute">Zapojit se</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="style_choose_title">Vzhled aplikace</string>
|
||||
<string name="style_choose_summary">Motiv vzhledu aplikace</string>
|
||||
<string name="icon_pack_choose_title">Sada ikon</string>
|
||||
@@ -364,13 +354,9 @@
|
||||
<string name="content_description_keyboard_close_fields">Zavři kolonky</string>
|
||||
<string name="error_create_database_file">Nelze vytvořit databázi s tímto heslem a klíčem ze souboru.</string>
|
||||
<string name="menu_advanced_unlock_settings">Pokročilé odemčení</string>
|
||||
<string name="biometric_prompt_store_credential_title">Uložit biometrické rozlišení</string>
|
||||
<string name="biometric_prompt_store_credential_message">VAROVÁNÍ: I s použitím biometrického rozlišení budete muset znát své hlavní heslo.</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Otevřít databázi skrze biometrické rozlišení</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Vytáhnout heslo databáze biometrickými daty</string>
|
||||
<string name="biometric">Biometrika</string>
|
||||
<string name="biometric_auto_open_prompt_title">Automaticky otevřít biometrickou pobídku</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automaticky žádat biometriku, je-li databáze nastavena k jejímu použití</string>
|
||||
<string name="biometric_auto_open_prompt_title">Automaticky otevřít pobídku</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automaticky žádat pokročilé odemknutí, je-li databáze nastavena k jejímu použití</string>
|
||||
<string name="enable">Zapnout</string>
|
||||
<string name="disable">Vypnout</string>
|
||||
<string name="master_key">Hlavní klíč</string>
|
||||
@@ -424,7 +410,6 @@
|
||||
<string name="command_execution">Provádím příkaz…</string>
|
||||
<string name="warning_permanently_delete_nodes">Natrvalo smazat vybrané uzly\?</string>
|
||||
<string name="keystore_not_accessible">Úložiště klíčů není řádně inicializováno.</string>
|
||||
<string name="credential_before_click_biometric_button">Zadejte heslo a pak klikněte na tlačítko \"Biometrika\".</string>
|
||||
<string name="recycle_bin_group_title">Skupina Koš</string>
|
||||
<string name="enable_auto_save_database_title">Uložit databázi automaticky</string>
|
||||
<string name="enable_auto_save_database_summary">Uložit databázi po každé důležité akci (v režimu \"Zápis\")</string>
|
||||
@@ -505,4 +490,47 @@
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Odstraní přílohy obsažené v databázi, ale nikoli přílohy propojené se záznamem</string>
|
||||
<string name="education_add_attachment_title">Přidat přílohu</string>
|
||||
<string name="education_add_attachment_summary">Nahrát přílohu k záznamu pro uložení důležitých externích dat.</string>
|
||||
<string name="show_uuid_summary">Ukáže UUID propojené se záznamem</string>
|
||||
<string name="show_uuid_title">Ukázat UUID</string>
|
||||
<string name="autofill_read_only_save">Uložení dat není povoleno, je-li databáze v režimu pouze pro čtení.</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Zeptat se na uložení dat, jakmile byl formulář přezkoušen</string>
|
||||
<string name="autofill_ask_to_save_data_title">Zeptat se před uložením</string>
|
||||
<string name="autofill_save_search_info_summary">Pokuste se uložit údaje hledání, když manuálně vybíráte položku</string>
|
||||
<string name="autofill_save_search_info_title">Uložit info hledání</string>
|
||||
<string name="autofill_close_database_summary">Zavřít databázi po samodoplnění polí</string>
|
||||
<string name="autofill_close_database_title">Zavřít databázi</string>
|
||||
<string name="keyboard_previous_lock_summary">Po uzamknutí databáze automaticky přepnout zpět na předchozí klávesnici</string>
|
||||
<string name="keyboard_previous_lock_title">Uzamknout databázi</string>
|
||||
<string name="keyboard_save_search_info_summary">Pokuste se uložit sdílené info, když manuálné vybíráte položku</string>
|
||||
<string name="keyboard_save_search_info_title">Uložit sdílené info</string>
|
||||
<string name="notification">Oznámení</string>
|
||||
<string name="biometric_security_update_required">Vyžadována aktualizace biometrického zabezpečení.</string>
|
||||
<string name="configure_biometric">Žádné přihlašovací ani biometrické údaje nejsou registrovány.</string>
|
||||
<string name="warning_empty_recycle_bin">Trvale odstranit všechny položky z koše\?</string>
|
||||
<string name="registration_mode">Režim registrace</string>
|
||||
<string name="save_mode">Režim ukládání</string>
|
||||
<string name="search_mode">Režim vyhledávání</string>
|
||||
<string name="error_field_name_already_exists">Jméno položky již existuje.</string>
|
||||
<string name="error_registration_read_only">Uložení nové položky v režimu databáze pouze pro čtení není povoleno</string>
|
||||
<string name="enter">Enter</string>
|
||||
<string name="backspace">Backspace</string>
|
||||
<string name="select_entry">Vybrat záznam</string>
|
||||
<string name="back_to_previous_keyboard">Zpět na předchozí klávesnici</string>
|
||||
<string name="custom_fields">Vlastní položky</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Smazat všechny šifrovací klíče související s rozpoznáním pokročilého odemknutí\?</string>
|
||||
<string name="device_credential_unlock_enable_summary">Dovolí pro otevření databáze použít heslo Vašeho zařízení</string>
|
||||
<string name="device_credential_unlock_enable_title">Odemknutí heslem zařízení</string>
|
||||
<string name="device_credential">Heslo zařízení</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Zadejte heslo a klikněte na tlačítko \"Pokročilé odemknutí\".</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Nelze inicializovat pobídku pro pokročilé odemknutí.</string>
|
||||
<string name="advanced_unlock_scanning_error">Chyba při pokročilém odemknutí: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Otisk pro pokročilé odemknutí nebyl rozpoznán</string>
|
||||
<string name="advanced_unlock_invalid_key">Nelze načíst klíč pokročilého odemknutí. Prosím, smažte jej a opakujte proces rozpoznání uzamknutí.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Načíst důvěrný údaj pomocí dat pokročilého odemknutí</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Otevřít databázi pomocí rozpoznání pokročilého odemknutí</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Varování: Pokud použijete rozpoznání pokročilého odemknutí, musíte si i nadále pamatovat hlavní heslo.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Rozpoznání pokročilého odemknutí</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Pro uložení důvěrných údajů otevřete pobídku pokročilého odemknutí</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Pro odemknutí databáze otevřete pobídku pokročilého odemknutí</string>
|
||||
<string name="menu_keystore_remove_key">Smazat klíč pokročilého odemknutí</string>
|
||||
</resources>
|
||||
@@ -35,7 +35,7 @@
|
||||
<string name="clipboard_error">Nogle enheder, vil ikke lade programmer bruge udklipsholderen.</string>
|
||||
<string name="clipboard_error_clear">Kunne ikke rydde udklipsholderen</string>
|
||||
<string name="clipboard_timeout">Udklipsholder timeout</string>
|
||||
<string name="clipboard_timeout_summary">Varighed af opbevaring i udklipsholder</string>
|
||||
<string name="clipboard_timeout_summary">Varighed af opbevaring i udklipsholder (hvis det understøttes)</string>
|
||||
<string name="select_to_copy">Vælg for at kopiere %1$s til udklipsholder</string>
|
||||
<string name="retrieving_db_key">Opretter databasenøgle…</string>
|
||||
<string name="database">Database</string>
|
||||
@@ -168,7 +168,6 @@
|
||||
<string name="menu_move">Flyt</string>
|
||||
<string name="menu_paste">Indsæt</string>
|
||||
<string name="menu_cancel">Annuller</string>
|
||||
<string name="menu_biometric_remove_key">Slet gemt fingeraftryk</string>
|
||||
<string name="menu_file_selection_read_only">Skrivebeskyttet</string>
|
||||
<string name="menu_open_file_read_and_write">Modificerbar</string>
|
||||
<string name="read_only">Skrivebeskyttet</string>
|
||||
@@ -191,12 +190,7 @@
|
||||
<string name="warning_password_encoding">Undgå adgangskodetegn uden for tekstkodningsformatet i databasefilen (ukendte tegn konverteres til samme bogstav).</string>
|
||||
<string name="warning_empty_password">Bekræft brug af ingen adgangskode til beskyttelse mod oplåsning\?</string>
|
||||
<string name="warning_no_encryption_key">Fortsæt uden krypteringsnøgle\?</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Åbn biometriske forespørgsel for at låse databasen op</string>
|
||||
<string name="encrypted_value_stored">Krypteret adgangskode er gemt</string>
|
||||
<string name="biometric_invalid_key">Kan ikke læse den biometriske nøgle. Slet den og gentag den biometriske genkendelsesprocedure.</string>
|
||||
<string name="biometric_not_recognized">Kunne ikke genkende biometrisk</string>
|
||||
<string name="biometric_scanning_error">Biometrisk fejl: %1$s</string>
|
||||
<string name="open_biometric_prompt_store_credential">Åbn den biometriske prompt for at gemme legitimationsoplysninger</string>
|
||||
<string name="no_credentials_stored">Databasen har endnu ikke en adgangskode.</string>
|
||||
<string name="database_history">Historik</string>
|
||||
<string name="menu_appearance_settings">Udseende</string>
|
||||
@@ -212,7 +206,7 @@
|
||||
<string name="list_password_generator_options_summary">Angiv tilladte tegn for adgangskodegenerator</string>
|
||||
<string name="clipboard">Udklipsholder</string>
|
||||
<string name="clipboard_notifications_title">Udklipsholdermeddelelser</string>
|
||||
<string name="clipboard_notifications_summary">Aktivér udklipsholder for at kopiere felter når en post vises</string>
|
||||
<string name="clipboard_notifications_summary">Vis udklipsholder for at kopiere felter når en post vises</string>
|
||||
<string name="clipboard_warning">Hvis automatisk sletning af udklipsholder mislykkes, slet historikken manuelt.</string>
|
||||
<string name="lock">Lås</string>
|
||||
<string name="lock_database_screen_off_title">Skærmlås</string>
|
||||
@@ -222,9 +216,8 @@
|
||||
<string name="biometric_unlock_enable_summary">Giver mulighed for at scanne biometriske for at åbne databasen</string>
|
||||
<string name="biometric_delete_all_key_title">Slet krypteringsnøgler</string>
|
||||
<string name="biometric_delete_all_key_summary">Slet alle krypteringsnøgler, der er relateret til biometrisk genkendelse</string>
|
||||
<string name="biometric_delete_all_key_warning">Slet alle krypteringsnøgler, der er relateret til biometrisk genkendelse\?</string>
|
||||
<string name="unavailable_feature_text">Funktionen kunne ikke startes.</string>
|
||||
<string name="unavailable_feature_version">Android-version %1$s opfylder ikke minimum versionskrav %2$s.</string>
|
||||
<string name="unavailable_feature_version">Enheden kører Android %1$s, men har brug for %2$s eller nyere.</string>
|
||||
<string name="unavailable_feature_hardware">Kunne ikke finde den tilsvarende hardware.</string>
|
||||
<string name="file_name">Filnavn</string>
|
||||
<string name="path">Sti</string>
|
||||
@@ -247,13 +240,13 @@
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
<string name="magic_keyboard_explanation_summary">Aktiver et brugerdefineret tastatur, der udfylder adgangskoder og alle identitetsfelter</string>
|
||||
<string name="allow_no_password_title">Tillad ingen hovednøgle</string>
|
||||
<string name="allow_no_password_summary">Aktiver knappen \"Åbn\", hvis der ikke er valgt nogen legitimationsoplysninger</string>
|
||||
<string name="allow_no_password_summary">Tillader at trykke på knappen \"Åbn\", hvis der ikke er valgt nogen legitimationsoplysninger</string>
|
||||
<string name="enable_read_only_title">Skrivebeskyttet</string>
|
||||
<string name="enable_read_only_summary">Åbn database skrivebeskyttet som standard</string>
|
||||
<string name="enable_education_screens_title">Pædagogiske tips</string>
|
||||
<string name="enable_education_screens_summary">Fremhæv elementer for at lære, hvordan programmet fungerer</string>
|
||||
<string name="reset_education_screens_title">Nulstil pædagogiske tip</string>
|
||||
<string name="reset_education_screens_summary">Vis alle pædagogiske info igen</string>
|
||||
<string name="reset_education_screens_summary">Vis al uddannelsesinformation igen</string>
|
||||
<string name="reset_education_screens_text">Nulstilling af pædagogiske tips</string>
|
||||
<string name="education_create_database_title">Opret databasefilen</string>
|
||||
<string name="education_create_database_summary">Opret den første adgangskodeadministrationsfil.</string>
|
||||
@@ -265,8 +258,6 @@
|
||||
\nGrupper (~mapper) organiserer poster i databasen.</string>
|
||||
<string name="education_search_title">Søg i poster</string>
|
||||
<string name="education_search_summary">Indtast titel, brugernavn eller indhold af andre felter for at hente adgangskoder.</string>
|
||||
<string name="education_biometric_title">Biometrisk oplåsning af databasen</string>
|
||||
<string name="education_biometric_summary">Knyt adgangskoden til det scannede biometri for hurtigt at låse databasen op.</string>
|
||||
<string name="education_entry_edit_title">Rediger posten</string>
|
||||
<string name="education_entry_edit_summary">Rediger post med brugerdefinerede felter. Pool data kan refereres mellem forskellige indtastningsfelter.</string>
|
||||
<string name="education_generate_password_title">Opret en stærk adgangskode</string>
|
||||
@@ -304,7 +295,6 @@
|
||||
<string name="contribute">Bidrag</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="style_choose_title">Tema</string>
|
||||
<string name="style_choose_summary">Tema, der bruges i programmet</string>
|
||||
<string name="icon_pack_choose_title">Ikonpakke</string>
|
||||
@@ -364,10 +354,6 @@
|
||||
<string name="content_description_keyboard_close_fields">Luk felter</string>
|
||||
<string name="error_create_database_file">Kan ikke oprette database med denne adgangskode og nøglefil.</string>
|
||||
<string name="menu_advanced_unlock_settings">Avanceret oplåsning</string>
|
||||
<string name="biometric_prompt_store_credential_title">Gem biometrisk genkendelse</string>
|
||||
<string name="biometric_prompt_store_credential_message">Advarsel: hovedadgangskoden skal stadig huskes, hvis der bruges biometrisk genkendelse.</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Åbn database med biometrisk genkendelse</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Uddrag databasens legitimationsoplysninger med biometriske data</string>
|
||||
<string name="biometric">Biometrisk</string>
|
||||
<string name="biometric_auto_open_prompt_title">Åbn automatisk biometrisk prompt</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Spørg automatisk efter biometri, hvis databasen er konfigureret til at bruge den</string>
|
||||
@@ -401,7 +387,7 @@
|
||||
<string name="clipboard_explanation_summary">Kopier indtastningsfelter ved hjælp af enhedens udklipsholder</string>
|
||||
<string name="advanced_unlock_explanation_summary">Brug avanceret oplåsning for at gøre det lettere at åbne en database</string>
|
||||
<string name="database_data_compression_title">Datakomprimering</string>
|
||||
<string name="database_data_compression_summary">Datakomprimering reducerer databasens størrelse.</string>
|
||||
<string name="database_data_compression_summary">Datakomprimering reducerer databasens størrelse</string>
|
||||
<string name="max_history_items_title">Max. antal</string>
|
||||
<string name="max_history_items_summary">Begræns antallet af historikposter pr. indtastning</string>
|
||||
<string name="max_history_size_title">Max. størrelse</string>
|
||||
@@ -424,7 +410,6 @@
|
||||
<string name="command_execution">Udfører kommandoen…</string>
|
||||
<string name="warning_permanently_delete_nodes">Slet markerede noder permanent\?</string>
|
||||
<string name="keystore_not_accessible">Nøglelageret er ikke korrekt initialiseret.</string>
|
||||
<string name="credential_before_click_biometric_button">Indtast adgangskoden, og klik derefter på knappen \"Biometrisk\".</string>
|
||||
<string name="recycle_bin_group_title">Papirkurvsgruppe</string>
|
||||
<string name="enable_auto_save_database_title">Gem automatisk database</string>
|
||||
<string name="enable_auto_save_database_summary">Gem databasen efter hver en vigtig handling (i tilstanden \"Modificerbar\")</string>
|
||||
@@ -448,10 +433,10 @@
|
||||
<string name="html_about_contribution">For at <strong>holde vores frihed</strong>, <strong>rette fejl</strong>, <strong>tilføje funktioner</strong> og <strong>at være altid aktiv</strong>, regner vi med <strong>bidrag</strong>.</string>
|
||||
<string name="auto_focus_search_title">Hurtig søgning</string>
|
||||
<string name="auto_focus_search_summary">Anmod om en søgning når en database åbnes</string>
|
||||
<string name="remember_database_locations_title">Gem placering af databaser</string>
|
||||
<string name="remember_database_locations_summary">Husk placeringen af databaser</string>
|
||||
<string name="remember_keyfile_locations_title">Gem placering af nøglefiler</string>
|
||||
<string name="remember_keyfile_locations_summary">Husker placeringen af databasernøglefiler</string>
|
||||
<string name="remember_database_locations_title">Husk placeringer af databaser</string>
|
||||
<string name="remember_database_locations_summary">Holder styr på, hvor databaserne gemmes</string>
|
||||
<string name="remember_keyfile_locations_title">Husk placering af nøglefiler</string>
|
||||
<string name="remember_keyfile_locations_summary">Holder styr på, hvor nøglefiler gemmes</string>
|
||||
<string name="show_recent_files_title">Vis seneste filer</string>
|
||||
<string name="show_recent_files_summary">Vis placeringer af de seneste databaser</string>
|
||||
<string name="hide_broken_locations_title">Skjule brudte databaselinks</string>
|
||||
@@ -464,7 +449,7 @@
|
||||
<string name="discard">Kassér</string>
|
||||
<string name="discard_changes">Kasser ændringer\?</string>
|
||||
<string name="validate">Valider</string>
|
||||
<string name="autofill_auto_search_summary">Foreslår automatisk søgeresultater fra webdomænet eller applikationsId</string>
|
||||
<string name="autofill_auto_search_summary">Foreslår automatisk søgeresultater fra webdomænet eller applikations-id</string>
|
||||
<string name="autofill_auto_search_title">Automatisk søgning</string>
|
||||
<string name="lock_database_show_button_summary">Viser låseknappen i brugergrænsefladen</string>
|
||||
<string name="lock_database_show_button_title">Vis låseknap</string>
|
||||
@@ -479,14 +464,52 @@
|
||||
<string name="autofill_web_domain_blocklist_title">Blokeringsliste for webdomæne</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Blokeringsliste der forhindrer automatisk udfyldning af programmer</string>
|
||||
<string name="autofill_application_id_blocklist_title">Blokeringsliste for program</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Skift automatisk tilbage til det forrige tastatur efter udførelse af automatisk tastehandling</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Skift automatisk tilbage til det forrige tastatur efter udførelse af \"Automatisk tastehandling\"</string>
|
||||
<string name="keyboard_previous_fill_in_title">Auto nøglehandling</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Skift automatisk tilbage til det forrige tastatur på databasens legitimationsskærm</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Database legitimationsoplysninger skærm</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Skærmbilledet til databaselegitimationsoplysninger</string>
|
||||
<string name="keyboard_change">Skifte tastatur</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="subdomain_search_summary">Søg på webdomæner med begrænsninger på underdomæner</string>
|
||||
<string name="subdomain_search_title">Underdomæne søgning</string>
|
||||
<string name="error_string_type">Teksten stemmer ikke overens med det ønskede element.</string>
|
||||
<string name="content_description_add_item">Tilføj element</string>
|
||||
<string name="show_uuid_summary">Viser UUID\'en, der er knyttet til en post</string>
|
||||
<string name="show_uuid_title">Vis UUID</string>
|
||||
<string name="upload_attachment">Overfør %1$s</string>
|
||||
<string name="education_add_attachment_title">Vedhæft fil</string>
|
||||
<string name="autofill_read_only_save">Lagring af data er ikke tilladt for en database, der er åbnet som skrivebeskyttet.</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Bed om at gemme data, når en formular er valideret</string>
|
||||
<string name="autofill_ask_to_save_data_title">Bed om at gemme data</string>
|
||||
<string name="autofill_save_search_info_title">Gem søgeoplysninger</string>
|
||||
<string name="autofill_close_database_summary">Luk databasen efter en markering af autofyld</string>
|
||||
<string name="autofill_close_database_title">Luk database</string>
|
||||
<string name="keyboard_previous_lock_summary">Skift automatisk tilbage til det forrige tastatur efter låsning af databasen</string>
|
||||
<string name="keyboard_previous_lock_title">Lås databasen</string>
|
||||
<string name="keyboard_save_search_info_title">Gem delte oplysninger</string>
|
||||
<string name="notification">Anmeldelse</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Fjerner vedhæftede filer indeholdt i databasen, men ikke knyttet til en post</string>
|
||||
<string name="database_data_remove_unlinked_attachments_title">Fjern ikke-sammenkædede data</string>
|
||||
<string name="data">Data</string>
|
||||
<string name="biometric_security_update_required">Biometrisk sikkerhedsopdatering påkrævet.</string>
|
||||
<string name="warning_empty_keyfile_explanation">Indholdet af nøglefilen bør aldrig ændres og bør i bedste fald indeholde tilfældigt genererede data.</string>
|
||||
<string name="warning_empty_keyfile">Det anbefales ikke at tilføje en tom nøglefil.</string>
|
||||
<string name="warning_sure_remove_data">Fjern alligevel disse data\?</string>
|
||||
<string name="warning_remove_unlinked_attachment">Fjernelse af ikke-linkede data kan mindske størrelsen på databasen, men kan også slette data, der bruges til KeePass-plugins.</string>
|
||||
<string name="warning_sure_add_file">Tilføj alligevel filen\?</string>
|
||||
<string name="warning_replace_file">Overførelsen af filen erstatter den eksisterende.</string>
|
||||
<string name="warning_file_too_big">En KeePass database formodes kun at indeholde små hjælpefiler (såsom PGP nøglefiler).
|
||||
\n
|
||||
\nDatabasen kan blive meget stor og reducere ydeevnen med denne overførelse.</string>
|
||||
<string name="warning_empty_recycle_bin">Slet alle noder permanent fra papirkurven\?</string>
|
||||
<string name="registration_mode">Registreringstilstand</string>
|
||||
<string name="save_mode">Gem tilstand</string>
|
||||
<string name="search_mode">Søgetilstand</string>
|
||||
<string name="error_registration_read_only">Det er ikke tilladt at gemme et nyt element i en skrivebeskyttet database</string>
|
||||
<string name="content_description_credentials_information">Oplysninger om legitimationsoplysninger</string>
|
||||
<string name="configure_biometric">Der er ikke tilmeldt biometriske legitimationsoplysninger eller enhedsoplysninger.</string>
|
||||
<string name="education_add_attachment_summary">Overfør en vedhæftet fil til posten for at gemme vigtige eksterne data.</string>
|
||||
<string name="autofill_save_search_info_summary">Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg</string>
|
||||
<string name="keyboard_save_search_info_summary">Forsøger at gemme delte oplysninger, når der foretages et manuelt indtastningsvalg</string>
|
||||
<string name="error_field_name_already_exists">Feltnavnet findes allerede.</string>
|
||||
</resources>
|
||||
@@ -22,7 +22,7 @@
|
||||
Translations from David Ramiro
|
||||
--><resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="contribution">Beitrag</string>
|
||||
<string name="contribution">Beiträge</string>
|
||||
<string name="feedback">Rückmeldung</string>
|
||||
<string name="homepage">Webseite</string>
|
||||
<string name="about_description">Android-Implementierung des Passwortmanagers KeePass</string>
|
||||
@@ -43,9 +43,9 @@
|
||||
<string name="clipboard_timeout">Zwischenablage-Timeout</string>
|
||||
<string name="clipboard_timeout_summary">Dauer der Speicherung in der Zwischenablage (falls vom Gerät unterstützt)</string>
|
||||
<string name="select_to_copy">%1$s in die Zwischenablage kopieren</string>
|
||||
<string name="retrieving_db_key">Datenbank-Schlüsseldatei erzeugen…</string>
|
||||
<string name="retrieving_db_key">Datenbank-Schlüsseldatei erzeugen …</string>
|
||||
<string name="database">Datenbank</string>
|
||||
<string name="decrypting_db">Entschlüsselung der Datenbankinhalte…</string>
|
||||
<string name="decrypting_db">Entschlüsselung der Datenbankinhalte …</string>
|
||||
<string name="default_checkbox">Als Standard-Datenbank verwenden</string>
|
||||
<string name="digits">Zahlen</string>
|
||||
<string name="html_about_licence">KeePassDX © %1$d Kunzisoft ist <strong>quelloffen</strong> und <strong>ohne Werbung</strong>.\nDie Nutzung der Software erfolgt auf eigene Verantwortung und ohne jegliche Garantie. Die Applikation wird unter den Bedingungen der <strong>GPLv3</strong> lizenziert.</string>
|
||||
@@ -97,7 +97,7 @@
|
||||
<string name="length">Länge</string>
|
||||
<string name="list_size_title">Größe der Listenelemente</string>
|
||||
<string name="list_size_summary">Schriftgröße der Listenelemente</string>
|
||||
<string name="loading_database">Datenbank wird geladen…</string>
|
||||
<string name="loading_database">Datenbank wird geladen …</string>
|
||||
<string name="lowercase">Kleinbuchstaben</string>
|
||||
<string name="hide_password_title">Passwort verstecken</string>
|
||||
<string name="hide_password_summary">Passwörter standardmäßig mit (***) maskieren</string>
|
||||
@@ -118,12 +118,12 @@
|
||||
<string name="never">Nie</string>
|
||||
<string name="no_results">Keine Suchergebnisse</string>
|
||||
<string name="no_url_handler">Bitte einen Webbrowser installieren, um diese URL zu öffnen.</string>
|
||||
<string name="omit_backup_search_title">Papierkorb/Backup nicht durchsuchen</string>
|
||||
<string name="omit_backup_search_summary">Die Gruppen „Backup“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt</string>
|
||||
<string name="omit_backup_search_title">Papierkorb/Sicherung nicht durchsuchen</string>
|
||||
<string name="omit_backup_search_summary">Die Gruppen „Sicherung“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt</string>
|
||||
<string name="auto_focus_search_title">Schnellsuche</string>
|
||||
<string name="auto_focus_search_summary">Beim Öffnen einer Datenbank eine Suche veranlassen</string>
|
||||
<string name="progress_create">Neue Datenbank anlegen…</string>
|
||||
<string name="progress_title">Ausführen…</string>
|
||||
<string name="progress_create">Neue Datenbank anlegen …</string>
|
||||
<string name="progress_title">Ausführen …</string>
|
||||
<string name="protection">Sicherheit</string>
|
||||
<string name="read_only">Schreibgeschützt</string>
|
||||
<string name="read_only_warning">KeePassDX benötigt Schreibrechte, um etwas an der Datenbank zu ändern.</string>
|
||||
@@ -132,7 +132,7 @@
|
||||
<string name="root">Start</string>
|
||||
<string name="rounds">Schlüsseltransformationen</string>
|
||||
<string name="rounds_explanation">Zusätzliche Schlüsseltransformationen bieten einen besseren Schutz gegen Wörterbuch- oder Brute-Force-Angriffe. Allerdings dauert dann auch das Laden und Speichern der Datenbank entsprechend länger.</string>
|
||||
<string name="saving_database">Datenbank wird gespeichert…</string>
|
||||
<string name="saving_database">Datenbank wird gespeichert …</string>
|
||||
<string name="space">Leerzeichen</string>
|
||||
<string name="search_label">Suchen</string>
|
||||
<string name="sort_db">Natürliche Sortierung</string>
|
||||
@@ -185,13 +185,9 @@
|
||||
<string name="lock">Sperren</string>
|
||||
<string name="list_password_generator_options_summary">Erlaubte Zeichen für Passwortgenerator festlegen</string>
|
||||
<string name="list_password_generator_options_title">Passwortzeichen</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Biometrie-Abfrage öffnen, um die Datenbank zu entsperren</string>
|
||||
<string name="encrypted_value_stored">Verschlüsseltes Passwort wurde gespeichert</string>
|
||||
<string name="biometric_invalid_key">Der biometrische Schlüssel kann nicht gelesen werden. Bitte löschen Sie ihn und wiederholen Sie den biometrischen Erkennungsprozess.</string>
|
||||
<string name="biometric_scanning_error">Biometrischer Fehler: %1$s</string>
|
||||
<string name="database_history">Verlauf</string>
|
||||
<string name="general">Allgemein</string>
|
||||
<string name="open_biometric_prompt_store_credential">Biometrische Abfrage öffnen, um Anmeldedaten zu speichern.</string>
|
||||
<string name="no_credentials_stored">Diese Datenbank hat noch kein Passwort.</string>
|
||||
<string name="encryption">Verschlüsselung</string>
|
||||
<string name="key_derivation_function">Schlüsselableitungsfunktion</string>
|
||||
@@ -200,7 +196,6 @@
|
||||
<string name="error_autofill_enable_service">Dienst für automatisches Ausfüllen kann nicht aktiviert werden.</string>
|
||||
<string name="copy_field">Kopie von %1$s</string>
|
||||
<string name="menu_form_filling_settings">Formularausfüllung</string>
|
||||
<string name="menu_biometric_remove_key">Gespeicherten biometrischen Schlüssel löschen</string>
|
||||
<string name="encryption_explanation">Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet.</string>
|
||||
<string name="kdf_explanation">Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird.</string>
|
||||
<string name="memory_usage">Speichernutzung</string>
|
||||
@@ -216,7 +211,6 @@
|
||||
<string name="sort_creation_time">Erstelldatum</string>
|
||||
<string name="sort_last_modify_time">Änderungsdatum</string>
|
||||
<string name="sort_last_access_time">Zugriffsdatum</string>
|
||||
<string name="biometric_not_recognized">Biometrische Daten nicht erkannt</string>
|
||||
<string name="autofill">Automatisches Ausfüllen</string>
|
||||
<string name="autofill_service_name">KeePassDX autom. Formularausfüllung</string>
|
||||
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
|
||||
@@ -225,7 +219,6 @@
|
||||
<string name="clipboard">Zwischenablage</string>
|
||||
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
|
||||
<string name="biometric_delete_all_key_summary">Alle mit der biometrischen Erkennung verbundenen Verschlüsselungsschlüssel löschen.</string>
|
||||
<string name="biometric_delete_all_key_warning">Sind Sie sicher, dass Sie alle mit der biometrischen Erkennung verknüpften Schlüssel löschen möchten\?</string>
|
||||
<string name="unavailable_feature_version">Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.</string>
|
||||
<string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string>
|
||||
<string name="recycle_bin_title">Papierkorb-Nutzung</string>
|
||||
@@ -256,8 +249,6 @@
|
||||
\nGruppen (wie Ordner) helfen, Einträge in der Datenbank zu ordnen.</string>
|
||||
<string name="education_search_title">Einträge durchsuchen</string>
|
||||
<string name="education_search_summary">Titel, Benutzernamen oder Inhalte anderer Feldern eingeben, um die Passwörter wiederzufinden.</string>
|
||||
<string name="education_biometric_title">Biometrische Datenbank-Entsperrung</string>
|
||||
<string name="education_biometric_summary">Verknüpft Ihr Passwort mit Ihrer gescannten Biometrie, um Ihre Datenbank schnell zu entsperren.</string>
|
||||
<string name="education_entry_edit_title">Eintrag bearbeiten</string>
|
||||
<string name="education_entry_edit_summary">Bearbeiten Sie Ihren Eintrag mit persönlichen Feldern. Persönliche Felder können mit Querverweisen aus anderen Einträgen ergänzt werden.</string>
|
||||
<string name="education_generate_password_title">Ein starkes Passwort erstellen</string>
|
||||
@@ -285,12 +276,11 @@
|
||||
<string name="html_text_dev_feature_encourage">Sie ermutigen die Entwickler:innen, <strong>neue Funktionen</strong> einzuführen und gemäß Ihren Anmerkungen <strong>Fehler zu beheben</strong>.</string>
|
||||
<string name="html_text_dev_feature_thanks">Vielen Dank für Ihre Unterstützung.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Wir bemühen uns, diese Funktion bald zu veröffentlichen.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Vergessen Sie nicht, Ihre App aktuell zu halten, indem Sie neue Versionen installieren.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Vergessen Sie nicht, Ihre Anwendung aktuell zu halten, indem Sie neue Versionen installieren.</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="contribute">Unterstützen</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="icon_pack_choose_title">Symbolpaket</string>
|
||||
<string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string>
|
||||
<string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string>
|
||||
@@ -346,7 +336,7 @@
|
||||
<string name="show_recent_files_summary">Speicherort zuletzt verwendeter Datenbanken anzeigen</string>
|
||||
<string name="hide_broken_locations_title">Defekte Datenbankverknüpfungen ausblenden</string>
|
||||
<string name="hide_broken_locations_summary">Nicht funktionierende Verknüpfungen in der Liste der zuletzt verwendeten Datenbanken ausblenden</string>
|
||||
<string name="do_not_kill_app">Nicht die App beenden…</string>
|
||||
<string name="do_not_kill_app">Nicht die Anwendung beenden …</string>
|
||||
<string name="lock_database_back_root_summary">Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird</string>
|
||||
<string name="clear_clipboard_notification_title">Beim Schließen löschen</string>
|
||||
<string name="recycle_bin">Papierkorb</string>
|
||||
@@ -357,7 +347,7 @@
|
||||
<string name="content_description_open_file">Datei öffnen</string>
|
||||
<string name="content_description_add_entry">Eintrag hinzufügen</string>
|
||||
<string name="content_description_add_group">Gruppe hinzufügen</string>
|
||||
<string name="content_description_file_information">Datei-Info</string>
|
||||
<string name="content_description_file_information">Dateiinformationen</string>
|
||||
<string name="content_description_entry_icon">Symbol für den Eintrag</string>
|
||||
<string name="entry_password_generator">Passwort-Generator</string>
|
||||
<string name="content_description_password_length">Passwortlänge</string>
|
||||
@@ -380,13 +370,9 @@
|
||||
<string name="content_description_keyboard_close_fields">Felder schließen</string>
|
||||
<string name="error_create_database_file">Es ist nicht möglich, eine Datenbank mit diesem Passwort und dieser Schlüsseldatei zu erstellen.</string>
|
||||
<string name="menu_advanced_unlock_settings">Erweitertes Entsperren</string>
|
||||
<string name="biometric_prompt_store_credential_title">Biometrische Erkennung speichern</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Datenbank mit biometrischer Erkennung öffnen</string>
|
||||
<string name="biometric">Biometrisch</string>
|
||||
<string name="enable">Aktivieren</string>
|
||||
<string name="disable">Deaktivieren</string>
|
||||
<string name="biometric_prompt_store_credential_message">Achtung: Wenn Sie die biometrische Erkennung verwenden, müssen Sie sich trotzdem Ihr Master-Passwort merken.</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Datenbank-Anmeldeinformationen aus biometrischen Daten extrahieren</string>
|
||||
<string name="biometric_auto_open_prompt_title">Biometrische Abfrage automatisch öffnen</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatisch nach Biometrie fragen, wenn die Datenbank dafür eingerichtet ist</string>
|
||||
<string name="master_key">Hauptschlüssel</string>
|
||||
@@ -408,7 +394,7 @@
|
||||
<string name="error_otp_period">Zeitraum muss zwischen %1$d und %2$d Sekunden liegen.</string>
|
||||
<string name="error_otp_digits">Token muss %1$d bis %2$d Stellen enthalten.</string>
|
||||
<string name="invalid_db_same_uuid">%1$s mit derselben UUID %2$s existiert bereits.</string>
|
||||
<string name="creating_database">Datenbank wird erstellt…</string>
|
||||
<string name="creating_database">Datenbank wird erstellt …</string>
|
||||
<string name="menu_security_settings">Sicherheits-Einstellungen</string>
|
||||
<string name="menu_master_key_settings">Hauptschlüssel-Einstellungen</string>
|
||||
<string name="contains_duplicate_uuid">Die Datenbank enthält UUID-Duplikate.</string>
|
||||
@@ -437,22 +423,21 @@
|
||||
<string name="error_save_database">Die Datenbank konnte nicht gespeichert werden.</string>
|
||||
<string name="menu_save_database">Datenbank speichern</string>
|
||||
<string name="menu_empty_recycle_bin">Papierkorb leeren</string>
|
||||
<string name="command_execution">Befehl ausführen…</string>
|
||||
<string name="warning_permanently_delete_nodes">Sind Sie sicher, dass Sie die ausgewählten Knoten dauerhaft löschen möchten\?</string>
|
||||
<string name="command_execution">Befehl ausführen …</string>
|
||||
<string name="warning_permanently_delete_nodes">Sollen die ausgewählten Knoten wirklich gelöscht werden\?</string>
|
||||
<string name="keystore_not_accessible">Der Schlüsselspeicher ist nicht richtig initialisiert.</string>
|
||||
<string name="credential_before_click_biometric_button">Geben Sie das Passwort ein und klicken Sie auf die Biometrie-Schaltfläche.</string>
|
||||
<string name="recycle_bin_group_title">Papierkorb-Gruppe</string>
|
||||
<string name="enable_auto_save_database_title">Datenbank automatisch speichern</string>
|
||||
<string name="enable_auto_save_database_summary">Automatisches Speichern der Datenbank nach einer wichtigen Aktion (im Modus \"Bearbeiten\")</string>
|
||||
<string name="enable_auto_save_database_summary">Automatisches Speichern der Datenbank nach einer wichtigen Aktion (im Modus „Bearbeiten“)</string>
|
||||
<string name="entry_attachments">Anhänge</string>
|
||||
<string name="menu_restore_entry_history">Historie wiederherstellen</string>
|
||||
<string name="menu_delete_entry_history">Historie löschen</string>
|
||||
<string name="keyboard_auto_go_action_title">Auto-Key-Aktion</string>
|
||||
<string name="keyboard_auto_go_action_summary">Aktion der Go-Taste, die automatisch nach dem Drücken einer Feldtaste ausgeführt wird</string>
|
||||
<string name="download_attachment">%1$s herunterladen</string>
|
||||
<string name="download_initialization">Initialisieren…</string>
|
||||
<string name="download_initialization">Initialisieren …</string>
|
||||
<string name="download_progression">Fortschritt: %1$d%%</string>
|
||||
<string name="download_finalization">Fertigstellen…</string>
|
||||
<string name="download_finalization">Fertigstellen …</string>
|
||||
<string name="download_complete">Vollständig!</string>
|
||||
<string name="hide_expired_entries_title">Abgelaufene Einträge ausblenden</string>
|
||||
<string name="hide_expired_entries_summary">Abgelaufene Einträge werden nicht angezeigt</string>
|
||||
@@ -486,9 +471,9 @@
|
||||
<string name="keyboard_search_share_title">Gemeinsame Infos durchsuchen</string>
|
||||
<string name="autofill_block_restart">Starten Sie die Anwendung, die das Formular enthält, neu, um die Sperrung zu aktivieren.</string>
|
||||
<string name="autofill_block">Automatisches Ausfüllen sperren</string>
|
||||
<string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen unterbunden wird</string>
|
||||
<string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen unterlassen wird</string>
|
||||
<string name="autofill_web_domain_blocklist_title">Liste gesperrter Web-Domains</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen unterbunden wird</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen verhindert wird</string>
|
||||
<string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string>
|
||||
<string name="subdomain_search_summary">Suche Web-Domains mit Subdomain-Beschränkungen</string>
|
||||
<string name="subdomain_search_title">Subdomain-Suche</string>
|
||||
@@ -498,7 +483,7 @@
|
||||
<string name="keyboard_change">Tastatur wechseln</string>
|
||||
<string name="keyboard_previous_fill_in_title">Auto-Key-Aktion</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Datenbank-Anmeldebildschirm</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Automatisches Zurückschalten auf die vorherige Tastatur nach Ausführung der automatischen Tastenaktion</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Nach Ausführung der automatischen Tastenaktion automatisch zur vorherigen Tastatur wechseln</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Automatisches Zurückschalten zur vorherigen Tastatur auf dem Datenbank-Anmeldebildschirm</string>
|
||||
<string name="education_add_attachment_summary">Laden Sie einen Anhang für Ihren Eintrag hoch, um wichtige externe Daten zu speichern.</string>
|
||||
<string name="content_description_credentials_information">Anmeldeinformationen</string>
|
||||
@@ -516,4 +501,56 @@
|
||||
<string name="education_add_attachment_title">Anhang hinzufügen</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Entfernt Anhänge die in der Datenbank enthalten, aber keinem Eintrag zugeordnet sind</string>
|
||||
<string name="warning_sure_add_file">Die Datei trotzdem hinzufügen\?</string>
|
||||
<string name="show_uuid_summary">Zeigt die mit einem Eintrag verknüpfte UUID an</string>
|
||||
<string name="show_uuid_title">UUID anzeigen</string>
|
||||
<string name="autofill_read_only_save">Das Speichern von Daten ist für eine als schreibgeschützt geöffnete Datenbank nicht zulässig.</string>
|
||||
<string name="autofill_close_database_title">Datenbank schließen</string>
|
||||
<string name="keyboard_previous_lock_summary">Automatisches Zurückschalten zur vorherigen Tastatur nach dem Sperren der Datenbank</string>
|
||||
<string name="keyboard_previous_lock_title">Datenbank sperren</string>
|
||||
<string name="notification">Benachrichtigung</string>
|
||||
<string name="biometric_security_update_required">Biometrisches Sicherheitsupdate erforderlich.</string>
|
||||
<string name="configure_biometric">Es sind keine biometrischen oder Geräteanmeldeinformationen registriert.</string>
|
||||
<string name="registration_mode">Registrierungsmodus</string>
|
||||
<string name="save_mode">Speichermodus</string>
|
||||
<string name="search_mode">Suchmodus</string>
|
||||
<string name="error_registration_read_only">Das Speichern eines neuen Elements in einer schreibgeschützten Datenbank ist nicht zulässig</string>
|
||||
<string name="autofill_save_search_info_summary">Versuche bei manueller Eingabeauswahl gemeinsam genutzte Informationen zu speichern</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Speichern von Daten anfordern, wenn ein Formular überprüft wird</string>
|
||||
<string name="autofill_ask_to_save_data_title">Speichern von Daten anfordern</string>
|
||||
<string name="autofill_save_search_info_title">Suchinformationen speichern</string>
|
||||
<string name="autofill_close_database_summary">Datenbank nach der Auswahl des automatischen Ausfüllens schließen</string>
|
||||
<string name="keyboard_save_search_info_summary">Versuche bei manueller Eingabeauswahl gemeinsam genutzte Informationen zu speichern</string>
|
||||
<string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string>
|
||||
<string name="warning_empty_recycle_bin">Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?</string>
|
||||
<string name="error_field_name_already_exists">Der Feldname existiert bereits.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Warnung: Sie müssen sich immer noch an ihr Masterpasswort erinnern, wenn sie die erweiterte Entsperrerkennung verwenden.</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string>
|
||||
<string name="menu_keystore_remove_key">Löschen des Schlüssels zum erweiterten Entsperren</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Fortschrittliche Entsperrerkennung</string>
|
||||
<string name="education_advanced_unlock_summary">Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.</string>
|
||||
<string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string>
|
||||
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Dauer der erweiterten Entsperrung bevor sein Inhalt gelöscht wird</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Verfall der erweiterten Entsperrung</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Temporäre erweiterte Entsperrung</string>
|
||||
<string name="device_credential_unlock_enable_summary">Erlaubt Ihnn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
|
||||
<string name="advanced_unlock_tap_delete">Drücken um erweiterte Entsperrschlüssel zu löschen</string>
|
||||
<string name="content">Inhalt</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit fortgeschriitener Entsperrungs-Erkennung</string>
|
||||
<string name="enter">Eingabetaste</string>
|
||||
<string name="backspace">Rücktaste</string>
|
||||
<string name="select_entry">Wähle Eintrag</string>
|
||||
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
|
||||
<string name="custom_fields">Benutzerdefinierte Felder</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Löschen aller Schlüssel in Zusammenhang mit Erkennung des erweiterterten Entsperrens\?</string>
|
||||
<string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string>
|
||||
<string name="device_credential">Geräteanmeldedaten</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Initialisieren des erweitertes Entsperren Dialogs fehlgeschlagen.</string>
|
||||
<string name="advanced_unlock_scanning_error">Erweitertes Entsperren Fehler: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string>
|
||||
<string name="advanced_unlock_invalid_key">Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</string>
|
||||
</resources>
|
||||
@@ -213,7 +213,7 @@
|
||||
<string name="database_description_title">Περιγραφή Βάσης Δεδομένων</string>
|
||||
<string name="database_version_title">Έκδοση Βάσης Δεδομένων</string>
|
||||
<string name="text_appearance">Κείμενο</string>
|
||||
<string name="application_appearance">Εφαρμογή</string>
|
||||
<string name="application_appearance">Διεπαφή</string>
|
||||
<string name="other">Άλλα</string>
|
||||
<string name="keyboard">Πληκτρολόγιο</string>
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
@@ -257,12 +257,11 @@
|
||||
<string name="html_text_dev_feature_encourage">ενθαρρύνετε τους προγραμματιστές να δημιουργούν <strong>νέες λειτουργίες</strong> και να <strong>διορθώνουν σφάλματα</strong> σύμφωνα με τις παρατηρήσεις σας.</string>
|
||||
<string name="html_text_dev_feature_thanks">Ευχαριστούμε πολύ για τη συνεισφορά σας.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Εργαζόμαστε σκληρά για να διαθέσουμε αυτό το χαρακτηριστικό γρήγορα.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Μην ξεχνάτε να ενημερώνετε την εφαρμογή σας, εγκαθιστώντας νέες εκδόσεις.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Θυμηθείτε να ενημερώνετε την εφαρμογή σας, εγκαθιστώντας νέες εκδόσεις.</string>
|
||||
<string name="download">Download</string>
|
||||
<string name="contribute">Συνεισφορά</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="style_choose_title">Θέμα Εφαρμογής</string>
|
||||
<string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string>
|
||||
<string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string>
|
||||
@@ -286,7 +285,7 @@
|
||||
<string name="education_read_only_summary">Αλλάξτε τη λειτουργία ανοίγματος για το session.
|
||||
\n
|
||||
\nΤο \"Προστατευμένο από εγγραφή\" αποτρέπει τυχόν μη επιθυμητές αλλαγές στη βάση δεδομένων.
|
||||
\nΤο \"Τροποποιητικό\" σάς επιτρέπει να προσθέσετε, να διαγράψετε ή να τροποποιήσετε όλα τα στοιχεία.</string>
|
||||
\nΤο \"Τροποποιητικό\" σάς επιτρέπει να προσθέσετε, να διαγράψετε ή να τροποποιήσετε όλα τα στοιχεία όπως επιθυμείτε.</string>
|
||||
<string name="edit_entry">Επεξεργασία καταχώρησης</string>
|
||||
<string name="error_load_database">Δεν ήταν δυνατή η φόρτωση της βάσης δεδομένων σας.</string>
|
||||
<string name="error_load_database_KDF_memory">Δεν ήταν δυνατή η φόρτωση του κλειδιού. Προσπαθήστε να μειώσετε την KDF \"Χρήση μνήμης\".</string>
|
||||
@@ -315,7 +314,7 @@
|
||||
<string name="selection_mode">Λειτουργία επιλογής</string>
|
||||
<string name="do_not_kill_app">Μη κλείσιμο της εφαρμογής …</string>
|
||||
<string name="lock_database_back_root_title">Πατήστε \'Πίσω\' για να κλειδώσετε</string>
|
||||
<string name="lock_database_back_root_summary">Κλείδωμα της βάσης δεδομένων όταν ο χρήστης κάνει κλικ στο κουμπί \"πίσω\" στη αρχική οθόνη</string>
|
||||
<string name="lock_database_back_root_summary">Κλείδωμα της βάσης δεδομένων όταν ο χρήστης κάνει κλικ στο κουμπί \"πίσω\" στην οθόνη προέλευσης</string>
|
||||
<string name="clear_clipboard_notification_title">Καθαρισμός στο κλείσιμο</string>
|
||||
<string name="clear_clipboard_notification_summary">Κλείδωμα της βάσης δεδομένων όταν λήξει η διάρκεια του προχείρου ή η ειδοποίηση κλείσει αφού αρχίσετε να την χρησιμοποιείτε</string>
|
||||
<string name="recycle_bin">Κάδος ανακύκλωσης</string>
|
||||
@@ -347,27 +346,16 @@
|
||||
<string name="content_description_keyboard_close_fields">Κλείσιμο πεδίων</string>
|
||||
<string name="error_create_database_file">Δεν είναι δυνατή η δημιουργία βάσης δεδομένων με αυτόν τον κωδικό πρόσβασης και το αρχείο κλειδί.</string>
|
||||
<string name="menu_advanced_unlock_settings">Προηγμένο ξεκλείδωμα</string>
|
||||
<string name="menu_biometric_remove_key">Διαγράψτε το αποθηκευμένο βιομετρικό κλειδί</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Ανοίξτε τη βιομετρική προτροπή για να ξεκλειδώσετε τη βάση δεδομένων</string>
|
||||
<string name="open_biometric_prompt_store_credential">Ανοίξτε τη βιομετρική προτροπή για την αποθήκευση διαπιστευτηρίων</string>
|
||||
<string name="biometric_prompt_store_credential_title">Αποθήκευση βιομετρικής αναγνώρισης</string>
|
||||
<string name="biometric_prompt_store_credential_message">Προειδοποίηση: Πρέπει ακόμα να θυμάστε τον κύριο κωδικό πρόσβασης σας εάν χρησιμοποιείτε βιομετρική αναγνώριση.</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Άνοιγμα βάσης δεδομένων με βιομετρική αναγνώριση</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Εξαγωγή της πιστοποίησης βάσης δεδομένων με βιομετρικά δεδομένα</string>
|
||||
<string name="biometric_invalid_key">Δεν είναι δυνατή η ανάγνωση του βιομετρικού κλειδιού. Διαγράψτε το και επαναλάβετε τη διαδικασία βιομετρικής αναγνώρισης.</string>
|
||||
<string name="biometric_not_recognized">Δεν ήταν δυνατή η αναγνώριση βιομετρικών στοιχείων</string>
|
||||
<string name="biometric_scanning_error">Βιομετρικό σφάλμα: %1$s</string>
|
||||
<string name="no_credentials_stored">Αυτή η βάση δεδομένων δεν έχει αποθηκευμένα διαπιστευτήρια ακόμα.</string>
|
||||
<string name="menu_appearance_settings">Εμφάνιση</string>
|
||||
<string name="biometric">Βιομετρία</string>
|
||||
<string name="advanced_unlock">Προηγμένο ξεκλείδωμα</string>
|
||||
<string name="biometric_unlock_enable_title">Βιομετρικό ξεκλείδωμα</string>
|
||||
<string name="biometric_unlock_enable_summary">Σας επιτρέπει να σαρώσετε το βιομετρικό σας για να ανοίξετε τη βάση δεδομένων</string>
|
||||
<string name="biometric_auto_open_prompt_title">Αυτόματο άνοιγμα βιομετρικής προτροπής</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Ζητήστε αυτόματα βιομετρία εάν η βάση δεδομένων έχει ρυθμιστεί για να τη χρησιμοποιήσει</string>
|
||||
<string name="biometric_auto_open_prompt_title">Αυτόματο άνοιγμα προτροπής</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Ζητήστε αυτόματα προηγμένο ξεκλείδωμα εάν η βάση δεδομένων έχει ρυθμιστεί για να το χρησιμοποιήσει</string>
|
||||
<string name="biometric_delete_all_key_title">Διαγράψτε τα κλειδιά κρυπτογράφησης</string>
|
||||
<string name="biometric_delete_all_key_summary">Διαγράψτε όλα τα κλειδιά κρυπτογράφησης που σχετίζονται με τη βιομετρική αναγνώριση</string>
|
||||
<string name="biometric_delete_all_key_warning">Διαγραφή όλων των κλειδιών κρυπτογράφησης που σχετίζονται με τη βιομετρική αναγνώριση;</string>
|
||||
<string name="biometric_delete_all_key_summary">Διαγράψτε όλα τα κλειδιά κρυπτογράφησης που σχετίζονται με το προηγμένο ξεκλείδωμα</string>
|
||||
<string name="enable">Ενεργοποίηση</string>
|
||||
<string name="disable">Απενεργοποίηση</string>
|
||||
<string name="master_key">Κύριο κλειδί</string>
|
||||
@@ -418,15 +406,12 @@
|
||||
<string name="compression_gzip">Gzip</string>
|
||||
<string name="magic_keyboard_explanation_summary">Ενεργοποιώντας ένα προσαρμοσμένο πληκτρολόγιο συγκεντρώνει τους κωδικούς πρόσβασής σας και όλα τα πεδία ταυτότητας</string>
|
||||
<string name="device_keyboard_setting_title">Ρυθμίσεις πληκτρολογίου συσκευής</string>
|
||||
<string name="education_biometric_title">Βιομετρικό ξεκλείδωμα βάσης δεδομένων</string>
|
||||
<string name="education_biometric_summary">Συνδέστε τον κωδικό πρόσβασής σας στο σαρωμένο βιομετρικό σας για να ξεκλειδώσετε γρήγορα τη βάση δεδομένων σας.</string>
|
||||
<string name="error_save_database">Δεν ήταν δυνατή η αποθήκευση της βάσης δεδομένων.</string>
|
||||
<string name="menu_save_database">Αποθήκευση βάσης δεδομένων</string>
|
||||
<string name="menu_empty_recycle_bin">Αδειάστε τον κάδο ανακύκλωσης</string>
|
||||
<string name="command_execution">Εκτέλεση της εντολής…</string>
|
||||
<string name="warning_permanently_delete_nodes">Οριστική διαγραφή επιλεγμένων κόμβων;</string>
|
||||
<string name="keystore_not_accessible">Η κλειδοθήκη δεν έχει προετοιμαστεί σωστά.</string>
|
||||
<string name="credential_before_click_biometric_button">Πληκτρολογήστε τον κωδικό πρόσβασης και στη συνέχεια κάντε κλικ στο κουμπί \"Biometric\".</string>
|
||||
<string name="recycle_bin_group_title">Ομάδα Κάδου Ανακύκλωσης</string>
|
||||
<string name="enable_auto_save_database_title">Αυτόματη αποθήκευση βάσης δεδομένων</string>
|
||||
<string name="enable_auto_save_database_summary">Αποθήκευση της βάσης δεδομένων μετά από κάθε σημαντική ενέργεια (σε λειτουργία \"Τροποποιήσιμο\")</string>
|
||||
@@ -480,7 +465,7 @@
|
||||
<string name="autofill_application_id_blocklist_title">Λίστα αποκλεισμού Εφαρμογών</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Λίστα αποκλεισμού που αποτρέπει την αυτόματη συμπλήρωση εφαρμογών</string>
|
||||
<string name="keyboard_previous_fill_in_title">Αυτόματη ενέργεια πλήκτρου</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Επιστρέψτε αυτόματα στο προηγούμενο πληκτρολόγιο μετά την εκτέλεση της ενέργειας του αυτόματου πλήκτρου</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Επιστρέψτε αυτόματα στο προηγούμενο πληκτρολόγιο μετά την εκτέλεση της ενέργειας του \"Αυτόματου πλήκτρου\"</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Επιστρέψτε αυτόματα στο προηγούμενο πληκτρολόγιο στην οθόνη διαπιστευτηρίων βάσης δεδομένων</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Οθόνη διαπιστευτηρίων βάσης δεδομένων</string>
|
||||
<string name="keyboard_change">Εναλλαγή πληκτρολογίου</string>
|
||||
@@ -504,4 +489,56 @@
|
||||
<string name="error_string_type">Αυτό το κείμενο δεν ταιριάζει με το ζητούμενο στοιχείο.</string>
|
||||
<string name="content_description_credentials_information">Πληροφορίες Διαπιστευτηρίων</string>
|
||||
<string name="content_description_add_item">Προσθήκη είδους</string>
|
||||
<string name="show_uuid_summary">Εμφανίζει το UUID που είναι συνδεδεμένο σε μια καταχώριση</string>
|
||||
<string name="show_uuid_title">Εμφάνιση UUID</string>
|
||||
<string name="autofill_read_only_save">Δεν επιτρέπεται η αποθήκευση δεδομένων για μια βάση δεδομένων που ανοίγει ως μόνο για ανάγνωση.</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Ζητήστε να αποθηκεύσετε δεδομένα όταν επικυρώνεται μια φόρμα</string>
|
||||
<string name="autofill_ask_to_save_data_title">Ζητήστε να αποθηκεύσετε δεδομένα</string>
|
||||
<string name="autofill_save_search_info_summary">Προσπαθήστε να αποθηκεύσετε πληροφορίες αναζήτησης κατά την επιλογή χειροκίνητης καταχώρισης</string>
|
||||
<string name="autofill_save_search_info_title">Αποθήκευση πληροφοριών αναζήτησης</string>
|
||||
<string name="autofill_close_database_summary">Κλείστε τη βάση δεδομένων μετά από μια επιλογή αυτόματης συμπλήρωσης</string>
|
||||
<string name="autofill_close_database_title">Κλείσιμο βάσης δεδομένων</string>
|
||||
<string name="keyboard_previous_lock_summary">Επιστρέψτε αυτόματα στο προηγούμενο πληκτρολόγιο μετά το κλείδωμα της βάσης δεδομένων</string>
|
||||
<string name="keyboard_previous_lock_title">Κλείδωμα βάσης δεδομένων</string>
|
||||
<string name="keyboard_save_search_info_summary">Προσπαθήστε να αποθηκεύσετε κοινόχρηστες πληροφορίες όταν κάνετε μια χειροκίνητη επιλογή καταχώρησης</string>
|
||||
<string name="keyboard_save_search_info_title">Αποθήκευση κοινόχρηστων πληροφοριών</string>
|
||||
<string name="notification">Ειδοποίηση</string>
|
||||
<string name="biometric_security_update_required">Απαιτείται ενημέρωση βιομετρικής ασφάλειας.</string>
|
||||
<string name="configure_biometric">Κανένα πιστοποιητικό βιομετρίας ή συσκευής δεν είναι εγγεγραμμένο.</string>
|
||||
<string name="warning_empty_recycle_bin">Να διαγραφούν οριστικά όλοι οι κόμβοι από τον κάδο ανακύκλωσης;</string>
|
||||
<string name="registration_mode">Τρόπος εγγραφής</string>
|
||||
<string name="save_mode">Λειτουργία αποθήκευσης</string>
|
||||
<string name="search_mode">Λειτουργία αναζήτησης</string>
|
||||
<string name="error_registration_read_only">Η αποθήκευση ενός νέου αντικειμένου δεν επιτρέπεται σε μια βάση δεδομένων μόνο για ανάγνωση</string>
|
||||
<string name="error_field_name_already_exists">Το όνομα πεδίου υπάρχει ήδη.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Προηγμένο ξεκλείδωμα αναγνώρισης</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Προειδοποίηση: Θα πρέπει να θυμάστε τον κύριο κωδικό πρόσβασης εάν χρησιμοποιείτε προηγμένο ξεκλείδωμα αναγνώρισης.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Ανοίξτε τη βάση δεδομένων με προηγμένο ξεκλείδωμα αναγνώρισης</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για αποθήκευση διαπιστευτηρίων</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Ανοίξτε τη προηγμένη προτροπή ξεκλειδώματος για να ξεκλείιδώσετε τη βάση δεδομένων</string>
|
||||
<string name="menu_keystore_remove_key">Διαγραφή προηγμένου κλειδιού ξεκλειδώματος</string>
|
||||
<string name="enter">Enter</string>
|
||||
<string name="backspace">Backspace</string>
|
||||
<string name="select_entry">Επιλέξτε καταχώριση</string>
|
||||
<string name="back_to_previous_keyboard">Επιστροφή στο προηγούμενο πληκτρολόγιο</string>
|
||||
<string name="custom_fields">Προσαρμοσμένα πεδία</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Διαγραφή όλων των κλειδιών κρυπτογράφησης που σχετίζονται με το προηγμένο ξεκλείδωμα αναγνώρισης;</string>
|
||||
<string name="device_credential_unlock_enable_summary">Σας επιτρέπει να χρησιμοποιήσετε τα διαπιστευτήρια της συσκευής σας για να ανοίξετε τη βάση δεδομένων</string>
|
||||
<string name="device_credential_unlock_enable_title">Ξεκλείδωμα διαπιστευτηρίων συσκευής</string>
|
||||
<string name="device_credential">Διαπιστευτήρια συσκευής</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Πληκτρολογήστε τον κωδικό πρόσβασης, και στη συνέχεια κάντε κλικ στο κουμπί \"Προηγμένο ξεκλείδωμα\".</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Δεν είναι δυνατή η προετοιμασία προτροπής προηγμένου ξεκλειδώματος.</string>
|
||||
<string name="advanced_unlock_not_recognized">Δεν ήταν δυνατή η αναγνώριση αποτυπώματος προηγμένου ξεκλειδώματος</string>
|
||||
<string name="advanced_unlock_scanning_error">Προηγμένο ξεκλείδωμα σφάλμα: %1$s</string>
|
||||
<string name="advanced_unlock_invalid_key">Δεν είναι δυνατή η ανάγνωση του προηγμένου κλειδιού ξεκλειδώματος. Διαγράψτε το και επαναλάβετε τη διαδικασία αναγνώρισης ξεκλειδώματος.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Εξαγωγή διαπιστευτηρίων βάσης δεδομένων με προηγμένο ξεκλείδωμα δεδομένων</string>
|
||||
<string name="education_advanced_unlock_summary">Συνδέστε τον κωδικό πρόσβασής σας με το σαρωμένο βιομετρικό ή τα διαπιστευτήρια της συσκευής σας για να ξεκλειδώσετε γρήγορα τη βάση δεδομένων σας.</string>
|
||||
<string name="education_advanced_unlock_title">Προηγμένο ξεκλείδωμα βάσης δεδομένων</string>
|
||||
<string name="advanced_unlock_timeout">Χρονικό όριο προηγμένου ξεκλειδώματος</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Προσωρινό προηγμένο ξεκλείδωμα</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Μην αποθηκεύετε κανένα κρυπτογραφημένο περιεχόμενο για να χρησιμοποιήσετε προηγμένο ξεκλείδωμα</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Διάρκεια της χρήσης προηγμένου ξεκλειδώματος πριν την διαγραφή του περιεχομένου</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Λήξη προηγμένου ξεκλειδώματος</string>
|
||||
<string name="advanced_unlock_tap_delete">Πατήστε για διαγραφή προηγμένων κλειδιών ξεκλειδώματος</string>
|
||||
<string name="content">Περιεχόμενα</string>
|
||||
</resources>
|
||||
@@ -157,7 +157,6 @@
|
||||
<string name="keyfile_is_empty">El archivo de clave está vacío.</string>
|
||||
<string name="copy_field">Copia de %1$s</string>
|
||||
<string name="menu_form_filling_settings">Llenado de formulario</string>
|
||||
<string name="menu_biometric_remove_key">Quite la clave de huella dactilar</string>
|
||||
<string name="protection">Protección</string>
|
||||
<string name="read_only">Protegida contra escritura</string>
|
||||
<string name="read_only_warning">KeePassDX necesita permiso de escritura para modificar la base de datos.</string>
|
||||
@@ -182,14 +181,9 @@
|
||||
<string name="warning_password_encoding">Evite emplear en la base de datos caracteres que no pertenezcan al formato de codificación del texto (los caracteres no reconocidos se convierten a la misma letra).</string>
|
||||
<string name="warning_empty_password">¿Continuar sin la protección de desbloqueo de contraseña\?</string>
|
||||
<string name="warning_no_encryption_key">¿Continuar sin clave de cifrado\?</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Abra la petición de datos biométricos para desbloquear la base de datos</string>
|
||||
<string name="encrypted_value_stored">Contraseña cifrada almacenada</string>
|
||||
<string name="biometric_invalid_key">No se puede leer la clave biométrica. Bórrela y repita el procedimiento de reconocimiento biométrico.</string>
|
||||
<string name="biometric_not_recognized">No fue posible identificar los datos biométricos</string>
|
||||
<string name="biometric_scanning_error">Error de biometría: %1$s</string>
|
||||
<string name="database_history">Historial</string>
|
||||
<string name="autofill_explanation_summary">Habilite el servicio para completar formularios fácilmente desde otras aplicaciones</string>
|
||||
<string name="open_biometric_prompt_store_credential">Abra la petición de datos biométricos para almacenar credenciales</string>
|
||||
<string name="no_credentials_stored">Esta base de datos aún no tiene credenciales almacenadas.</string>
|
||||
<string name="menu_appearance_settings">Apariencia</string>
|
||||
<string name="general">General</string>
|
||||
@@ -212,7 +206,6 @@
|
||||
<string name="biometric_unlock_enable_summary">Le permite escanear su huella dactilar u otro dato biométrico para abrir la base de datos</string>
|
||||
<string name="biometric_delete_all_key_title">Eliminar claves de cifrado</string>
|
||||
<string name="biometric_delete_all_key_summary">Eliminar todas las claves de cifrado relacionadas con el reconocimiento biométrico</string>
|
||||
<string name="biometric_delete_all_key_warning">¿Confirma que quiere eliminar todas las claves relativas al reconocimiento biométrico\?</string>
|
||||
<string name="unavailable_feature_text">No se pudo iniciar esta funcionalidad.</string>
|
||||
<string name="unavailable_feature_version">La versión de Android del dispositivo es %1$s, pero es necesaria la %2$s o posterior.</string>
|
||||
<string name="unavailable_feature_hardware">No se encontró el hardware correspondiente.</string>
|
||||
@@ -248,8 +241,6 @@
|
||||
\nLos grupos (~carpetas) organizan las entradas en su base de datos.</string>
|
||||
<string name="education_search_title">Busque registros fácilmente</string>
|
||||
<string name="education_search_summary">Busque entradas por título, nombre de usuario u otros campos para recuperar fácilmente sus contraseñas.</string>
|
||||
<string name="education_biometric_title">Desbloquee su base de datos con su huella digital</string>
|
||||
<string name="education_biometric_summary">Vincule la contraseña a su escaneo biométrico para desbloquear rápidamente la base de datos.</string>
|
||||
<string name="education_entry_edit_title">Editar la entrada</string>
|
||||
<string name="education_entry_edit_summary">Edite la entrada con campos personalizados, puede agregar referencias a los datos de la agrupación entre campos de diferentes entradas.</string>
|
||||
<string name="education_generate_password_title">Crear una contraseña segura</string>
|
||||
@@ -282,7 +273,6 @@
|
||||
<string name="contribute">Contribuir</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES-KDF</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="style_choose_title">Tema de aplicación</string>
|
||||
<string name="style_choose_summary">Tema utilizado en la aplicación</string>
|
||||
<string name="icon_pack_choose_title">Seleccione un paquete de iconos</string>
|
||||
@@ -360,10 +350,6 @@
|
||||
<string name="content_description_keyboard_close_fields">Cerrar campos</string>
|
||||
<string name="error_create_database_file">No se puede crear la base de datos con esta contraseña y este archivo de clave.</string>
|
||||
<string name="menu_advanced_unlock_settings">Desbloqueo avanzado</string>
|
||||
<string name="biometric_prompt_store_credential_title">Guardar reconocimiento biométrico</string>
|
||||
<string name="biometric_prompt_store_credential_message">Atención: debe recordar su contraseña maestra aunque use el reconocimiento biométrico.</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Abrir base de datos con reconocimiento biométrico</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Extraer credencial de base de datos con datos biométricos</string>
|
||||
<string name="biometric">Biometría</string>
|
||||
<string name="biometric_auto_open_prompt_title">Abrir petición de datos biométricos automáticamente</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Abrir automáticamente la petición de datos biométricos cuando se define una clave biométrica para una base de datos</string>
|
||||
@@ -409,7 +395,7 @@
|
||||
<string name="compression_none">Ninguna</string>
|
||||
<string name="compression">Compresión</string>
|
||||
<string name="database_default_username_title">Nombre de usuario predeterminado</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">Requerir cambiar la contraseña maestra la próxima vez (una sóla vez)</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">Requerir cambiar la contraseña maestra la próxima vez (una sola vez)</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_title">Forzar renovación la próxima vez</string>
|
||||
<string name="settings_database_force_changing_master_key_summary">Requerir un cambio de contraseña maestra (días)</string>
|
||||
<string name="settings_database_force_changing_master_key_title">Forzar renovación</string>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
--><resources>
|
||||
<string name="feedback">Feedback</string>
|
||||
<string name="homepage">Hasiera orria</string>
|
||||
<string name="about_description">Keepass pasahitza kudeatzailearen Androiderako inplementazioa</string>
|
||||
<string name="about_description">KeePass pasahitza kudeatzailearen Androiderako inplementazioa</string>
|
||||
<string name="accept">Onartu</string>
|
||||
<string name="add_entry">Sarrera gehitu</string>
|
||||
<string name="add_group">Taldea gehitu</string>
|
||||
|
||||
@@ -2,18 +2,8 @@
|
||||
<resources>
|
||||
<string name="menu_appearance_settings">ظاهر</string>
|
||||
<string name="database_history">تاریخچه</string>
|
||||
<string name="credential_before_click_biometric_button">رمز ورود را وارد کنید و سپس روی دکمه \"بیومتریک\" کلیک کنید.</string>
|
||||
<string name="no_credentials_stored">این پایگاه داده هنوز اطلاعات کاربری ذخیره نشده است.</string>
|
||||
<string name="biometric_scanning_error">خطای بیومتریک:%1$s</string>
|
||||
<string name="biometric_not_recognized">بایومتریک قابل تشخیص نیست</string>
|
||||
<string name="biometric_invalid_key">"کلید بیومتریک را نمی توان خواند. لطفاً آن را حذف کرده و روش شناخت بیومتریک را تکرار کنید."</string>
|
||||
<string name="encrypted_value_stored">رمز رمزگذاری شده ذخیره شده است</string>
|
||||
<string name="biometric_prompt_extract_credential_message">استخراج اطلاعات کاربری پایگاه داده با داده های بیومتریک</string>
|
||||
<string name="biometric_prompt_extract_credential_title">پایگاه داده را با تشخیص بیومتریک باز کنید</string>
|
||||
<string name="biometric_prompt_store_credential_message">هشدار: اگر از تشخیص بیومتریک استفاده می کنید ، هنوز باید رمز عبور اصلی خود را به خاطر بسپارید.</string>
|
||||
<string name="biometric_prompt_store_credential_title">تشخیص بیومتریک را ذخیره کنید</string>
|
||||
<string name="open_biometric_prompt_store_credential">اعلان بیومتریک را برای ذخیره اعتبارنامه باز کنید</string>
|
||||
<string name="open_biometric_prompt_unlock_database">برای باز کردن قفل پایگاه داده ، دستور بیومتریک را باز کنید</string>
|
||||
<string name="keystore_not_accessible">فروشگاه اصلی به درستی تنظیم نشده است.</string>
|
||||
<string name="build_label">%1$s را بسازید</string>
|
||||
<string name="version_label">نسخه %1$s</string>
|
||||
@@ -88,7 +78,6 @@
|
||||
<string name="menu_open_file_read_and_write">قابل تغییر</string>
|
||||
<string name="menu_file_selection_read_only">نوشتن-محافظت شده</string>
|
||||
<string name="menu_url">رفتن به آدرس اینترنتی</string>
|
||||
<string name="menu_biometric_remove_key">حذف کلید بیومتریک ذخیره شده</string>
|
||||
<string name="menu_showpass">نمایش رمز عبور</string>
|
||||
<string name="menu_search">جستجو</string>
|
||||
<string name="menu_open">باز</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="menu_app_settings">Ohjelman asetukset</string>
|
||||
<string name="brackets">Hakasulkeet</string>
|
||||
<string name="file_manager_install_description">Tietokantojen avaamista, luomista ja tallentamista varten tarvitaan tiedostonhallintaohjelma, joka tukee ACTION_CREATE_DOCUMENT ja ACTION_OPEN_DOCUMENT Intent-toimintoja.</string>
|
||||
<string name="clipboard_cleared">Leikepöytä tyhjennetty.</string>
|
||||
<string name="clipboard_cleared">Leikepöytä tyhjennetty</string>
|
||||
<string name="clipboard_error_title">Leikepöytävirhe</string>
|
||||
<string name="clipboard_error">Jotkin laitteet eivät anna sovellusten käyttää leikepöytää.</string>
|
||||
<string name="clipboard_error_clear">Leikepöytää ei voitu tyhjentää</string>
|
||||
@@ -71,7 +71,7 @@
|
||||
<string name="error_pass_match">Salasanat eivät täsmää.</string>
|
||||
<string name="error_rounds_too_large">Kierroksia on liian paljon. Asetetaan se arvoon 2147483648.</string>
|
||||
<string name="error_string_key">Jokaisella tekstillä tulee olla kentässä nimi.</string>
|
||||
<string name="error_wrong_length">Syötä positiivinen kokonaisluku pituus-kenttään</string>
|
||||
<string name="error_wrong_length">Syötä positiivinen kokonaisluku pituus-kenttään.</string>
|
||||
<string name="field_name">Kentän nimi</string>
|
||||
<string name="field_value">Kentän arvo</string>
|
||||
<string name="file_browser">Tiedostoselain</string>
|
||||
@@ -161,7 +161,7 @@
|
||||
<string name="security">Turvallisuus</string>
|
||||
<string name="edit_entry">Muokkaa merkintää</string>
|
||||
<string name="error_disallow_no_credentials">Ainakin yksi pääsytieto tulee olla asetettuna.</string>
|
||||
<string name="error_load_database_KDF_memory">Avainta ei pystytty lataamaan. Kokeile vähentää KDF \"Muistin käyttöä\".</string>
|
||||
<string name="error_load_database_KDF_memory">Avainta ei pystytty lataamaan. Kokeile vähentää KDF ”Muistin käyttöä”.</string>
|
||||
<string name="error_load_database">Tietokantaa ei pystytty avaamaan.</string>
|
||||
<string name="error_invalid_OTP">Virheellinen OTP salaisuus.</string>
|
||||
<string name="entry_otp">OTP</string>
|
||||
@@ -208,17 +208,8 @@
|
||||
<string name="autofill_explanation_summary">Ota käyttöön automaattinen täyttö täyttääksesi lomakkeita nopeasti muissa sovelluksissa</string>
|
||||
<string name="menu_appearance_settings">Ulkonäkö</string>
|
||||
<string name="database_history">Historia</string>
|
||||
<string name="credential_before_click_biometric_button">Kirjoita salana ja paina \"Biometrinen\" painiketta.</string>
|
||||
<string name="biometric">Biometrinen</string>
|
||||
<string name="no_credentials_stored">Tässä salasanatietokannassa ei ole vielä pääsytietoja.</string>
|
||||
<string name="biometric_scanning_error">Biometrinen virhe: %1$s</string>
|
||||
<string name="biometric_not_recognized">Biometristä tunnistusta ei tunnistettu</string>
|
||||
<string name="biometric_invalid_key">Biometristä avainta ei voitu lukea. Poista se ja toista biometrinen tunnistus.</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Avaa salasanatietokanta biometrisellä tunnistuksella</string>
|
||||
<string name="biometric_prompt_store_credential_message">Varoitus: Sinun pitää vielä muistaa pääsalasanasi jos käytät biometristä tunnistusta.</string>
|
||||
<string name="biometric_prompt_store_credential_title">Tallenna biometrinen tunnistus</string>
|
||||
<string name="open_biometric_prompt_store_credential">Avaa biometrinen komentokehote tallentaaksesi pääsytiedot</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Avaa biometrinen komentokehote avataksesi salasanatietokannan</string>
|
||||
<string name="keystore_not_accessible">Avainsäilöä ei ole kunnolla alustettu.</string>
|
||||
<string name="build_label">Koontiversio %1$s</string>
|
||||
<string name="warning_database_link_revoked">Tiedostoon pääsy evätty</string>
|
||||
@@ -241,7 +232,7 @@
|
||||
<string name="allow_copy_password_title">Luota leikepöytään</string>
|
||||
<string name="monospace_font_fields_enable_summary">Vaihda fontti, jota käytetään kentissä parantaaksesi merkkien näkyvyyttä</string>
|
||||
<string name="monospace_font_fields_enable_title">Kenttäfontti</string>
|
||||
<string name="recycle_bin_summary">Siirrä ryhmät ja tietueet \"Roskakori\" ryhmään ennen poistamista</string>
|
||||
<string name="recycle_bin_summary">Siirrä ryhmät ja tietueet ”Roskakori” ryhmään ennen poistamista</string>
|
||||
<string name="recycle_bin_title">Roskakorin käyttö</string>
|
||||
<string name="assign_master_key">Aseta pääavain</string>
|
||||
<string name="path">Polku</string>
|
||||
@@ -302,7 +293,6 @@
|
||||
<string name="menu_restore_entry_history">Palauta historia</string>
|
||||
<string name="menu_empty_recycle_bin">Tyhjennä roskakori</string>
|
||||
<string name="menu_file_selection_read_only">Kirjoitussuojattu</string>
|
||||
<string name="menu_biometric_remove_key">Poista tallennettu biometrinen avain</string>
|
||||
<string name="menu_save_database">Tallenna salasanatietokanta</string>
|
||||
<string name="menu_cancel">Peruuta</string>
|
||||
<string name="menu_paste">Liitä</string>
|
||||
@@ -334,4 +324,4 @@
|
||||
<string name="error_move_folder_in_itself">Et voi siirtää ryhmää itsensä sisälle.</string>
|
||||
<string name="error_autofill_enable_service">Automaattista täyttöä ei voitu ottaa käyttöön.</string>
|
||||
<string name="content_description_node_children">Solmun lapset</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -33,7 +33,7 @@
|
||||
<string name="menu_form_filling_settings">Remplissage de formulaire</string>
|
||||
<string name="brackets">Parenthèses (ou autres)</string>
|
||||
<string name="extended_ASCII">ASCII étendu</string>
|
||||
<string name="file_manager_install_description">Un gestionnaire de fichiers qui accepte l\'action d\'intention ACTION_CREATE_DOCUMENT et ACTION_OPEN_DOCUMENT est nécessaire pour créer, ouvrir et enregistrer des fichiers de base de données.</string>
|
||||
<string name="file_manager_install_description">Un gestionnaire de fichiers qui accepte l’action d’intention ACTION_CREATE_DOCUMENT et ACTION_OPEN_DOCUMENT est nécessaire pour créer, ouvrir et enregistrer des fichiers de base de données.</string>
|
||||
<string name="allow">Autoriser</string>
|
||||
<string name="clipboard_cleared">Presse-papier vidé</string>
|
||||
<string name="clipboard_error_title">Erreur de presse-papier</string>
|
||||
@@ -113,7 +113,6 @@
|
||||
<string name="menu_open">Ouvrir</string>
|
||||
<string name="menu_search">Rechercher</string>
|
||||
<string name="menu_showpass">Afficher le mot de passe</string>
|
||||
<string name="menu_biometric_remove_key">Supprimer l’empreinte biométrique enregistrée</string>
|
||||
<string name="menu_url">Ouvrir l’URL</string>
|
||||
<string name="minus">Moins</string>
|
||||
<string name="never">Jamais</string>
|
||||
@@ -162,13 +161,8 @@
|
||||
<string name="warning_no_encryption_key">Continuer sans clé de chiffrement \?</string>
|
||||
<string name="version_label">Version %1$s</string>
|
||||
<string name="configure_biometric">Aucune information d’identification biométrique ou de périphérique n’est enregistrée.</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Ouvrir l’invite biométrique pour déverrouiller la base de données</string>
|
||||
<string name="encrypted_value_stored">Mot de passe chiffré stocké</string>
|
||||
<string name="database_history">Historique</string>
|
||||
<string name="biometric_invalid_key">Impossible de lire la clé biométrique. Veuillez la supprimer et répéter la procédure de reconnaissance biométrique.</string>
|
||||
<string name="biometric_not_recognized">Impossible de reconnaître l’empreinte biométique</string>
|
||||
<string name="biometric_scanning_error">Erreur biométrique : %1$s</string>
|
||||
<string name="open_biometric_prompt_store_credential">Ouvrir l’invite biométrique pour stocker les identifiants</string>
|
||||
<string name="no_credentials_stored">Cette base de données n’a pas encore stocké d’identifiants.</string>
|
||||
<string name="menu_appearance_settings">Apparence</string>
|
||||
<string name="general">Général</string>
|
||||
@@ -193,7 +187,6 @@
|
||||
<string name="biometric_unlock_enable_summary">Permet de numériser votre empreinte biométrique pour ouvrir la base de données</string>
|
||||
<string name="biometric_delete_all_key_title">Supprimer les clés de chiffrement</string>
|
||||
<string name="biometric_delete_all_key_summary">Supprime toutes les clés de chiffrement liées à la reconnaissance biométrique</string>
|
||||
<string name="biometric_delete_all_key_warning">Supprimer toutes les clés de chiffrement liées à la reconnaissance biométrique \?</string>
|
||||
<string name="unavailable_feature_text">Impossible de démarrer cette fonctionnalité.</string>
|
||||
<string name="unavailable_feature_version">L’appareil tourne sous Android %1$s, mais la version %2$s ou supérieure est requise.</string>
|
||||
<string name="unavailable_feature_hardware">Impossible de trouver le matériel correspondant.</string>
|
||||
@@ -212,7 +205,7 @@
|
||||
<string name="database_description_title">Description de la base de données</string>
|
||||
<string name="database_version_title">Version de la base de données</string>
|
||||
<string name="text_appearance">Texte</string>
|
||||
<string name="application_appearance">Application</string>
|
||||
<string name="application_appearance">Interface</string>
|
||||
<string name="other">Autres</string>
|
||||
<string name="keyboard">Clavier</string>
|
||||
<string name="magic_keyboard_title">Magiclavier</string>
|
||||
@@ -220,7 +213,7 @@
|
||||
<string name="enable_education_screens_title">Conseils pédagogiques</string>
|
||||
<string name="enable_education_screens_summary">Met en surbrillance les éléments pour apprendre le fonctionnement de l’application</string>
|
||||
<string name="reset_education_screens_title">Réinitialiser les conseils pédagogiques</string>
|
||||
<string name="reset_education_screens_summary">Afficher à nouveau toutes les informations pédagogiques</string>
|
||||
<string name="reset_education_screens_summary">Affiche à nouveau toutes les informations pédagogiques</string>
|
||||
<string name="reset_education_screens_text">Conseils pédagogiques réinitialisés</string>
|
||||
<string name="education_create_database_title">Créer votre fichier de base de données</string>
|
||||
<string name="education_create_database_summary">Crée votre premier fichier de gestion de mots de passe.</string>
|
||||
@@ -232,8 +225,6 @@
|
||||
\nLes groupes (≈dossiers) organisent les entrées dans votre base de données.</string>
|
||||
<string name="education_search_title">Rechercher dans les entrées</string>
|
||||
<string name="education_search_summary">Saisir le titre, le nom d’utilisateur ou le contenu des autres champs pour récupérer vos mots de passe.</string>
|
||||
<string name="education_biometric_title">Déverrouillage biométrique de la base de données</string>
|
||||
<string name="education_biometric_summary">Associe votre mot de passe à votre empreinte biométrique numérisée pour déverrouiller rapidement votre base de données.</string>
|
||||
<string name="education_entry_edit_title">Modifier l’entrée</string>
|
||||
<string name="education_entry_edit_summary">Modifie votre entrée avec des champs personnalisés. La collection des données peut être référencée entre différents champs de l’entrée.</string>
|
||||
<string name="education_generate_password_title">Créer un mot de passe fort</string>
|
||||
@@ -271,7 +262,6 @@
|
||||
<string name="encryption_twofish">Twofish</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string-array name="timeout_options">
|
||||
<item>5 secondes</item>
|
||||
<item>10 secondes</item>
|
||||
@@ -306,7 +296,7 @@
|
||||
<string name="menu_move">Déplacer</string>
|
||||
<string name="menu_paste">Coller</string>
|
||||
<string name="menu_cancel">Annuler</string>
|
||||
<string name="allow_no_password_title">Autoriser l\'absence de clé principale</string>
|
||||
<string name="allow_no_password_title">Autoriser l’absence de clé principale</string>
|
||||
<string name="allow_no_password_summary">Autorise l’appui du bouton « Ouvrir » si aucun identifiant n’est sélectionné</string>
|
||||
<string name="menu_file_selection_read_only">Protéger en écriture</string>
|
||||
<string name="menu_open_file_read_and_write">Modifiable</string>
|
||||
@@ -316,12 +306,12 @@
|
||||
<string name="education_read_only_summary">Changez le mode d’ouverture pour la session.
|
||||
\n
|
||||
\n« Protégé en écriture » empêche les modifications involontaires de la base de données.
|
||||
\n« Modifiable » vous permet d’ajouter, de supprimer ou de modifier tous les éléments.</string>
|
||||
\n« Modifiable » vous permet d’ajouter, de supprimer ou de modifier tous les éléments comme vous le souhaitez.</string>
|
||||
<string name="edit_entry">Modifier l’entrée</string>
|
||||
<string name="error_load_database">Impossible de charger votre base de données.</string>
|
||||
<string name="error_load_database_KDF_memory">Impossible de charger la clé. Veuillez essayer de diminuer l’utilisation mémoire de la fonction de dérivation de clé.</string>
|
||||
<string name="list_entries_show_username_title">Afficher les noms d’utilisateur</string>
|
||||
<string name="list_entries_show_username_summary">Afficher les noms d’utilisateur dans les listes d’entrées</string>
|
||||
<string name="list_entries_show_username_summary">Affiche les noms d’utilisateur dans les listes d’entrées</string>
|
||||
<string name="build_label">Compilation %1$s</string>
|
||||
<string name="keyboard_name">Magiclavier</string>
|
||||
<string name="keyboard_label">Magiclavier (KeePassDX)</string>
|
||||
@@ -343,9 +333,9 @@
|
||||
<string name="keyboard_key_sound_title">Appui clavier audible</string>
|
||||
<string name="keyboard_change">Changement de clavier</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Écran des identifications de la base de données</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Revenir automatiquement au clavier précédent sur l\'écran des identifications de la base de données</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Revenir automatiquement au clavier précédent sur l’écran des identifications de la base de données</string>
|
||||
<string name="keyboard_previous_fill_in_title">Action de touche automatique</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Revenir automatiquement au clavier précédent après avoir exécuté "Action de touche automatique"</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Revenir automatiquement au clavier précédent après avoir exécuté « Action de touche automatique »</string>
|
||||
<string name="selection_mode">Mode sélection</string>
|
||||
<string name="do_not_kill_app">Veuillez ne pas tuer l’application…</string>
|
||||
<string name="lock_database_back_root_title">Appuyer sur « Retour » pour verrouiller</string>
|
||||
@@ -368,7 +358,7 @@
|
||||
<string name="content_description_remove_field">Supprimer un champ</string>
|
||||
<string name="entry_UUID">UUID</string>
|
||||
<string name="list_groups_show_number_entries_title">Afficher le nombre d’entrées</string>
|
||||
<string name="list_groups_show_number_entries_summary">Afficher le nombre d’entrées dans un groupe</string>
|
||||
<string name="list_groups_show_number_entries_summary">Affiche le nombre d’entrées dans un groupe</string>
|
||||
<string name="content_description_node_children">Enfants du nœud</string>
|
||||
<string name="content_description_file_information">Informations du fichier</string>
|
||||
<string name="content_description_password_checkbox">Case à cocher du mot de passe</string>
|
||||
@@ -383,18 +373,14 @@
|
||||
<string name="menu_advanced_unlock_settings">Déverrouillage avancé</string>
|
||||
<string name="enable">Activer</string>
|
||||
<string name="disable">Désactiver</string>
|
||||
<string name="biometric_prompt_store_credential_title">Enregistrer la reconnaissance biométrique</string>
|
||||
<string name="biometric_prompt_store_credential_message">Attention : Vous devez toujours vous souvenir de votre mot de passe principal si vous utilisez la reconnaissance biométrique.</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Ouvrir la base de données avec la reconnaissance biométrique</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Extraire les identifiants de la base de données avec les données biométriques</string>
|
||||
<string name="biometric">Biométrie</string>
|
||||
<string name="biometric_auto_open_prompt_title">Ouvrir automatiquement l’invite biométrique</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Demander automatiquement la reconnaissance biométrique si la base de données est configurée pour l\'utiliser</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Demande automatiquement la reconnaissance biométrique si la base de données est configurée pour l’utiliser</string>
|
||||
<string name="master_key">Clé principale</string>
|
||||
<string name="security">Sécurité</string>
|
||||
<string name="entry_history">Historique</string>
|
||||
<string name="entry_setup_otp">Configuration d’un mot de passe à usage unique</string>
|
||||
<string name="otp_type">Type OTP</string>
|
||||
<string name="otp_type">Type MDP à usage unique</string>
|
||||
<string name="otp_secret">Secret</string>
|
||||
<string name="otp_period">Période (secondes)</string>
|
||||
<string name="otp_counter">Compteur</string>
|
||||
@@ -416,11 +402,11 @@
|
||||
<string name="contains_duplicate_uuid_procedure">Résoudre le problème en générant de nouveaux UUID pour les doublons et continuer \?</string>
|
||||
<string name="database_opened">Base de données ouverte</string>
|
||||
<string name="clipboard_explanation_summary">Copier les champs d’une entrée à l’aide du presse-papier de votre appareil</string>
|
||||
<string name="advanced_unlock_explanation_summary">Utilise le déverrouillage avancé pour ouvrir plus facilement une base de données</string>
|
||||
<string name="advanced_unlock_explanation_summary">Utiliser le déverrouillage avancé pour ouvrir plus facilement une base de données</string>
|
||||
<string name="database_data_compression_title">Compression de données</string>
|
||||
<string name="database_data_compression_summary">La compression des données réduit la taille de la base de données</string>
|
||||
<string name="max_history_items_title">Nombre maximum</string>
|
||||
<string name="max_history_items_summary">Limite le nombre d\'éléments de l’historique par entrée</string>
|
||||
<string name="max_history_items_summary">Limite le nombre d’éléments de l’historique par entrée</string>
|
||||
<string name="max_history_size_title">Taille maximum</string>
|
||||
<string name="max_history_size_summary">Limite la taille de l’historique (en octets) par entrée</string>
|
||||
<string name="settings_database_recommend_changing_master_key_title">Recommander le renouvellement</string>
|
||||
@@ -441,14 +427,13 @@
|
||||
<string name="command_execution">Exécution de la commande…</string>
|
||||
<string name="warning_permanently_delete_nodes">Supprimer définitivement les nœuds sélectionnés \?</string>
|
||||
<string name="keystore_not_accessible">Le magasin de clés n’est pas correctement initialisé.</string>
|
||||
<string name="credential_before_click_biometric_button">Saisissez le mot de passe puis cliquez sur le bouton biométrique.</string>
|
||||
<string name="recycle_bin_group_title">Groupe de la corbeille</string>
|
||||
<string name="enable_auto_save_database_title">Enregistrement automatique de la base de données</string>
|
||||
<string name="enable_auto_save_database_summary">Enregistre la base de données après chaque action importante (en mode « Modifiable »)</string>
|
||||
<string name="entry_attachments">Attachements</string>
|
||||
<string name="menu_restore_entry_history">Restaurer l\'historique</string>
|
||||
<string name="menu_delete_entry_history">Effacer l\'historique</string>
|
||||
<string name="keyboard_auto_go_action_title">Action de touche auto</string>
|
||||
<string name="menu_restore_entry_history">Restaurer l’historique</string>
|
||||
<string name="menu_delete_entry_history">Effacer l’historique</string>
|
||||
<string name="keyboard_auto_go_action_title">Action de touche automatique</string>
|
||||
<string name="keyboard_auto_go_action_summary">Action de la touche « Go » après avoir appuyé sur une touche « Champ »</string>
|
||||
<string name="download_attachment">Téléchargement %1$s</string>
|
||||
<string name="download_initialization">Initialisation…</string>
|
||||
@@ -461,15 +446,15 @@
|
||||
<string name="contribution">Contribution</string>
|
||||
<string name="html_about_contribution">Afin de <strong>garder notre liberté</strong>, <strong>corriger les bugs</strong>, <strong>ajouter des fonctionnalités</strong> et <strong>être toujours actif</strong>, nous comptons sur votre <strong>contribution</strong>.</string>
|
||||
<string name="auto_focus_search_title">Recherche rapide</string>
|
||||
<string name="auto_focus_search_summary">Demander une recherche lors de l\'ouverture d\'une base de données</string>
|
||||
<string name="auto_focus_search_summary">Demande une recherche lors de l’ouverture d’une base de données</string>
|
||||
<string name="remember_database_locations_title">Mémoriser l’emplacement des bases de données</string>
|
||||
<string name="remember_database_locations_summary">Garde en mémoire l’emplacement où les bases de données sont stockées</string>
|
||||
<string name="remember_keyfile_locations_title">Mémoriser les emplacements des fichiers clé</string>
|
||||
<string name="remember_keyfile_locations_summary">Garde en mémoire l’emplacement où les fichiers clé sont stockés</string>
|
||||
<string name="show_recent_files_title">Afficher les fichiers récents</string>
|
||||
<string name="show_recent_files_summary">Afficher les emplacements des bases de données récentes</string>
|
||||
<string name="show_recent_files_summary">Affiche les emplacements des bases de données récentes</string>
|
||||
<string name="hide_broken_locations_title">Masquer les liens rompus de base de données</string>
|
||||
<string name="hide_broken_locations_summary">Masquer les liens rompus dans la liste des bases de données récentes</string>
|
||||
<string name="hide_broken_locations_summary">Masque les liens rompus dans la liste des bases de données récentes</string>
|
||||
<string name="warning_database_read_only">Accorder un accès en écriture au fichier pour enregistrer les modifications de la base de données</string>
|
||||
<string name="education_setup_OTP_summary">Définit le mot de passe à usage unique (temporel ou évènementiel) pour générer un jeton demandé par l’authentification à deux facteurs (A2F).</string>
|
||||
<string name="education_setup_OTP_title">Définir l’OTP</string>
|
||||
@@ -478,25 +463,25 @@
|
||||
<string name="discard">Abandonner</string>
|
||||
<string name="discard_changes">Abandonner les modifications \?</string>
|
||||
<string name="validate">Valider</string>
|
||||
<string name="autofill_auto_search_summary">Suggérer automatiquement des résultats de recherche à partir du domaine Web ou de l\'application ID</string>
|
||||
<string name="autofill_auto_search_summary">Suggérer automatiquement des résultats de recherche à partir du domaine Web ou de l’identifiant de l’application</string>
|
||||
<string name="autofill_auto_search_title">Recherche automatique</string>
|
||||
<string name="lock_database_show_button_summary">Affiche le bouton de verrouillage dans l\'interface utilisateur</string>
|
||||
<string name="lock_database_show_button_summary">Affiche le bouton de verrouillage dans l’interface utilisateur</string>
|
||||
<string name="lock_database_show_button_title">Afficher le bouton de verrouillage</string>
|
||||
<string name="autofill_preference_title">Paramètres de remplissage automatique</string>
|
||||
<string name="warning_database_link_revoked">Accès au fichier révoqué par le gestionnaire de fichiers</string>
|
||||
<string name="error_label_exists">Ce label existe déjà.</string>
|
||||
<string name="autofill_block_restart">Redémarrez l\'application contenant le formulaire pour activer le blocage.</string>
|
||||
<string name="autofill_block_restart">Redémarrez l’application contenant le formulaire pour activer le blocage.</string>
|
||||
<string name="autofill_block">Blocker le remplissage automatique</string>
|
||||
<string name="autofill_web_domain_blocklist_summary">Liste de blocage qui empêche le remplissage automatique des domaines Web</string>
|
||||
<string name="autofill_web_domain_blocklist_title">Liste de blocage de domaine Web</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Liste de blocage qui empêche le remplissage automatique des applications</string>
|
||||
<string name="autofill_application_id_blocklist_title">Liste de blocage d\'application</string>
|
||||
<string name="keyboard_search_share_summary">Rechercher automatiquement les informations partagées pour remplir le clavier</string>
|
||||
<string name="autofill_application_id_blocklist_title">Liste de blocage d’application</string>
|
||||
<string name="keyboard_search_share_summary">Recherche automatiquement les informations partagées pour remplir le clavier</string>
|
||||
<string name="keyboard_search_share_title">Rechercher les informations partagées</string>
|
||||
<string name="filter">Filtre</string>
|
||||
<string name="subdomain_search_summary">Rechercher des domaines Web avec des contraintes de sous-domaines</string>
|
||||
<string name="subdomain_search_summary">Recherche des domaines Web avec des contraintes de sous-domaines</string>
|
||||
<string name="subdomain_search_title">Recherche de sous-domaine</string>
|
||||
<string name="error_string_type">Ce texte ne correspond pas à l\'élément demandé.</string>
|
||||
<string name="error_string_type">Ce texte ne correspond pas à l’élément demandé.</string>
|
||||
<string name="content_description_add_item">Ajouter un élément</string>
|
||||
<string name="upload_attachment">Téléverser %1$s</string>
|
||||
<string name="education_add_attachment_summary">Téléverse une pièce-jointe à votre entrée pour enregistrer d’importantes données externes.</string>
|
||||
@@ -509,9 +494,60 @@
|
||||
<string name="content_description_credentials_information">Informations des identifiants</string>
|
||||
<string name="warning_remove_unlinked_attachment">Supprimer les données non-liées peut réduire la taille de votre base de données mais peut également supprimer des données utilisées par des extensions KeePass.</string>
|
||||
<string name="warning_sure_remove_data">Supprimer ces données quand même ?</string>
|
||||
<string name="warning_empty_keyfile">Il n\'est pas recommandé d\'ajouter un fichier clé vide.</string>
|
||||
<string name="warning_empty_keyfile">Il n’est pas recommandé d’ajouter un fichier clé vide.</string>
|
||||
<string name="warning_empty_keyfile_explanation">Le contenu du fichier clé ne devrait jamais changer, et dans le meilleur des cas, devrait contenir des données générées aléatoirement.</string>
|
||||
<string name="database_data_remove_unlinked_attachments_title">Supprimer les données non-liées</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Supprimer les pièces jointes contenues dans la base de données mais non-liées à une entrée</string>
|
||||
<string name="data">Données</string>
|
||||
<string name="show_uuid_summary">Affiche l’UUID lié à une entrée</string>
|
||||
<string name="show_uuid_title">Afficher l’UUID</string>
|
||||
<string name="autofill_read_only_save">L’enregistrement des données n’est pas autorisé pour une base de données ouverte en lecture seule.</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Demande à enregistrer des données quand un formulaire est validé</string>
|
||||
<string name="autofill_ask_to_save_data_title">Demander à enregistrer des données</string>
|
||||
<string name="autofill_save_search_info_summary">Essayer d’enregistrer les informations de recherche lors de la sélection manuelle d’une entrée</string>
|
||||
<string name="autofill_save_search_info_title">Enregistrer les infos de recherche</string>
|
||||
<string name="autofill_close_database_summary">Ferme la base de données après une sélection de remplissage automatique</string>
|
||||
<string name="autofill_close_database_title">Fermer la base de données</string>
|
||||
<string name="keyboard_previous_lock_summary">Revient automatiquement au clavier précédent après le verrouillage de la base de données</string>
|
||||
<string name="keyboard_previous_lock_title">Verrouiller la base de données</string>
|
||||
<string name="keyboard_save_search_info_summary">Essayer d’enregistrer les informations partagées lors de la sélection manuelle d’une entrée</string>
|
||||
<string name="keyboard_save_search_info_title">Enregistrer les infos partagées</string>
|
||||
<string name="notification">Notification</string>
|
||||
<string name="biometric_security_update_required">Mise à jour de sécurité biométrique requise.</string>
|
||||
<string name="warning_empty_recycle_bin">Supprimer définitivement tous les nœuds de la corbeille \?</string>
|
||||
<string name="registration_mode">Mode enregistrement</string>
|
||||
<string name="save_mode">Mode sauvegarde</string>
|
||||
<string name="search_mode">Mode recherche</string>
|
||||
<string name="error_registration_read_only">L’enregistrement d’un nouvel élément n’est pas autorisé dans une base de données en lecture seule</string>
|
||||
<string name="error_field_name_already_exists">Le nom du champ existe déjà.</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Supprimer toutes les clés de chiffrement liées à la reconnaissance de déverrouillage avancée \?</string>
|
||||
<string name="device_credential_unlock_enable_summary">Vous permet d\'utiliser les informations d\'identification de votre appareil pour ouvrir la base de données</string>
|
||||
<string name="device_credential_unlock_enable_title">Déverrouillage par identifiants de l\'appareil</string>
|
||||
<string name="device_credential">Déverouillage de l\'appareil</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Tapez le mot de passe, puis cliquez sur le bouton \"Déverrouillage avancé\".</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Impossible d\'initialiser l\'invite de déverrouillage avancé.</string>
|
||||
<string name="advanced_unlock_scanning_error">Erreur de déverrouillage avancé : %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Impossible de reconnaître l\'empreinte de déverrouillage avancé</string>
|
||||
<string name="advanced_unlock_invalid_key">Impossible de lire la clé de déverrouillage avancé. Veuillez la supprimer et répéter la procédure de reconnaissance de déverrouillage.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extraire les identifiants de la base de données avec des données de déverrouillage avancées</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Ouvrir la base de données avec la reconnaissance de déverrouillage avancée</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Attention : Vous devez toujours vous souvenir de votre mot de passe principal si vous utilisez la reconnaissance de déverrouillage avancée.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Reconnaissance de déverrouillage avancée</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Ouvrez l\'invite de déverrouillage avancé pour stocker les informations d\'identification</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Ouvrez l\'invite de déverrouillage avancé pour déverrouiller la base de données</string>
|
||||
<string name="menu_keystore_remove_key">Supprimer la clé de déverrouillage avancé</string>
|
||||
<string name="enter">Entrer</string>
|
||||
<string name="backspace">Retour arrière</string>
|
||||
<string name="select_entry">Sélection d\'une entrée</string>
|
||||
<string name="back_to_previous_keyboard">Retour au clavier précédent</string>
|
||||
<string name="custom_fields">Champs customisés</string>
|
||||
<string name="education_advanced_unlock_summary">Lier votre mot de passe à vos informations d\'identification biométriques ou de périphérique scannées pour déverrouiller rapidement votre base de données.</string>
|
||||
<string name="education_advanced_unlock_title">Déverrouillage avancé de la base de données</string>
|
||||
<string name="advanced_unlock_timeout">Délai du déverrouillage avancé</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Durée d\'utilisation du déverrouillage avancé avant de supprimer son contenu</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Expiration du déverrouillage avancé</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Ne stocker aucun contenu crypté pour utiliser le déverrouillage avancé</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Déverrouillage avancé temporaire</string>
|
||||
<string name="advanced_unlock_tap_delete">Appuyez pour supprimer les clés de déverrouillage avancées</string>
|
||||
<string name="content">Contenu</string>
|
||||
</resources>
|
||||
@@ -27,7 +27,7 @@
|
||||
<string name="encryption_algorithm">Algoritmo de cifrado</string>
|
||||
<string name="key_derivation_function">Función de derivación de chave</string>
|
||||
<string name="app_timeout">Tempo de espera da aplicación</string>
|
||||
<string name="app_timeout_summary">Tempo antes de bloquear a base de datos cando a aplicación está inactiva.</string>
|
||||
<string name="app_timeout_summary">Tiempo de inactividad antes de bloquear la base de datos</string>
|
||||
<string name="application">Aplicación</string>
|
||||
<string name="brackets">Parénteses</string>
|
||||
<string name="extended_ASCII">ASCII extendido</string>
|
||||
|
||||
@@ -133,7 +133,6 @@
|
||||
<string name="menu_open">Otvori</string>
|
||||
<string name="menu_search">Traži</string>
|
||||
<string name="menu_showpass">Prikaži lozinku</string>
|
||||
<string name="menu_biometric_remove_key">Izbriši spremljene biometrijske ključeve</string>
|
||||
<string name="menu_url">Idi na URL</string>
|
||||
<string name="menu_open_file_read_and_write">Promjenjivo</string>
|
||||
<string name="menu_empty_recycle_bin">Isprazni koš za smeće</string>
|
||||
@@ -149,7 +148,7 @@
|
||||
<string name="progress_create">Stvaranje nove baze podataka …</string>
|
||||
<string name="protection">Zaštita</string>
|
||||
<string name="contains_duplicate_uuid">Baza podataka sadrži duplicirane UUID-ove.</string>
|
||||
<string name="selection_mode">Mod odabira</string>
|
||||
<string name="selection_mode">Modus odabira</string>
|
||||
<string name="encryption_explanation">Algoritam šifriranja baze podataka koji se koristi za sve podatke.</string>
|
||||
<string name="memory_usage">Korištenje memorije</string>
|
||||
<string name="parallelism">Paralelnost</string>
|
||||
@@ -176,15 +175,12 @@
|
||||
<string name="warning_permanently_delete_nodes">Trajno izbrisati odabrane čvorove\?</string>
|
||||
<string name="version_label">Verzija %1$s</string>
|
||||
<string name="build_label">Izgradnja %1$s</string>
|
||||
<string name="biometric_prompt_store_credential_message">Upozorenje: Ako koristiš biometrijsko prepoznavanje i dalje moraš zapamtiti svoju glavnu lozinku.</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Otvori bazu podataka pomoću biometrijskog prepoznavanja</string>
|
||||
<string name="encrypted_value_stored">Šifrirana lozinka pohranjena</string>
|
||||
<string name="biometric_invalid_key">Nije moguće pročitati biometrijski ključ. Izbriši ga i ponovi postupak prepoznavanja.</string>
|
||||
<string name="encrypted_value_stored">Šifrirana lozinka spremljena</string>
|
||||
<string name="database_history">Povijest</string>
|
||||
<string name="menu_appearance_settings">Izgled</string>
|
||||
<string name="general">Opće</string>
|
||||
<string name="autofill">Automatsko ispunjavanje</string>
|
||||
<string name="autofill_service_name">Automatsko ispunjavanje obrazaca KeepassDX</string>
|
||||
<string name="autofill_service_name">Automatsko ispunjavanje obrazaca KeePassDX</string>
|
||||
<string name="set_autofill_service_title">Postavi standardnu uslugu automatskog ispunjavanja</string>
|
||||
<string name="list_password_generator_options_title">Znakovi lozinke</string>
|
||||
<string name="list_password_generator_options_summary">Postavi dozvoljene znakove za generiranje lozinke</string>
|
||||
@@ -199,8 +195,7 @@
|
||||
<string name="biometric_unlock_enable_title">Biometrijsko otključavanje</string>
|
||||
<string name="biometric_unlock_enable_summary">Otvaranje baze podataka skeniranjem biometrike</string>
|
||||
<string name="biometric_delete_all_key_title">Izbriši ključeve šifriranja</string>
|
||||
<string name="biometric_delete_all_key_summary">Izbriši sve ključeve šifriranja povezane s biometrijskim prepoznavanjem</string>
|
||||
<string name="biometric_delete_all_key_warning">Izbrisati sve ključeve povezane s biometrijskim prepoznavanjem\?</string>
|
||||
<string name="biometric_delete_all_key_summary">Izbriši sve ključeve šifriranja povezane s naprednim prepoznavanjem otključavanja</string>
|
||||
<string name="unavailable_feature_hardware">Nije moguće pronaći odgovarajući hardver.</string>
|
||||
<string name="file_name">Ime datoteke</string>
|
||||
<string name="path">Putanja</string>
|
||||
@@ -227,7 +222,7 @@
|
||||
<string name="database_custom_color_title">Proizvoljna boja baze podataka</string>
|
||||
<string name="database_version_title">Verzija baze podataka</string>
|
||||
<string name="text_appearance">Tekst</string>
|
||||
<string name="application_appearance">Aplikacija</string>
|
||||
<string name="application_appearance">Sučelje</string>
|
||||
<string name="other">Ostalo</string>
|
||||
<string name="compression">Komprimiranje</string>
|
||||
<string name="compression_none">Bez</string>
|
||||
@@ -244,7 +239,7 @@
|
||||
<string name="error_out_of_memory">Nema dovoljno memorije za učitavanje cijele baze podataka.</string>
|
||||
<string name="error_load_database">Nije moguće učitati bazu podataka.</string>
|
||||
<string name="error_load_database_KDF_memory">Nije moguće učitati ključ. Pokušaje smanjiti uporabu memorije KDF.</string>
|
||||
<string name="error_disallow_no_credentials">Barem jedna akreditacija mora biti postavljena.</string>
|
||||
<string name="error_disallow_no_credentials">Barem jedan skup podataka za prijavu mora biti postavljen.</string>
|
||||
<string name="error_string_key">Svaki niz mora imati ime polja.</string>
|
||||
<string name="error_autofill_enable_service">Nije moguće aktivirati uslugu automatskog ispunjavanja.</string>
|
||||
<string name="error_move_folder_in_itself">Nije moguće premjestiti grupu u samu sebe.</string>
|
||||
@@ -255,7 +250,7 @@
|
||||
<string name="error_save_database">Nije moguće spremiti bazu podataka.</string>
|
||||
<string name="error_otp_period">Razdoblje mora biti između %1$d i %2$d sekundi.</string>
|
||||
<string name="file_not_found_content">Nije moguće pronaći datoteku. Probaj je ponovo otvoriti iz upravitelja datoteka.</string>
|
||||
<string name="invalid_credentials">Nije moguće čitati akreditacije.</string>
|
||||
<string name="invalid_credentials">Nije moguće čitati podatke za prijavu.</string>
|
||||
<string name="list_size_title">Veličina elemenata popisa</string>
|
||||
<string name="list_size_summary">Veličina teksta u popisu elemenata</string>
|
||||
<string name="lowercase">Mala slova</string>
|
||||
@@ -277,11 +272,7 @@
|
||||
<string name="special">Posebni znakovi</string>
|
||||
<string name="underline">Podcrtaj</string>
|
||||
<string name="uppercase">Velika slova</string>
|
||||
<string name="biometric_prompt_store_credential_title">Spremi biometrijsko prepoznavanje</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Izvadi akreditaciju baze podataka s biometrijskim podacima</string>
|
||||
<string name="biometric_not_recognized">Nije moguće prepoznati biometriju</string>
|
||||
<string name="no_credentials_stored">Ova baza podataka još nema spremljenu akreditaciju.</string>
|
||||
<string name="credential_before_click_biometric_button">Upiši lozinku, zatim klikne gumb „Biometrija”.</string>
|
||||
<string name="no_credentials_stored">Ova baza podataka još nema spremljene podatke za prijavu.</string>
|
||||
<string name="biometric">Biometrija</string>
|
||||
<string name="autofill_sign_in_prompt">Prijavi se s KeePassDX</string>
|
||||
<string name="autofill_explanation_summary">Aktiviraj automatsko ispunjavanje za brzo ispunjavanje obrazaca u drugim aplikacijama</string>
|
||||
@@ -321,7 +312,7 @@
|
||||
<string name="keyboard_key_vibrate_title">Vibracija tipki</string>
|
||||
<string name="keyboard_key_sound_title">Zvuk tipki</string>
|
||||
<string name="allow_no_password_title">Dozvoli bez lozinke</string>
|
||||
<string name="allow_no_password_summary">Dozvoljava dodir gumba „Otvori”, ako nijedna akreditacija nije odabrana</string>
|
||||
<string name="allow_no_password_summary">Dozvoljava dodir gumba „Otvori”, ako nisu odabrani nikoji podaci za prijavu</string>
|
||||
<string name="delete_entered_password_title">Izbriši lozinku</string>
|
||||
<string name="delete_entered_password_summary">Briše upisanu lozinku nakon pokušaja povezivanja s bazom podataka</string>
|
||||
<string name="enable_read_only_title">Zaštićeno od pisanja</string>
|
||||
@@ -375,10 +366,8 @@
|
||||
<string name="keyboard_entry_timeout_summary">Istek vremena za brisanje unosa tipkovnicom</string>
|
||||
<string name="education_read_only_title">Zaštiti bazu podataka od pisanja</string>
|
||||
<string name="autofill_web_domain_blocklist_title">Popis blokiranja web domena</string>
|
||||
<string name="education_biometric_title">Biometrijsko otključavanje baze podataka</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="contribution">Doprinos</string>
|
||||
<string name="open_biometric_prompt_store_credential">Za spremanje akreditacija, otvori biometrijsku prijavu</string>
|
||||
<string name="error_label_exists">Ova oznaka već postoji.</string>
|
||||
<string name="warning_database_read_only">Za spremanje promjena u bazi podataka, datoteci dozvoli pisanje</string>
|
||||
<string name="app_timeout">Istek vremena aplikacije</string>
|
||||
@@ -387,7 +376,7 @@
|
||||
<string name="rounds_explanation">Dodatni prolazi šifriranja pružaju veću zaštitu od brutalnih napada, ali stvarno mogu usporiti učitavanje i spremanje.</string>
|
||||
<string name="subdomain_search_summary">Traži web domene s ograničenjima poddomena</string>
|
||||
<string name="keyboard_search_share_title">Traži dijeljene informacije</string>
|
||||
<string name="html_text_dev_feature_upgrade">Ne zaboravi aktualizirati aplikaciju najnovijim verzijama.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Redovito aktualiziraj aplikaciju instaliranjem najnovijih verzija.</string>
|
||||
<string name="autofill_block_restart">Za aktiviranje blokiranja, ponovo pokreni aplikaciju koja sadrži obrazac.</string>
|
||||
<string name="education_sort_summary">Odaberi način razvrstavanja unosa i grupa.</string>
|
||||
<string name="warning_database_link_revoked">Pristup datoteci koju je opozvao upravitelj datoteka</string>
|
||||
@@ -405,24 +394,22 @@
|
||||
<string name="html_text_dev_feature_contibute"><strong>Doprinosom</strong>,</string>
|
||||
<string name="education_entry_new_field_title">Dodaj prilagođena polja</string>
|
||||
<string name="education_lock_summary">Zaključaj bazu podataka brzo, aplikaciju možeš postaviti tako da bazu nakon nekog vremena zaključa i kad se ekran isključi.</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="encryption_twofish">Twofish</string>
|
||||
<string name="show_recent_files_summary">Prikaži mjesto nedavnih baza podataka</string>
|
||||
<string name="education_biometric_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.</string>
|
||||
<string name="education_advanced_unlock_summary">Za brzo otključavanje baze podataka, poveži lozinku sa skeniranom biometrijom.</string>
|
||||
<string name="html_text_donation">Kako bismo zadržali našu slobodu i uvijek bili aktivni, računamo na tvoj<strong>doprinos.</strong></string>
|
||||
<string name="kdf_explanation">Za stvaranje ključa za algoritam šifriranja, glavni ključ se transformira pomoću funkcije za generiranje ključeva koja sadrži slučajnu komponentu.</string>
|
||||
<string name="lock_database_back_root_summary">Zaključaj bazu podataka kad korisnik pritisne gumb za natrag na ekranu</string>
|
||||
<string name="lock_database_back_root_summary">Zaključaj bazu podataka kad korisnik pritisne gumb za natrag na glavnom ekranu</string>
|
||||
<string name="hide_broken_locations_title">Sakrij pokvarene poveznice baze podataka</string>
|
||||
<string name="autofill_block">Blokiranje automatskog ispunjavanja</string>
|
||||
<string name="keystore_not_accessible">Baza ključeva nije ispravno inicijalizirana.</string>
|
||||
<string name="icon_pack_choose_summary">Paket ikona, koji se koristi u aplikaciji</string>
|
||||
<string name="hide_expired_entries_summary">Istekli unosi se ne pokazuju</string>
|
||||
<string name="education_lock_title">Zaključaj bazu podataka</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Za otključavanje baze podataka, otvori biometrijsku prijavu</string>
|
||||
<string name="education_unlock_title">Otključaj bazu podataka</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatski traži biometriju, ako je baza podataka tako postavljena</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatski zatraži napredno otključavanje ako je baza podataka tako postavljena</string>
|
||||
<string name="unavailable_feature_text">Nije moguće pokrenuti ovu funkciju.</string>
|
||||
<string name="biometric_auto_open_prompt_title">Automatski otvori biometrijsku prijavu</string>
|
||||
<string name="biometric_auto_open_prompt_title">Automatski otvori prozor za prijavu</string>
|
||||
<string name="clipboard_timeout">Istek vremena međuspremnika</string>
|
||||
<string name="education_search_title">Pretraži unose</string>
|
||||
<string name="education_field_copy_title">Kopiraj jedno polje</string>
|
||||
@@ -460,17 +447,16 @@
|
||||
<string name="html_text_ad_free">Za razliku od mnogih aplikacija za upravljanje lozinkama, ova je <strong>bez oglasa</strong>, <strong>copylefted slobodan softver</strong> i ne prikuplja osobne podatke na svojim poslužiteljima, bez obzira na korištenu verziju.</string>
|
||||
<string name="rounds">Transformacijski prolazi</string>
|
||||
<string name="download_initialization">Inicijaliziranje …</string>
|
||||
<string name="biometric_scanning_error">Biometrijska greška: %1$s</string>
|
||||
<string name="education_donation_title">Sudjeluj</string>
|
||||
<string name="education_new_node_summary">Unosi pomažu u upravljanju digitalnim identitetom.
|
||||
\n
|
||||
\nGrupe (~mape) organiziraju unose u bazi podataka.</string>
|
||||
<string name="download_progression">U tijeku: %1$d%%</string>
|
||||
<string name="download_complete">Gotovo!</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja automatske radnje tipke</string>
|
||||
<string name="keyboard_previous_fill_in_summary">Automatski se vrati na prethodnu tipkovnicu nakon izvršavanja „Automatska radnje tipke”</string>
|
||||
<string name="keyboard_previous_fill_in_title">Automatska radnja tipke</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu u bazu podataka</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Ekran za unos podataka za prijavu u bazu podataka</string>
|
||||
<string name="keyboard_previous_database_credentials_summary">Automatski se prebaci na prethodnu tipkovnicu pri ekranu za unos podataka za prijavu na bazu podataka</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Ekran za unos podataka za prijavu na bazu podataka</string>
|
||||
<string name="keyboard_change">Promijeni tipkovnicu</string>
|
||||
<string name="warning_file_too_big">Baza podataka za KeePass trebala bi sadržavati samo male datoteke uslužnih programa (poput PGP datoteke ključeva).
|
||||
\n
|
||||
@@ -487,5 +473,56 @@
|
||||
<string name="database_data_remove_unlinked_attachments_title">Ukloni nepovezane podatke</string>
|
||||
<string name="data">Podaci</string>
|
||||
<string name="warning_sure_remove_data">Svejedno ukloniti ove podatke\?</string>
|
||||
<string name="content_description_credentials_information">Podaci podataka prijave</string>
|
||||
<string name="content_description_credentials_information">Podaci za prijavu</string>
|
||||
<string name="autofill_save_search_info_summary">Pokušaj spremiti podatke pretrage prilikom odabira ručnog unosa</string>
|
||||
<string name="notification">Obavijest</string>
|
||||
<string name="error_registration_read_only">Nije dopušteno spremati novi element u zaštićenoj bazi podataka</string>
|
||||
<string name="autofill_read_only_save">Spremanje podataka nije dopušteno za bazu podataka koja je otvorena u zaštićenom stanju.</string>
|
||||
<string name="show_uuid_summary">Prikazuje UUID povezan s unosom</string>
|
||||
<string name="show_uuid_title">Prikaži UUID</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Zatraži spremanje podataka kad se obrazac provjeri</string>
|
||||
<string name="autofill_ask_to_save_data_title">Zatraži spremanje podataka</string>
|
||||
<string name="autofill_save_search_info_title">Spremi podatke pretrage</string>
|
||||
<string name="autofill_close_database_summary">Zatvori bazu podataka nakon odabira automatskog ispunjavanja</string>
|
||||
<string name="autofill_close_database_title">Zatvori bazu podataka</string>
|
||||
<string name="keyboard_previous_lock_summary">Automatski prebaci na prethodnu tipkovnicu nakon zaključavanja baze podataka</string>
|
||||
<string name="keyboard_previous_lock_title">Zaključaj bazu podataka</string>
|
||||
<string name="keyboard_save_search_info_summary">Pokušaj spremiti dijeljene informacije prilikom odabira ručnog unosa</string>
|
||||
<string name="keyboard_save_search_info_title">Spremi dijeljene informacije</string>
|
||||
<string name="warning_empty_recycle_bin">Trajno izbrisati sve čvorove iz smeća\?</string>
|
||||
<string name="biometric_security_update_required">Potrebno je aktualizirati biometrijsku zaštitu.</string>
|
||||
<string name="configure_biometric">Ne postoji biometrijski ključ niti podaci za prijavu uređaja.</string>
|
||||
<string name="registration_mode">Modus registracije</string>
|
||||
<string name="save_mode">Modus spremanja</string>
|
||||
<string name="search_mode">Modus pretrage</string>
|
||||
<string name="error_field_name_already_exists">Ime polja već postoji.</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Izbrisati sve ključeve šifriranja povezane s naprednim prepoznavanjem otključavanja\?</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Upiši lozinku i zatim pritisni gumb „Napredno otključavanje”.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Otvori bazu podataka pomoću naprednog prepoznavanja otključavanja</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Upozorenje: Ako koristiš prepoznavanje naprednog otključavanja morat ćeš i dalje znati glavnu lozinku.</string>
|
||||
<string name="menu_keystore_remove_key">Izbriši ključ naprednog otključavanja</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Napredno prepoznavanje otključavanja</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Nije moguće pokrenuti prozor naprednog otključavanja.</string>
|
||||
<string name="advanced_unlock_scanning_error">Greška naprednog otključavanja: %1$s</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Izdvoji podatake za prijavu na bazu podataka pomoću podataka naprednog otključavanja</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Otvori prozor naprednog otključavanja za spremanje podataka za prijavu</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Otvori prozor naprednog otključavanja za otključavanje baze podataka</string>
|
||||
<string name="advanced_unlock_not_recognized">Nije moguće prepoznati digitanlni otisak za napredno otključavanje</string>
|
||||
<string name="advanced_unlock_invalid_key">Nije moguće pročitati ključ naprednog otključavanja. Izbriši ga i ponovi postupak prepoznavanja.</string>
|
||||
<string name="enter">Tipka Enter</string>
|
||||
<string name="backspace">Tipka Backspace</string>
|
||||
<string name="select_entry">Odaberi unos</string>
|
||||
<string name="back_to_previous_keyboard">Natrag na prethodnu tipkovnicu</string>
|
||||
<string name="custom_fields">Prilagođena polja</string>
|
||||
<string name="device_credential_unlock_enable_summary">Omogućuje otvaranje baze podataka pomoću podataka za prijavu</string>
|
||||
<string name="device_credential_unlock_enable_title">Otključavanje s podacima za prijavu uređaja</string>
|
||||
<string name="device_credential">Podaci za prijavu uređaja</string>
|
||||
<string name="advanced_unlock_tap_delete">Dodirni za brisanje ključeva naprednog otključavanja</string>
|
||||
<string name="education_advanced_unlock_title">Napredno otključavanje baze podataka</string>
|
||||
<string name="advanced_unlock_timeout">Vremensko ograničenje neprednog otključavanja</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Trajanje korištenja naprednog otključavanja prije brisanja sadržaja</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Istek naprednog otključavanja</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Nemoj spremati šifrirani sadržaj za napredno otključavanje</string>
|
||||
<string name="content">Sadržaj</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Privremeno napredno otključavanje</string>
|
||||
</resources>
|
||||
@@ -35,7 +35,7 @@
|
||||
<string name="clipboard_error">Egyes eszközök nem engedik, hogy az alkalmazások használják a vágólapot.</string>
|
||||
<string name="clipboard_error_clear">A vágólap törlése sikertelen</string>
|
||||
<string name="clipboard_timeout">Vágólap időkorlátja</string>
|
||||
<string name="clipboard_timeout_summary">A vágólapon tárolás időtartama</string>
|
||||
<string name="clipboard_timeout_summary">A vágólapon tárolás időtartama (ha támogatja az eszköz)</string>
|
||||
<string name="select_to_copy">%1$s másolása a vágólapra</string>
|
||||
<string name="retrieving_db_key">Adatbázis létrehozása…</string>
|
||||
<string name="database">Adatbázis</string>
|
||||
@@ -137,11 +137,7 @@
|
||||
<string name="warning">Figyelmeztetés</string>
|
||||
<string name="warning_password_encoding">Kerülje a Latin-1 karakterkészlettől eltérő jelszókaraktereket az adatbázis-fájlban (a nem felismert karakterek mert ugyanarra a betűre lesznek alakítva).</string>
|
||||
<string name="version_label">Verzió: %1$s</string>
|
||||
<string name="open_biometric_prompt_unlock_database">Ujjlenyomat-leolvasás</string>
|
||||
<string name="encrypted_value_stored">Titkosított jelszó tárolva</string>
|
||||
<string name="biometric_invalid_key">Az biometrikus kulcs nem olvasható. Törölje, és ismételje meg a biometrikus felismerési folyamatot.</string>
|
||||
<string name="biometric_scanning_error">Ujjlenyomat probléma: %1$s</string>
|
||||
<string name="open_biometric_prompt_store_credential">Használjon ujjlenyomatot a jelszó tárolásához</string>
|
||||
<string name="no_credentials_stored">Az adatbázisnak még nincs jelszava.</string>
|
||||
<string name="education_unlock_summary">Adja meg a jelszót és/vagy a kulcsfájlt, hogy kinyithassa az adatbázist.
|
||||
\n
|
||||
@@ -180,7 +176,6 @@
|
||||
<string name="menu_move">Áthelyezés</string>
|
||||
<string name="menu_paste">Beillesztés</string>
|
||||
<string name="menu_cancel">Mégse</string>
|
||||
<string name="menu_biometric_remove_key">Mentett ujjlenyomat törlése</string>
|
||||
<string name="menu_file_selection_read_only">Írásvédett</string>
|
||||
<string name="menu_open_file_read_and_write">Módosítható</string>
|
||||
<string name="create_keepass_file">Új adatbázis létrehozása</string>
|
||||
@@ -202,7 +197,6 @@
|
||||
<string name="warning_empty_password">Biztos, hogy jelszavas feloldási védelem nélkül folytatja\?</string>
|
||||
<string name="warning_no_encryption_key">Biztos, hogy titkosítási kulcs nélkül folytatja\?</string>
|
||||
<string name="build_label">Összeállítás: %1$s</string>
|
||||
<string name="biometric_not_recognized">Az ujjlenyomat nem ismerhető fel</string>
|
||||
<string name="database_history">Előzmények</string>
|
||||
<string name="menu_appearance_settings">Megjelenés</string>
|
||||
<string name="general">Általános</string>
|
||||
@@ -217,7 +211,7 @@
|
||||
<string name="list_password_generator_options_summary">Beállítja a jelszó-előállító számára megengedett karaktereket</string>
|
||||
<string name="clipboard">Vágólap</string>
|
||||
<string name="clipboard_notifications_title">Vágólap-értesítések</string>
|
||||
<string name="clipboard_notifications_summary">A vágólap-értesítések engedélyezése a bejegyzésmezők másolásakor</string>
|
||||
<string name="clipboard_notifications_summary">Vágólap-értesítések megjelenítése a bejegyzésmezők másolásakor</string>
|
||||
<string name="clipboard_warning">Ha a vágólap automatikus törlése meghiúsul, akkor törölje kézzel az előzményeket.</string>
|
||||
<string name="lock">Zárolás</string>
|
||||
<string name="lock_database_screen_off_title">Képernyőzár</string>
|
||||
@@ -227,9 +221,8 @@
|
||||
<string name="biometric_unlock_enable_summary">Lehetővé teszi, hogy leolvassa az ujjlenyomatát az adatbázis megnyitásához</string>
|
||||
<string name="biometric_delete_all_key_title">Titkosítási kulcsok törlése</string>
|
||||
<string name="biometric_delete_all_key_summary">Az összes ujjlenyomat-felismeréssel kapcsolatos titkosítási kulcs törlése</string>
|
||||
<string name="biometric_delete_all_key_warning">Biztos, hogy törli az összes ujjlenyomat-felismeréssel kapcsolatos titkosítási kulcsot\?</string>
|
||||
<string name="unavailable_feature_text">A funkciót nem sikerült elindítani.</string>
|
||||
<string name="unavailable_feature_version">Az Android verziója (%1$s) nem éri el a minimálisan szükséges verziót (%2$s).</string>
|
||||
<string name="unavailable_feature_version">Az eszköz Android %1$s rendszert futtat, de %2$s vagy újabb szükséges.</string>
|
||||
<string name="unavailable_feature_hardware">Nem található a megfelelő hardver.</string>
|
||||
<string name="file_name">Fájlnév</string>
|
||||
<string name="path">Útvonal</string>
|
||||
@@ -287,8 +280,6 @@
|
||||
\nA csoportok (~mappák) rendszerezik a bejegyzéseket az adatbázisban.</string>
|
||||
<string name="education_search_title">Keresés a bejegyzések között</string>
|
||||
<string name="education_search_summary">Adja meg a címet, felhasználónevet vagy más mezők tartalmát, hogy lekérje a jelszavát.</string>
|
||||
<string name="education_biometric_title">Adatbázis feloldása ujjlenyomattal</string>
|
||||
<string name="education_biometric_summary">Kösse össze a jelszavát a mentett ujjlenyomatával, hogy gyorsan fel tudja oldani az adatbázist.</string>
|
||||
<string name="education_entry_edit_title">Bejegyzés szerkesztése</string>
|
||||
<string name="education_generate_password_title">Erős jelszó létrehozása</string>
|
||||
<string name="education_generate_password_summary">Állítson elő egy erős jelszót, és rendelje hozzá a bejegyzéshez, adja meg egyszerűen az űrlap feltételeinek megfelelően, és ne felejtse el biztonságosan tárolni.</string>
|
||||
@@ -326,7 +317,6 @@
|
||||
<string name="contribute">Támogatás</string>
|
||||
<string name="encryption_chacha20">ChaCha20</string>
|
||||
<string name="kdf_AES">AES</string>
|
||||
<string name="kdf_Argon2">Argon2</string>
|
||||
<string name="style_choose_title">Alkalmazástéma</string>
|
||||
<string name="style_choose_summary">Az alkalmazásban használt téma</string>
|
||||
<string name="icon_pack_choose_title">Ikoncsomag</string>
|
||||
@@ -346,26 +336,22 @@
|
||||
<string name="education_setup_OTP_title">OPT beállítása</string>
|
||||
<string name="autofill_auto_search_summary">Keresési találatok automatikus javaslata a webes domain vagy az alkalmazásazonosító alapján</string>
|
||||
<string name="autofill_auto_search_title">Automatikus keresés</string>
|
||||
<string name="clear_clipboard_notification_summary">Az adatbázis zárolása az értesítés bezárásakor</string>
|
||||
<string name="clear_clipboard_notification_summary">Az adatbázis zárolása, ha a vágólap ideje lejár, vagy ha a használata után bezárja az értesítést</string>
|
||||
<string name="clear_clipboard_notification_title">Zárolás bezáráskor</string>
|
||||
<string name="lock_database_show_button_summary">Megjeleníti a zárolás gombot a felhasználói felületen</string>
|
||||
<string name="lock_database_show_button_title">Zárolás gomb megjelenítése</string>
|
||||
<string name="lock_database_back_root_summary">Az adatbázis zárolása, ha a felhasználó a vissza gombra kattint az indítóképernyőn</string>
|
||||
<string name="autofill_preference_title">Automatikus kitöltés beállításai</string>
|
||||
<string name="biometric_prompt_extract_credential_message">Adatbázis hitelesítő adatainak kinyerése ujjlenyomattal</string>
|
||||
<string name="biometric_prompt_extract_credential_title">Adatbázis megnyitása ujjlenyomat-felismeréssel</string>
|
||||
<string name="biometric_prompt_store_credential_message">Figyelmeztetés: Továbbra is meg kell jegyeznie a mesterjelszót, ha ujjlenyomat-felismerést használ.</string>
|
||||
<string name="biometric_prompt_store_credential_title">Ujjlenyomat mentése</string>
|
||||
<string name="warning_database_link_revoked">A fájlhoz történő hozzáférést visszavonta a fájlkezelő</string>
|
||||
<string name="warning_database_read_only">Fájlírási-hozzáférés megadása az adatbázis-változások mentéséhez</string>
|
||||
<string name="hide_broken_locations_summary">A törött adatbázis-hivatkozások elrejtése a nemrég használt adatbázisok listájában</string>
|
||||
<string name="hide_broken_locations_title">Törött adatbázis-hivatkozások elrejtése</string>
|
||||
<string name="show_recent_files_summary">A nemrég használt adatbázisok helyének megjelenítése</string>
|
||||
<string name="show_recent_files_title">Nemrég használt fájlok megjelenítése</string>
|
||||
<string name="remember_keyfile_locations_summary">Az adatbázis-kulcsfájlok helyének megjegyzése</string>
|
||||
<string name="remember_keyfile_locations_title">Kulcsfájlok helyének mentése</string>
|
||||
<string name="remember_database_locations_summary">Az adatbázisok helyének megjegyzése</string>
|
||||
<string name="remember_database_locations_title">Adatbázisok helyének mentése</string>
|
||||
<string name="remember_keyfile_locations_summary">Megjegyzi az adatbázis-kulcsfájlok helyét</string>
|
||||
<string name="remember_keyfile_locations_title">Kulcsfájlok helyének megjegyzése</string>
|
||||
<string name="remember_database_locations_summary">Megjegyzi az adatbázisok helyét</string>
|
||||
<string name="remember_database_locations_title">Adatbázisok helyének megjegyzése</string>
|
||||
<string name="auto_focus_search_summary">Keresés kérése az adatbázis megnyitásakor</string>
|
||||
<string name="error_create_database">Az adatbázisfájl létrehozása sikertelen.</string>
|
||||
<string name="error_label_exists">Ez a címke már létezik.</string>
|
||||
@@ -394,7 +380,6 @@
|
||||
<string name="enable_auto_save_database_summary">Az adatbázis mentése minden fontos művelet után („Módosítható” módban)</string>
|
||||
<string name="enable_auto_save_database_title">Adatbázis automatikus mentése</string>
|
||||
<string name="recycle_bin_group_title">Kuka csoportja</string>
|
||||
<string name="credential_before_click_biometric_button">Írja be a jelszót, majd kattintson az „Ujjlenyomat” gombra.</string>
|
||||
<string name="keystore_not_accessible">Az kulcstár nincs helyesen előkészítve.</string>
|
||||
<string name="warning_permanently_delete_nodes">Biztos, hogy végleg törli a kiválasztott csomópontokat\?</string>
|
||||
<string name="command_execution">Parancs végrehajtása…</string>
|
||||
@@ -417,7 +402,7 @@
|
||||
<string name="max_history_size_title">Maximális méret</string>
|
||||
<string name="max_history_items_summary">Korlátozza az előzmények számát bejegyzésenként</string>
|
||||
<string name="max_history_items_title">Maximális szám</string>
|
||||
<string name="database_data_compression_summary">Az adattömörítés csökkenti az adatbázis méretét.</string>
|
||||
<string name="database_data_compression_summary">Az adattömörítés csökkenti az adatbázis méretét</string>
|
||||
<string name="database_data_compression_title">Adattömörítés</string>
|
||||
<string name="advanced_unlock_explanation_summary">Speciális feloldás használat az adatbázis könnyebb megnyitásához</string>
|
||||
<string name="clipboard_explanation_summary">A bejegyzésmezők másolása az eszköz vágólapjának használatával</string>
|
||||
@@ -470,4 +455,35 @@
|
||||
<string name="lock_database_back_root_title">Nyomja meg a „Vissza” gombot a zároláshoz</string>
|
||||
<string name="do_not_kill_app">Ne lője ki az alkalmazást…</string>
|
||||
<string name="selection_mode">Kiválasztási mód</string>
|
||||
<string name="keyboard_previous_database_credentials_title">Adatbázishitelesítő-adatok képernyője</string>
|
||||
<string name="keyboard_change">Billentyűzet váltása</string>
|
||||
<string name="keyboard_save_search_info_title">Megosztott információk mentése</string>
|
||||
<string name="keyboard_search_share_summary">Megosztott információk automatikus keresése a billentyűzet kitöltéséhez</string>
|
||||
<string name="keyboard_search_share_title">Megosztott információk keresése</string>
|
||||
<string name="notification">Értesítés</string>
|
||||
<string name="database_data_remove_unlinked_attachments_summary">Eltávolítja azokat a mellékleteket, melyek az adatbázisban szerepelnek, de nem tartoznak bejegyzéshez</string>
|
||||
<string name="database_data_remove_unlinked_attachments_title">Nem összekapcsolt adatok eltávolítása</string>
|
||||
<string name="data">Adatok</string>
|
||||
<string name="biometric_security_update_required">Biometrikus biztonsági frissítés szükséges.</string>
|
||||
<string name="configure_biometric">Nincs biometrikus vagy eszközazonosító beállítva.</string>
|
||||
<string name="warning_empty_keyfile_explanation">A kulcsfájl tartalmának sosem szabad megváltoznia, és a legjobb esetben véletlenszerűen előállított adatokat kellene tartalmaznia.</string>
|
||||
<string name="warning_empty_keyfile">Nem ajánlott, hogy üres kulcsfájl adjon hozzá.</string>
|
||||
<string name="warning_sure_remove_data">Mindenképp törli ezeket az adatokat\?</string>
|
||||
<string name="warning_remove_unlinked_attachment">A nem összekapcsolt adatok törlése csökkentheti az adatbázis méretét, de törölheti a KeePass-bővítmények által használt adatokat.</string>
|
||||
<string name="warning_sure_add_file">Mindenképp hozzáadja a fájlt\?</string>
|
||||
<string name="warning_replace_file">A fájl feltöltésével lecseréli a meglévőt.</string>
|
||||
<string name="warning_file_too_big">A KeePass-adatbázis csak kis segédfájlok tárolására lett kitalálva (például PGP-kulcsfájlokra).
|
||||
\n
|
||||
\nA feltöltéstől az adatbázis nagyon nagyra nőhet, és csökkenhet a teljesítmény.</string>
|
||||
<string name="warning_empty_recycle_bin">Végleg törli az összes csomópontot a kukából\?</string>
|
||||
<string name="filter">Szűrő</string>
|
||||
<string name="registration_mode">Regisztrációs mód</string>
|
||||
<string name="save_mode">Mentési mód</string>
|
||||
<string name="search_mode">Keresési mód</string>
|
||||
<string name="subdomain_search_summary">Webdomainek keresése aldomain megszorításokkal</string>
|
||||
<string name="subdomain_search_title">Aldomain keresés</string>
|
||||
<string name="error_registration_read_only">Az új elem mentése nem engedélyezett írásvédett adatbázisban</string>
|
||||
<string name="error_string_type">A szöveg nem egyezik a kért elemmel.</string>
|
||||
<string name="content_description_credentials_information">Hitelesítő adatok információi</string>
|
||||
<string name="content_description_add_item">Elem hozzáadása</string>
|
||||
</resources>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user