mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
169 Commits
2.5.0.0bet
...
2.5.0.0bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88ac2ecf98 | ||
|
|
227cb078a5 | ||
|
|
a17582bf6d | ||
|
|
e8ba5dc6e2 | ||
|
|
f37d3324e6 | ||
|
|
fcb06c8c22 | ||
|
|
d8bdf4500a | ||
|
|
69ea4d5414 | ||
|
|
7e9f99e5c8 | ||
|
|
95541d99c4 | ||
|
|
05ca7e6d69 | ||
|
|
644a4a4886 | ||
|
|
7eb82ddbea | ||
|
|
e721d4ca9e | ||
|
|
c72582b679 | ||
|
|
217f0a82b5 | ||
|
|
1efd172b42 | ||
|
|
e8d0d03ad9 | ||
|
|
6cb97de793 | ||
|
|
77dbdc235a | ||
|
|
a9c87ea2b8 | ||
|
|
232829526c | ||
|
|
b5d8b2f14c | ||
|
|
c2673a627c | ||
|
|
bb49256188 | ||
|
|
1b6a284dd6 | ||
|
|
36b35b907e | ||
|
|
f61b310ff2 | ||
|
|
c1e84b3bf1 | ||
|
|
9971d27ef3 | ||
|
|
3c0835f725 | ||
|
|
023aba2b60 | ||
|
|
abc0a14dda | ||
|
|
dc23c6a445 | ||
|
|
161a0ffc59 | ||
|
|
b96436c906 | ||
|
|
cee7419208 | ||
|
|
9ff6e7a080 | ||
|
|
9112a568d9 | ||
|
|
8d0c9bc894 | ||
|
|
e7b4e4501e | ||
|
|
df80084822 | ||
|
|
4df2891b1a | ||
|
|
d0560677fa | ||
|
|
9130a3851f | ||
|
|
bb3fb26847 | ||
|
|
8406264138 | ||
|
|
e51f48ebe0 | ||
|
|
a0a2aa4c5d | ||
|
|
65cea7b5ec | ||
|
|
fb589419de | ||
|
|
ae9844d5a1 | ||
|
|
645ea21581 | ||
|
|
60fd675dff | ||
|
|
48903da446 | ||
|
|
5399df134c | ||
|
|
a858040ace | ||
|
|
d792b7d5ae | ||
|
|
8b3ed1a7dc | ||
|
|
47004857ea | ||
|
|
82a6b8d7ef | ||
|
|
a30bcf1fa7 | ||
|
|
febe978fa1 | ||
|
|
1eeefcf495 | ||
|
|
ff6b0eee6c | ||
|
|
a7c8eaa937 | ||
|
|
55545e31e8 | ||
|
|
34132c6da7 | ||
|
|
fbafa99a3b | ||
|
|
bd63805611 | ||
|
|
ae8398a434 | ||
|
|
0b9318de88 | ||
|
|
55e4bbf9db | ||
|
|
f33a572751 | ||
|
|
2849cedbd8 | ||
|
|
041d1b29d1 | ||
|
|
cdcdc2fee1 | ||
|
|
8e35d5bc8d | ||
|
|
f12e368707 | ||
|
|
f01895c7a7 | ||
|
|
4ce0c067e1 | ||
|
|
728a2cbfae | ||
|
|
a4aab3b933 | ||
|
|
11ee0d6544 | ||
|
|
2b80dad47c | ||
|
|
7bb13ec1c2 | ||
|
|
344010d2bb | ||
|
|
ef37ecddc6 | ||
|
|
91a38e205e | ||
|
|
bd4de382e4 | ||
|
|
623244c3bd | ||
|
|
7a06ff6dec | ||
|
|
6d735e24ff | ||
|
|
5c30d544fa | ||
|
|
54a3876288 | ||
|
|
57f71afb98 | ||
|
|
d8ec198f6f | ||
|
|
faa6d7c9f6 | ||
|
|
d79a5b17a5 | ||
|
|
34deea5d32 | ||
|
|
edc7324c1d | ||
|
|
b9190e8254 | ||
|
|
534878ae99 | ||
|
|
99bfd21ecc | ||
|
|
cb5c324c9d | ||
|
|
55dc504f26 | ||
|
|
ecb0138c90 | ||
|
|
0860eeb87f | ||
|
|
f08bff61cf | ||
|
|
432aca6465 | ||
|
|
7cdb8db146 | ||
|
|
6ba9dedcb8 | ||
|
|
6d603608f4 | ||
|
|
64f4d9fb84 | ||
|
|
60ccc450ae | ||
|
|
ddb5f327a3 | ||
|
|
df90ea42eb | ||
|
|
a062d648b3 | ||
|
|
a59ae820b5 | ||
|
|
228831acdd | ||
|
|
bf15ee43da | ||
|
|
608f45677c | ||
|
|
1cb15f214b | ||
|
|
cd4a9e9b03 | ||
|
|
d6eae56d4f | ||
|
|
b5a87a63dc | ||
|
|
1eb17c4f34 | ||
|
|
8a6ce1f711 | ||
|
|
0f22f8af45 | ||
|
|
b2e81e6fd9 | ||
|
|
f82eab942d | ||
|
|
aa29aec40f | ||
|
|
181def52ab | ||
|
|
96d2bd63cc | ||
|
|
194021a957 | ||
|
|
9e307f94ea | ||
|
|
155b2de138 | ||
|
|
c76c3fd2be | ||
|
|
01c5554944 | ||
|
|
542cf65b41 | ||
|
|
f037561a67 | ||
|
|
379263a6d3 | ||
|
|
c420ca01f6 | ||
|
|
a1f9db6eee | ||
|
|
c9212174c4 | ||
|
|
0065336377 | ||
|
|
4f9625a3e1 | ||
|
|
7688ebd29b | ||
|
|
3f2a7f1eb3 | ||
|
|
b3d067d0c8 | ||
|
|
a3b4ad5ac1 | ||
|
|
e3b329d27f | ||
|
|
7d10c43822 | ||
|
|
fcb0d45d39 | ||
|
|
5492db0223 | ||
|
|
2207b05f5f | ||
|
|
f15a0c2591 | ||
|
|
92fb22129c | ||
|
|
ccca9c4400 | ||
|
|
0597cb4416 | ||
|
|
0602174e50 | ||
|
|
6fddc92ce7 | ||
|
|
aa30df6454 | ||
|
|
f6c61ab407 | ||
|
|
2ab81ed77c | ||
|
|
0ade035f43 | ||
|
|
73b62035d8 | ||
|
|
28837db308 | ||
|
|
85befef260 |
32
CHANGELOG
32
CHANGELOG
@@ -1,4 +1,34 @@
|
|||||||
KeepassDX (2.5.0.0beta11)
|
KeepassDX (2.5.0.0beta18)
|
||||||
|
* New recent databases views
|
||||||
|
* New information dialog
|
||||||
|
* Custom fields for the Magikeyboard
|
||||||
|
* Timeout for the Magikeyboard
|
||||||
|
* Long press for keyboard selection
|
||||||
|
* Fix memory when opening the database
|
||||||
|
* Memory management for attachments
|
||||||
|
|
||||||
|
KeepassDX (2.5.0.0beta17)
|
||||||
|
* Fix font and search
|
||||||
|
|
||||||
|
KeepassDX (2.5.0.0beta16)
|
||||||
|
* New search in a single fragment
|
||||||
|
* Search suggestions
|
||||||
|
* Added the display of usernames
|
||||||
|
* Added translations
|
||||||
|
* Fix read-only mode
|
||||||
|
* Fix parcelable / toolbar / back
|
||||||
|
|
||||||
|
KeepassDX (2.5.0.0beta15)
|
||||||
|
* Read only mode
|
||||||
|
* Best group recovery for the navigation fragment
|
||||||
|
* Fix copies in notifications
|
||||||
|
* Fix orientation
|
||||||
|
* Added translations
|
||||||
|
|
||||||
|
KeepassDX (2.5.0.0beta14)
|
||||||
|
* Optimize all the memory with parcelables / fix search
|
||||||
|
|
||||||
|
KeepassDX (2.5.0.0beta13)
|
||||||
* Fix memory issue with parcelable (crash in beta12 version)
|
* Fix memory issue with parcelable (crash in beta12 version)
|
||||||
|
|
||||||
KeepassDX (2.5.0.0beta12)
|
KeepassDX (2.5.0.0beta12)
|
||||||
|
|||||||
55
FAQ.md
55
FAQ.md
@@ -1,55 +0,0 @@
|
|||||||
# F.A.Q.
|
|
||||||
|
|
||||||
## Why KeePass DX?
|
|
||||||
|
|
||||||
KeePass DX is an **Android password manager** implemented from Keepass password manager.
|
|
||||||
|
|
||||||
KeePass DX was created to meet the security and usability needs of a KeePass application on Android :
|
|
||||||
|
|
||||||
- To be easy to use with **secure password management and form filling tools**.
|
|
||||||
- To use only tools under **open source license** to guarantee the security of the application (With [open source store](https://f-droid.org/en/) and no closed API).
|
|
||||||
- To be in a **native langage** (java) for weight, security and a better integration of the application.
|
|
||||||
- To respect **Android design, architecture and ergonomic**.
|
|
||||||
|
|
||||||
## What makes KeePass DX stand out from other password managers?
|
|
||||||
|
|
||||||
- We **do not recover your sensitive data** on a private server or a closed cloud, you have control of your passwords.
|
|
||||||
- We respect **KeePass file standards** to maintain compatibility and data porting on different devices (computers and portable devices with different operating system).
|
|
||||||
- The code is **open source**, which implies increased **security**, you can check how the encryption algorithms are implemented.
|
|
||||||
- We remain attentive to **your needs** and we can even integrate the features that you have defined.
|
|
||||||
- We **do not put advertising** even in the free version.
|
|
||||||
|
|
||||||
## How am I sure my passwords are safely stored on the application?
|
|
||||||
|
|
||||||
- We allow users to save and use passwords, keys and digital identities in a secure way by **integrating the last encryption algorithms** and **Android architecture standards**.
|
|
||||||
- You can increase the security of your database by increasing the rounds of encryption keys. *(In Settings -> Database Settings when your database is open)* **Warning**: *Increase the number of rounds sparingly to have a reasonable opening time.*
|
|
||||||
|
|
||||||
## Can I store my data on a cloud storage?
|
|
||||||
|
|
||||||
**Yes** this is possible. Otherwise, we **recommend using cloud with personal server and open source license**, like [NextCloud](https://f-droid.org/en/packages/com.nextcloud.client/) to be sure how your databases are stored.
|
|
||||||
|
|
||||||
## Can I recover my passwords on another device if I loose my main device?
|
|
||||||
|
|
||||||
**Yes** you can, but you **must first save the .kdb or .kdbx file from your database to an external storage** *(like a hardrive or a cloud)*.
|
|
||||||
We recommend you save your data after each modification so incase you loose your android device you could retrieve the data and import it into the new KeePass DX installed on the new android device.
|
|
||||||
|
|
||||||
## Why are updates not available at the same time on all stores?
|
|
||||||
|
|
||||||
- **PlayStore** only needs an APK generated and manually signed to be available on the store, it usually takes **20 minutes** to be available because it is deployed with fastlane. But the management of the APK and its data by the google servers is obscure.
|
|
||||||
- **F-Droid**, to **ensure that the code is open source**, checks the sources directly on git repository (by checking the presence of new tags) and builds itself the APK that the server signs during the compilation of code and dependencies. Updating the project will take **1-10 days** for F-Droid to analyze all available repositories, build sources and deploy the generated APK. So F-Droid is slower for deployment but it is run by **volunteers** and guaranteed a **clean APK**. :)
|
|
||||||
|
|
||||||
## Why not an online version?
|
|
||||||
|
|
||||||
The offline and online client concepts only exists with Keepass2Android because the file access network tools are directly integrated into the code of the main application. Which is a very dubious choice knowing that **it is not normally the purpose of a password management application to take care of external file synchronization on clouds** (which can be under closed licensed and recover your data base), it is rather the purpose of the [file management application](https://developer.android.com/guide/topics/providers/document-provider).
|
|
||||||
|
|
||||||
## Can I open my database easily other than with a password?
|
|
||||||
|
|
||||||
**Yes**, we have integrated a secure openning option of fingerprint for android devices that support this feature, so no one can access the application without scanning his/her fingerprint or fill a master key.
|
|
||||||
|
|
||||||
## Can I open my database without my master key (master password and/or key file)?
|
|
||||||
|
|
||||||
**No**, you can not open a database file without the master password (and / or) the associated key file. Be sure to remember your master password and save the key file in a safe place.
|
|
||||||
|
|
||||||
## Can I suggest features and report bugs for the application?
|
|
||||||
**Yes**, we welcome this you could go ahead and do that on our github:
|
|
||||||
https://github.com/Kunzisoft/KeePassDX
|
|
||||||
@@ -28,7 +28,7 @@ KeePass DX is a **free open source password manager for Android**, which helps y
|
|||||||
|
|
||||||
Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
|
Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
|
||||||
|
|
||||||
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free.*
|
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ You can contribute in different ways to help us on our work.
|
|||||||
|
|
||||||
## F.A.Q.
|
## F.A.Q.
|
||||||
|
|
||||||
Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassDX/blob/master/FAQ.md)
|
Other questions? You can read the [F.A.Q.](https://www.keepassdx.com/FAQ)
|
||||||
|
|
||||||
## Other devices
|
## Other devices
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassD
|
|||||||
|
|
||||||
This file is part of KeePass DX.
|
This file is part of KeePass DX.
|
||||||
|
|
||||||
KeePass DX is free software: you can redistribute it and/or modify
|
[KeePass DX](https://www.keepassdx.com) is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 27
|
compileSdkVersion 27
|
||||||
buildToolsVersion '27.0.3'
|
buildToolsVersion '28.0.2'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.kunzisoft.keepass"
|
applicationId "com.kunzisoft.keepass"
|
||||||
minSdkVersion 15
|
minSdkVersion 14
|
||||||
targetSdkVersion 27
|
targetSdkVersion 27
|
||||||
versionCode = 13
|
versionCode = 18
|
||||||
versionName = "2.5.0.0beta13"
|
versionName = "2.5.0.0beta18"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||||
@@ -39,7 +39,7 @@ android {
|
|||||||
productFlavors {
|
productFlavors {
|
||||||
libre {
|
libre {
|
||||||
applicationIdSuffix = ".libre"
|
applicationIdSuffix = ".libre"
|
||||||
versionNameSuffix "-libre"
|
buildConfigField "String", "BUILD_VERSION", "\"libre\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "false"
|
buildConfigField "boolean", "CLOSED_STORE", "false"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
||||||
@@ -48,7 +48,7 @@ android {
|
|||||||
}
|
}
|
||||||
pro {
|
pro {
|
||||||
applicationIdSuffix = ".pro"
|
applicationIdSuffix = ".pro"
|
||||||
versionNameSuffix "-pro"
|
buildConfigField "String", "BUILD_VERSION", "\"pro\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "true"
|
buildConfigField "boolean", "FULL_VERSION", "true"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{}"
|
||||||
@@ -56,7 +56,7 @@ android {
|
|||||||
}
|
}
|
||||||
free {
|
free {
|
||||||
applicationIdSuffix = ".free"
|
applicationIdSuffix = ".free"
|
||||||
versionNameSuffix "-free"
|
buildConfigField "String", "BUILD_VERSION", "\"free\""
|
||||||
buildConfigField "boolean", "FULL_VERSION", "false"
|
buildConfigField "boolean", "FULL_VERSION", "false"
|
||||||
buildConfigField "boolean", "CLOSED_STORE", "true"
|
buildConfigField "boolean", "CLOSED_STORE", "true"
|
||||||
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
buildConfigField "String[]", "STYLES_DISABLED", "{\"KeepassDXStyle_Dark\",\"KeepassDXStyle_Blue\",\"KeepassDXStyle_Red\",\"KeepassDXStyle_Purple\"}"
|
||||||
@@ -94,7 +94,7 @@ dependencies {
|
|||||||
implementation 'joda-time:joda-time:2.9.9'
|
implementation 'joda-time:joda-time:2.9.9'
|
||||||
implementation 'org.sufficientlysecure:html-textview:3.5'
|
implementation 'org.sufficientlysecure:html-textview:3.5'
|
||||||
implementation 'com.nononsenseapps:filepicker:4.1.0'
|
implementation 'com.nononsenseapps:filepicker:4.1.0'
|
||||||
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.11.0'
|
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
|
||||||
// Permissions
|
// Permissions
|
||||||
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
||||||
// if you don't use android.app.Fragment you can exclude support for them
|
// if you don't use android.app.Fragment you can exclude support for them
|
||||||
@@ -103,13 +103,14 @@ dependencies {
|
|||||||
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
|
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
|
||||||
// Apache Commons Collections
|
// Apache Commons Collections
|
||||||
implementation 'commons-collections:commons-collections:3.2.1'
|
implementation 'commons-collections:commons-collections:3.2.1'
|
||||||
|
implementation 'org.apache.commons:commons-io:1.3.2'
|
||||||
// Base64
|
// Base64
|
||||||
implementation 'biz.source_code:base64coder:2010-12-19'
|
implementation 'biz.source_code:base64coder:2010-12-19'
|
||||||
|
// IO-Extras
|
||||||
|
implementation 'com.github.davidmoten:io-extras:0.1'
|
||||||
implementation 'com.google.code.gson:gson:2.8.4'
|
implementation 'com.google.code.gson:gson:2.8.4'
|
||||||
implementation 'com.google.guava:guava:23.0-android'
|
implementation 'com.google.guava:guava:23.0-android'
|
||||||
// Icon pack, classic for all, material for libre and pro
|
// Icon pack
|
||||||
implementation project(path: ':icon-pack-classic')
|
implementation project(path: ':icon-pack-classic')
|
||||||
implementation project(path: ':icon-pack-material')
|
implementation project(path: ':icon-pack-material')
|
||||||
implementation project(path: ':magikeyboard')
|
|
||||||
implementation project(path: ':keepass-model')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ public class PwEntryTestV4 extends TestCase {
|
|||||||
|
|
||||||
entry.setBackgroupColor("blue");
|
entry.setBackgroupColor("blue");
|
||||||
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
|
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
|
||||||
entry.setCustomIcon(new PwIconCustom(UUID.randomUUID(), new byte[0]));
|
entry.setIconCustom(new PwIconCustom(UUID.randomUUID(), new byte[0]));
|
||||||
entry.setForegroundColor("red");
|
entry.setForegroundColor("red");
|
||||||
entry.addToHistory(new PwEntryV4());
|
entry.addToHistory(new PwEntryV4());
|
||||||
entry.setIcon(new PwIconStandard(5));
|
entry.setIconStandard(new PwIconStandard(5));
|
||||||
entry.setOverrideURL("override");
|
entry.setOverrideURL("override");
|
||||||
entry.setParent(new PwGroupV4());
|
entry.setParent(new PwGroupV4());
|
||||||
entry.addExtraField("key2", new ProtectedString(false, "value2"));
|
entry.addExtraField("key2", new ProtectedString(false, "value2"));
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.database.PwEntry;
|
|||||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||||
import com.kunzisoft.keepass.database.PwGroup;
|
import com.kunzisoft.keepass.database.PwGroup;
|
||||||
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
|
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
|
||||||
import com.kunzisoft.keepass.search.SearchDbHelper;
|
import com.kunzisoft.keepass.database.search.SearchDbHelper;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -72,8 +72,8 @@ public class DeleteEntry extends AndroidTestCase {
|
|||||||
|
|
||||||
// Verify the entries were removed from the search index
|
// Verify the entries were removed from the search index
|
||||||
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
|
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
|
||||||
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME);
|
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
|
||||||
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME);
|
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
|
||||||
|
|
||||||
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
|
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
|
||||||
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
|
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.tests.database;
|
package com.kunzisoft.keepass.tests.database;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.PwDatabaseV4;
|
import com.kunzisoft.keepass.database.PwDatabaseV4;
|
||||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
public class EntryV4 extends TestCase {
|
public class EntryV4 extends TestCase {
|
||||||
|
|
||||||
public void testBackup() {
|
public void testBackup() {
|
||||||
@@ -46,7 +46,7 @@ public class EntryV4 extends TestCase {
|
|||||||
entry.createBackup(db);
|
entry.createBackup(db);
|
||||||
|
|
||||||
PwEntryV4 backup = entry.getHistory().get(0);
|
PwEntryV4 backup = entry.getHistory().get(0);
|
||||||
entry.endToManageFieldReferences();
|
entry.stopToManageFieldReferences();
|
||||||
assertEquals("Title2", backup.getTitle());
|
assertEquals("Title2", backup.getTitle());
|
||||||
assertEquals("User2", backup.getUsername());
|
assertEquals("User2", backup.getUsername());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@
|
|||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:fullBackupContent="@xml/backup"
|
android:fullBackupContent="@xml/backup"
|
||||||
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
|
||||||
android:theme="@style/KeepassDXStyle.Night"
|
android:theme="@style/KeepassDXStyle.Night">
|
||||||
tools:replace="android:theme">
|
|
||||||
<!-- TODO backup API Key -->
|
<!-- TODO backup API Key -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.backup.api_key"
|
android:name="com.google.android.backup.api_key"
|
||||||
@@ -104,11 +103,19 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.activities.GroupActivity"
|
android:name="com.kunzisoft.keepass.activities.GroupActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:windowSoftInputMode="adjustPan">
|
android:windowSoftInputMode="adjustPan"
|
||||||
|
android:launchMode="singleTop">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.default_searchable"
|
android:name="android.app.default_searchable"
|
||||||
android:value="com.kunzisoft.keepass.search.SearchResults"
|
android:value="com.kunzisoft.keepass.search.SearchResults"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.app.searchable"
|
||||||
|
android:resource="@xml/searchable" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.activities.EntryActivity"
|
android:name="com.kunzisoft.keepass.activities.EntryActivity"
|
||||||
@@ -118,24 +125,28 @@
|
|||||||
android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
|
android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
|
||||||
android:configChanges="orientation|keyboardHidden"
|
android:configChanges="orientation|keyboardHidden"
|
||||||
android:windowSoftInputMode="stateHidden" />
|
android:windowSoftInputMode="stateHidden" />
|
||||||
<activity
|
|
||||||
android:name="com.kunzisoft.keepass.search.SearchResultsActivity"
|
|
||||||
android:launchMode="standard">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
</intent-filter>
|
|
||||||
<meta-data
|
|
||||||
android:name="android.app.searchable"
|
|
||||||
android:resource="@xml/searchable" />
|
|
||||||
</activity>
|
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
|
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
|
||||||
<activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity"
|
<activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity"
|
||||||
android:configChanges="orientation|keyboardHidden" />
|
android:configChanges="orientation|keyboardHidden" />
|
||||||
<activity android:name="com.kunzisoft.keepass.selection.EntrySelectionAuthActivity"
|
<activity android:name="com.kunzisoft.keepass.selection.EntrySelectionAuthActivity"
|
||||||
android:configChanges="orientation|keyboardHidden" />
|
android:configChanges="orientation|keyboardHidden" />
|
||||||
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
|
<activity android:name="com.kunzisoft.keepass.settings.SettingsAutofillActivity" />
|
||||||
|
<activity android:name="com.kunzisoft.keepass.magikeyboard.EntryRetrieverActivity"
|
||||||
|
android:label="@string/keyboard_name">
|
||||||
|
</activity>
|
||||||
|
<activity android:name="com.kunzisoft.keepass.settings.MagikIMESettings"
|
||||||
|
android:label="@string/keyboard_setting_label">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Receiver for Keyboard -->
|
||||||
|
<receiver
|
||||||
|
android:name="com.kunzisoft.keepass.magikeyboard.receiver.NotificationDeleteBroadcastReceiver"
|
||||||
|
android:exported="false" >
|
||||||
|
</receiver>
|
||||||
<service
|
<service
|
||||||
android:name="com.kunzisoft.keepass.notifications.NotificationCopyingService"
|
android:name="com.kunzisoft.keepass.notifications.NotificationCopyingService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -152,6 +163,17 @@
|
|||||||
<action android:name="android.service.autofill.AutofillService" />
|
<action android:name="android.service.autofill.AutofillService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
<service
|
||||||
|
android:name="com.kunzisoft.keepass.magikeyboard.MagikIME"
|
||||||
|
android:label="@string/keyboard_label"
|
||||||
|
android:permission="android.permission.BIND_INPUT_METHOD" >
|
||||||
|
<meta-data android:name="android.view.im"
|
||||||
|
android:resource="@xml/keyboard_method"/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.view.InputMethod" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<service android:name="com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService" />
|
||||||
|
|
||||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities;
|
package com.kunzisoft.keepass.activities;
|
||||||
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
@@ -27,6 +26,7 @@ import android.util.Log;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.BuildConfig;
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.stylish.StylishActivity;
|
import com.kunzisoft.keepass.stylish.StylishActivity;
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ public class AboutActivity extends StylishActivity {
|
|||||||
|
|
||||||
setContentView(R.layout.about);
|
setContentView(R.layout.about);
|
||||||
|
|
||||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
toolbar.setTitle(getString(R.string.menu_about));
|
toolbar.setTitle(getString(R.string.menu_about));
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
assert getSupportActionBar() != null;
|
assert getSupportActionBar() != null;
|
||||||
@@ -48,18 +48,25 @@ public class AboutActivity extends StylishActivity {
|
|||||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
|
||||||
String version;
|
String version;
|
||||||
|
String build;
|
||||||
try {
|
try {
|
||||||
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
|
version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
|
||||||
version = packageInfo.versionName;
|
build = BuildConfig.BUILD_VERSION;
|
||||||
} catch (NameNotFoundException e) {
|
} catch (NameNotFoundException e) {
|
||||||
Log.w(getClass().getSimpleName(), "Unable to get application version", e);
|
Log.w(getClass().getSimpleName(), "Unable to get the app or the build version", e);
|
||||||
version = "Unable to get application version.";
|
version = "Unable to get the app version";
|
||||||
|
build = "Unable to get the build version";
|
||||||
}
|
}
|
||||||
version = getString(R.string.version_label) + " " + version;
|
version = getString(R.string.version_label, version);
|
||||||
TextView versionText = (TextView) findViewById(R.id.activity_about_version);
|
TextView versionTextView = findViewById(R.id.activity_about_version);
|
||||||
versionText.setText(version);
|
versionTextView.setText(version);
|
||||||
|
|
||||||
TextView disclaimerText = (TextView) findViewById(R.id.disclaimer);
|
build = getString(R.string.build_label, build);
|
||||||
|
TextView buildTextView = findViewById(R.id.activity_about_build);
|
||||||
|
buildTextView.setText(build);
|
||||||
|
|
||||||
|
|
||||||
|
TextView disclaimerText = findViewById(R.id.disclaimer);
|
||||||
disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear()));
|
disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.graphics.Color;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -46,7 +47,6 @@ import com.kunzisoft.keepass.database.ExtraFields;
|
|||||||
import com.kunzisoft.keepass.database.PwDatabase;
|
import com.kunzisoft.keepass.database.PwDatabase;
|
||||||
import com.kunzisoft.keepass.database.PwEntry;
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
|
||||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||||
import com.kunzisoft.keepass.lock.LockingHideActivity;
|
import com.kunzisoft.keepass.lock.LockingHideActivity;
|
||||||
import com.kunzisoft.keepass.notifications.NotificationCopyingService;
|
import com.kunzisoft.keepass.notifications.NotificationCopyingService;
|
||||||
@@ -65,6 +65,7 @@ import java.util.Date;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static com.kunzisoft.keepass.settings.PreferencesUtil.isClipboardNotificationsEnable;
|
import static com.kunzisoft.keepass.settings.PreferencesUtil.isClipboardNotificationsEnable;
|
||||||
|
import static com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields;
|
||||||
|
|
||||||
public class EntryActivity extends LockingHideActivity {
|
public class EntryActivity extends LockingHideActivity {
|
||||||
private final static String TAG = EntryActivity.class.getName();
|
private final static String TAG = EntryActivity.class.getName();
|
||||||
@@ -78,15 +79,17 @@ public class EntryActivity extends LockingHideActivity {
|
|||||||
|
|
||||||
protected PwEntry mEntry;
|
protected PwEntry mEntry;
|
||||||
private boolean mShowPassword;
|
private boolean mShowPassword;
|
||||||
protected boolean readOnly = false;
|
|
||||||
|
|
||||||
private ClipboardHelper clipboardHelper;
|
private ClipboardHelper clipboardHelper;
|
||||||
private boolean firstLaunchOfActivity;
|
private boolean firstLaunchOfActivity;
|
||||||
|
|
||||||
public static void launch(Activity act, PwEntry pw) {
|
private int iconColor;
|
||||||
|
|
||||||
|
public static void launch(Activity act, PwEntry pw, boolean readOnly) {
|
||||||
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
|
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
|
||||||
Intent intent = new Intent(act, EntryActivity.class);
|
Intent intent = new Intent(act, EntryActivity.class);
|
||||||
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||||
|
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
|
||||||
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +113,7 @@ public class EntryActivity extends LockingHideActivity {
|
|||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
readOnly = db.isReadOnly();
|
readOnly = db.isReadOnly() || readOnly;
|
||||||
|
|
||||||
mShowPassword = !PreferencesUtil.isPasswordMask(this);
|
mShowPassword = !PreferencesUtil.isPasswordMask(this);
|
||||||
|
|
||||||
@@ -123,6 +126,11 @@ public class EntryActivity extends LockingHideActivity {
|
|||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve the textColor to tint the icon
|
||||||
|
int[] attrs = {R.attr.textColorInverse};
|
||||||
|
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
||||||
|
iconColor = ta.getColor(0, Color.WHITE);
|
||||||
|
|
||||||
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
|
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
@@ -225,7 +233,7 @@ public class EntryActivity extends LockingHideActivity {
|
|||||||
|
|
||||||
startService(intent);
|
startService(intent);
|
||||||
}
|
}
|
||||||
mEntry.endToManageFieldReferences();
|
mEntry.stopToManageFieldReferences();
|
||||||
}
|
}
|
||||||
firstLaunchOfActivity = false;
|
firstLaunchOfActivity = false;
|
||||||
}
|
}
|
||||||
@@ -310,18 +318,10 @@ public class EntryActivity extends LockingHideActivity {
|
|||||||
mEntry.startToManageFieldReferences(pm);
|
mEntry.startToManageFieldReferences(pm);
|
||||||
|
|
||||||
// Assign title icon
|
// Assign title icon
|
||||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
db.getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), iconColor);
|
||||||
// Retrieve the textColor to tint the icon
|
|
||||||
int[] attrs = {R.attr.textColorInverse};
|
|
||||||
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
|
||||||
int iconColor = ta.getColor(0, Color.WHITE);
|
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), true, iconColor);
|
|
||||||
} else {
|
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign title text
|
// Assign title text
|
||||||
titleView.setText(mEntry.getTitle());
|
titleView.setText(mEntry.getVisualTitle());
|
||||||
|
|
||||||
// Assign basic fields
|
// Assign basic fields
|
||||||
entryContentsView.assignUserName(mEntry.getUsername());
|
entryContentsView.assignUserName(mEntry.getUsername());
|
||||||
@@ -330,12 +330,39 @@ public class EntryActivity extends LockingHideActivity {
|
|||||||
getString(R.string.copy_field, getString(R.string.entry_user_name)))
|
getString(R.string.copy_field, getString(R.string.entry_user_name)))
|
||||||
);
|
);
|
||||||
|
|
||||||
entryContentsView.assignPassword(mEntry.getPassword());
|
boolean allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this);
|
||||||
if (PreferencesUtil.allowCopyPasswordAndProtectedFields(this)) {
|
entryContentsView.assignPassword(mEntry.getPassword(), allowCopyPassword);
|
||||||
|
if (allowCopyPassword) {
|
||||||
entryContentsView.assignPasswordCopyListener(view ->
|
entryContentsView.assignPasswordCopyListener(view ->
|
||||||
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
|
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
|
||||||
getString(R.string.copy_field, getString(R.string.entry_password)))
|
getString(R.string.copy_field, getString(R.string.entry_password)))
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// If dialog not already shown
|
||||||
|
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
|
||||||
|
entryContentsView.assignPasswordCopyListener(v -> {
|
||||||
|
String message = getString(R.string.allow_copy_password_warning) +
|
||||||
|
"\n\n" +
|
||||||
|
getString(R.string.clipboard_warning);
|
||||||
|
AlertDialog warningDialog = new AlertDialog.Builder(EntryActivity.this)
|
||||||
|
.setMessage(message).create();
|
||||||
|
warningDialog.setButton(AlertDialog.BUTTON1, getText(android.R.string.ok),
|
||||||
|
(dialog, which) -> {
|
||||||
|
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, true);
|
||||||
|
dialog.dismiss();
|
||||||
|
fillData();
|
||||||
|
});
|
||||||
|
warningDialog.setButton(AlertDialog.BUTTON2, getText(android.R.string.cancel),
|
||||||
|
(dialog, which) -> {
|
||||||
|
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, false);
|
||||||
|
dialog.dismiss();
|
||||||
|
fillData();
|
||||||
|
});
|
||||||
|
warningDialog.show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
entryContentsView.assignPasswordCopyListener(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entryContentsView.assignURL(mEntry.getUrl());
|
entryContentsView.assignURL(mEntry.getUrl());
|
||||||
@@ -369,7 +396,7 @@ public class EntryActivity extends LockingHideActivity {
|
|||||||
entryContentsView.assignExpiresDate(getString(R.string.never));
|
entryContentsView.assignExpiresDate(getString(R.string.never));
|
||||||
}
|
}
|
||||||
|
|
||||||
mEntry.endToManageFieldReferences();
|
mEntry.stopToManageFieldReferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
|
|||||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||||
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
|
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
|
||||||
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
|
||||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||||
import com.kunzisoft.keepass.lock.LockingHideActivity;
|
import com.kunzisoft.keepass.lock.LockingHideActivity;
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
@@ -71,7 +70,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.UNDEFINED_ICON_ID;
|
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD;
|
||||||
|
|
||||||
public class EntryEditActivity extends LockingHideActivity
|
public class EntryEditActivity extends LockingHideActivity
|
||||||
implements IconPickerDialogFragment.IconPickerListener,
|
implements IconPickerDialogFragment.IconPickerListener,
|
||||||
@@ -89,10 +88,12 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
|
public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
|
||||||
public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
|
public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
|
||||||
|
|
||||||
|
private Database database;
|
||||||
|
|
||||||
protected PwEntry mEntry;
|
protected PwEntry mEntry;
|
||||||
protected PwEntry mCallbackNewEntry;
|
protected PwEntry mCallbackNewEntry;
|
||||||
protected boolean mIsNew;
|
protected boolean mIsNew;
|
||||||
protected int mSelectedIconID = UNDEFINED_ICON_ID;
|
protected PwIconStandard mSelectedIconStandard;
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
private ScrollView scrollView;
|
private ScrollView scrollView;
|
||||||
@@ -143,7 +144,6 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
setContentView(R.layout.entry_edit);
|
setContentView(R.layout.entry_edit);
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
toolbar.setTitle(getString(R.string.app_name));
|
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
assert getSupportActionBar() != null;
|
assert getSupportActionBar() != null;
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
@@ -162,8 +162,8 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
entryExtraFieldsContainer = findViewById(R.id.advanced_container);
|
entryExtraFieldsContainer = findViewById(R.id.advanced_container);
|
||||||
|
|
||||||
// Likely the app has been killed exit the activity
|
// Likely the app has been killed exit the activity
|
||||||
Database db = App.getDB();
|
database = App.getDB();
|
||||||
if ( ! db.getLoaded() ) {
|
if ( ! database.getLoaded() ) {
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -176,18 +176,16 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
||||||
iconColor = ta.getColor(0, Color.WHITE);
|
iconColor = ta.getColor(0, Color.WHITE);
|
||||||
|
|
||||||
PwDatabase pm = db.getPwDatabase();
|
mSelectedIconStandard = database.getPwDatabase().getIconFactory().getUnknownIcon();
|
||||||
|
|
||||||
|
PwDatabase pm = database.getPwDatabase();
|
||||||
if ( uuidBytes == null ) {
|
if ( uuidBytes == null ) {
|
||||||
PwGroupId parentId = (PwGroupId) intent.getSerializableExtra(KEY_PARENT);
|
PwGroupId parentId = intent.getParcelableExtra(KEY_PARENT);
|
||||||
PwGroup parent = pm.getGroupByGroupId(parentId);
|
PwGroup parent = pm.getGroupByGroupId(parentId);
|
||||||
mEntry = db.createEntry(parent);
|
mEntry = database.createEntry(parent);
|
||||||
mIsNew = true;
|
mIsNew = true;
|
||||||
// Add the default icon
|
// Add the default icon
|
||||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
database.getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, iconColor);
|
||||||
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, true, iconColor);
|
|
||||||
} else {
|
|
||||||
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
UUID uuid = Types.bytestoUUID(uuidBytes);
|
UUID uuid = Types.bytestoUUID(uuidBytes);
|
||||||
mEntry = pm.getEntryByUUIDId(uuid);
|
mEntry = pm.getEntryByUUIDId(uuid);
|
||||||
@@ -195,8 +193,12 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
fillData();
|
fillData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign title
|
||||||
|
setTitle((mIsNew) ? getString(R.string.add_entry) : getString(R.string.edit_entry));
|
||||||
|
|
||||||
// Retrieve the icon after an orientation change
|
// Retrieve the icon after an orientation change
|
||||||
if (savedInstanceState != null && savedInstanceState.containsKey(IconPickerDialogFragment.KEY_ICON_ID)) {
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(KEY_ICON_STANDARD)) {
|
||||||
iconPicked(savedInstanceState);
|
iconPicked(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,9 +261,9 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
EntryEditActivity act = EntryEditActivity.this;
|
EntryEditActivity act = EntryEditActivity.this;
|
||||||
RunnableOnFinish task;
|
RunnableOnFinish task;
|
||||||
if ( mIsNew ) {
|
if ( mIsNew ) {
|
||||||
task = new AddEntryRunnable(act, App.getDB(), mCallbackNewEntry, onFinish);
|
task = new AddEntryRunnable(act, database, mCallbackNewEntry, onFinish);
|
||||||
} else {
|
} else {
|
||||||
task = new UpdateEntryRunnable(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
|
task = new UpdateEntryRunnable(act, database, mEntry, mCallbackNewEntry, onFinish);
|
||||||
}
|
}
|
||||||
task.setUpdateProgressTaskStatus(
|
task.setUpdateProgressTaskStatus(
|
||||||
new UpdateProgressTaskStatus(this,
|
new UpdateProgressTaskStatus(this,
|
||||||
@@ -409,7 +411,7 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
newEntry.setLastModificationTime(new PwDate());
|
newEntry.setLastModificationTime(new PwDate());
|
||||||
|
|
||||||
newEntry.setTitle(entryTitleView.getText().toString());
|
newEntry.setTitle(entryTitleView.getText().toString());
|
||||||
newEntry.setIcon(retrieveIcon());
|
newEntry.setIconStandard(retrieveIcon());
|
||||||
|
|
||||||
newEntry.setUrl(entryUrlView.getText().toString());
|
newEntry.setUrl(entryUrlView.getText().toString());
|
||||||
newEntry.setUsername(entryUserNameView.getText().toString());
|
newEntry.setUsername(entryUserNameView.getText().toString());
|
||||||
@@ -429,21 +431,21 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newEntry.endToManageFieldReferences();
|
newEntry.stopToManageFieldReferences();
|
||||||
|
|
||||||
return newEntry;
|
return newEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
|
* Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private PwIconStandard retrieveIcon() {
|
private PwIconStandard retrieveIcon() {
|
||||||
if(mSelectedIconID != UNDEFINED_ICON_ID)
|
|
||||||
return App.getDB().getPwDatabase().getIconFactory().getIcon(mSelectedIconID);
|
if (!mSelectedIconStandard.isUnknown())
|
||||||
|
return mSelectedIconStandard;
|
||||||
else {
|
else {
|
||||||
if (mIsNew) {
|
if (mIsNew) {
|
||||||
return App.getDB().getPwDatabase().getIconFactory().getKeyIcon();
|
return database.getPwDatabase().getIconFactory().getKeyIcon();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Keep previous icon, if no new one was selected
|
// Keep previous icon, if no new one was selected
|
||||||
@@ -475,16 +477,21 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assignIconView() {
|
||||||
|
database.getDrawFactory()
|
||||||
|
.assignDatabaseIconTo(
|
||||||
|
this,
|
||||||
|
entryIconView,
|
||||||
|
mEntry.getIcon(),
|
||||||
|
iconColor);
|
||||||
|
}
|
||||||
|
|
||||||
protected void fillData() {
|
protected void fillData() {
|
||||||
|
|
||||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
assignIconView();
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon(), true, iconColor);
|
|
||||||
} else {
|
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't start the field reference manager, we want to see the raw ref
|
// Don't start the field reference manager, we want to see the raw ref
|
||||||
mEntry.endToManageFieldReferences();
|
mEntry.stopToManageFieldReferences();
|
||||||
|
|
||||||
entryTitleView.setText(mEntry.getTitle());
|
entryTitleView.setText(mEntry.getTitle());
|
||||||
entryUserNameView.setText(mEntry.getUsername());
|
entryUserNameView.setText(mEntry.getUsername());
|
||||||
@@ -515,14 +522,15 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void iconPicked(Bundle bundle) {
|
public void iconPicked(Bundle bundle) {
|
||||||
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD);
|
||||||
entryIconView.setImageResource(IconPackChooser.getSelectedIconPack(this).iconToResId(mSelectedIconID));
|
mEntry.setIconStandard(mSelectedIconStandard);
|
||||||
|
assignIconView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
if (mSelectedIconID != UNDEFINED_ICON_ID) {
|
if (!mSelectedIconStandard.isUnknown()) {
|
||||||
outState.putInt(IconPickerDialogFragment.KEY_ICON_ID, mSelectedIconID);
|
outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard);
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -548,7 +556,7 @@ public class EntryEditActivity extends LockingHideActivity
|
|||||||
if (mCallbackNewEntry != null) {
|
if (mCallbackNewEntry != null) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
Intent intentEntry = new Intent();
|
Intent intentEntry = new Intent();
|
||||||
bundle.putSerializable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
|
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
|
||||||
intentEntry.putExtras(bundle);
|
intentEntry.putExtras(bundle);
|
||||||
if (mIsNew) {
|
if (mIsNew) {
|
||||||
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
|
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.activities;
|
package com.kunzisoft.keepass.activities;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
@@ -29,24 +30,31 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.RequiresApi;
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v7.widget.SearchView;
|
import android.support.v7.widget.SearchView;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.getkeepsafe.taptargetview.TapTarget;
|
import com.getkeepsafe.taptargetview.TapTarget;
|
||||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
||||||
|
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter;
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper;
|
import com.kunzisoft.keepass.autofill.AutofillHelper;
|
||||||
import com.kunzisoft.keepass.database.Database;
|
import com.kunzisoft.keepass.database.Database;
|
||||||
@@ -54,9 +62,11 @@ import com.kunzisoft.keepass.database.PwDatabase;
|
|||||||
import com.kunzisoft.keepass.database.PwEntry;
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
import com.kunzisoft.keepass.database.PwGroup;
|
import com.kunzisoft.keepass.database.PwGroup;
|
||||||
import com.kunzisoft.keepass.database.PwGroupId;
|
import com.kunzisoft.keepass.database.PwGroupId;
|
||||||
|
import com.kunzisoft.keepass.database.PwGroupV4;
|
||||||
import com.kunzisoft.keepass.database.PwIcon;
|
import com.kunzisoft.keepass.database.PwIcon;
|
||||||
import com.kunzisoft.keepass.database.PwIconStandard;
|
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||||
import com.kunzisoft.keepass.database.PwNode;
|
import com.kunzisoft.keepass.database.PwNode;
|
||||||
|
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||||
import com.kunzisoft.keepass.database.action.node.AddGroupRunnable;
|
import com.kunzisoft.keepass.database.action.node.AddGroupRunnable;
|
||||||
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
|
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
|
||||||
import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable;
|
import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable;
|
||||||
@@ -69,108 +79,178 @@ import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
|
|||||||
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
|
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
|
||||||
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
|
||||||
import com.kunzisoft.keepass.dialogs.ReadOnlyDialog;
|
import com.kunzisoft.keepass.dialogs.ReadOnlyDialog;
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
||||||
|
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||||
|
import com.kunzisoft.keepass.password.AssignPasswordHelper;
|
||||||
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
||||||
import com.kunzisoft.keepass.search.SearchResultsActivity;
|
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
|
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
|
||||||
import com.kunzisoft.keepass.tasks.UIToastTask;
|
import com.kunzisoft.keepass.tasks.UIToastTask;
|
||||||
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
|
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
|
||||||
|
import com.kunzisoft.keepass.utils.MenuUtil;
|
||||||
import com.kunzisoft.keepass.view.AddNodeButtonView;
|
import com.kunzisoft.keepass.view.AddNodeButtonView;
|
||||||
|
|
||||||
import net.cachapa.expandablelayout.ExpandableLayout;
|
import net.cachapa.expandablelayout.ExpandableLayout;
|
||||||
|
|
||||||
public class GroupActivity extends ListNodesActivity
|
import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
|
||||||
|
|
||||||
|
public class GroupActivity extends LockingActivity
|
||||||
implements GroupEditDialogFragment.EditGroupListener,
|
implements GroupEditDialogFragment.EditGroupListener,
|
||||||
IconPickerDialogFragment.IconPickerListener,
|
IconPickerDialogFragment.IconPickerListener,
|
||||||
NodeAdapter.NodeMenuListener,
|
NodeAdapter.NodeMenuListener,
|
||||||
ListNodesFragment.OnScrollListener {
|
ListNodesFragment.OnScrollListener,
|
||||||
|
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||||
|
NodeAdapter.NodeClickCallback,
|
||||||
|
SortDialogFragment.SortSelectionListener {
|
||||||
|
|
||||||
private static final String TAG = GroupActivity.class.getName();
|
private static final String TAG = GroupActivity.class.getName();
|
||||||
|
|
||||||
private Toolbar toolbar;
|
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
||||||
|
private static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
|
||||||
|
private static final String SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG";
|
||||||
|
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
|
||||||
|
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY";
|
||||||
|
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
|
||||||
|
|
||||||
|
private Toolbar toolbar;
|
||||||
|
private View searchTitleView;
|
||||||
private ExpandableLayout toolbarPasteExpandableLayout;
|
private ExpandableLayout toolbarPasteExpandableLayout;
|
||||||
private Toolbar toolbarPaste;
|
private Toolbar toolbarPaste;
|
||||||
|
|
||||||
private ImageView iconView;
|
private ImageView iconView;
|
||||||
private AddNodeButtonView addNodeButtonView;
|
private AddNodeButtonView addNodeButtonView;
|
||||||
|
private TextView groupNameView;
|
||||||
|
|
||||||
protected boolean addGroupEnabled = false;
|
private Database database;
|
||||||
protected boolean addEntryEnabled = false;
|
|
||||||
protected boolean isRoot = false;
|
|
||||||
protected boolean readOnly = false;
|
|
||||||
|
|
||||||
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
|
private ListNodesFragment listNodesFragment;
|
||||||
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY";
|
private boolean currentGroupIsASearch;
|
||||||
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
|
|
||||||
|
private PwGroup rootGroup;
|
||||||
|
private PwGroup mCurrentGroup;
|
||||||
private PwGroup oldGroupToUpdate;
|
private PwGroup oldGroupToUpdate;
|
||||||
private PwNode nodeToCopy;
|
private PwNode nodeToCopy;
|
||||||
private PwNode nodeToMove;
|
private PwNode nodeToMove;
|
||||||
|
|
||||||
public static void launch(Activity act) {
|
private boolean entrySelectionMode;
|
||||||
|
private AutofillHelper autofillHelper;
|
||||||
|
|
||||||
|
private SearchEntryCursorAdapter searchSuggestionAdapter;
|
||||||
|
|
||||||
|
private int iconColor;
|
||||||
|
|
||||||
|
// After a database creation
|
||||||
|
public static void launch(Activity act) {
|
||||||
|
launch(act, READ_ONLY_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void launch(Activity act, boolean readOnly) {
|
||||||
startRecordTime(act);
|
startRecordTime(act);
|
||||||
launch(act, null);
|
launch(act, null, readOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void launch(Activity act, PwGroup group) {
|
private static void buildAndLaunchIntent(Activity activity, PwGroup group, boolean readOnly,
|
||||||
if (checkTimeIsAllowedOrFinish(act)) {
|
IntentBuildLauncher intentBuildLauncher) {
|
||||||
Intent intent = new Intent(act, GroupActivity.class);
|
if (checkTimeIsAllowedOrFinish(activity)) {
|
||||||
|
Intent intent = new Intent(activity, GroupActivity.class);
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
intent.putExtra(GROUP_ID_KEY, group.getId());
|
intent.putExtra(GROUP_ID_KEY, group.getId());
|
||||||
}
|
}
|
||||||
act.startActivityForResult(intent, 0);
|
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
|
||||||
|
intentBuildLauncher.startActivityForResult(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void launchForKeyboardResult(Activity act) {
|
public static void launch(Activity activity, PwGroup group, boolean readOnly) {
|
||||||
|
buildAndLaunchIntent(activity, group, readOnly,
|
||||||
|
(intent) -> activity.startActivityForResult(intent, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void launchForKeyboardResult(Activity act, boolean readOnly) {
|
||||||
startRecordTime(act);
|
startRecordTime(act);
|
||||||
launchForKeyboardResult(act, null);
|
launchForKeyboardResult(act, null, readOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void launchForKeyboardResult(Activity act, PwGroup group) {
|
public static void launchForKeyboardResult(Activity activity, PwGroup group, boolean readOnly) {
|
||||||
// TODO remove
|
// TODO implement pre search to directly open the direct group
|
||||||
if (checkTimeIsAllowedOrFinish(act)) {
|
buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
|
||||||
Intent intent = new Intent(act, GroupActivity.class);
|
|
||||||
if (group != null) {
|
|
||||||
intent.putExtra(GROUP_ID_KEY, group.getId());
|
|
||||||
}
|
|
||||||
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
|
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
|
||||||
act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
|
activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public static void launchForAutofillResult(Activity act, AssistStructure assistStructure) {
|
public static void launchForAutofillResult(Activity act, AssistStructure assistStructure, boolean readOnly) {
|
||||||
if ( assistStructure != null ) {
|
if ( assistStructure != null ) {
|
||||||
startRecordTime(act);
|
startRecordTime(act);
|
||||||
launchForAutofillResult(act, null, assistStructure);
|
launchForAutofillResult(act, null, assistStructure, readOnly);
|
||||||
} else {
|
} else {
|
||||||
launch(act);
|
launch(act, readOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public static void launchForAutofillResult(Activity act, PwGroup group, AssistStructure assistStructure) {
|
public static void launchForAutofillResult(Activity activity, PwGroup group, AssistStructure assistStructure, boolean readOnly) {
|
||||||
// TODO remove
|
// TODO implement pre search to directly open the direct group
|
||||||
if ( assistStructure != null ) {
|
if ( assistStructure != null ) {
|
||||||
if (checkTimeIsAllowedOrFinish(act)) {
|
buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
|
||||||
Intent intent = new Intent(act, GroupActivity.class);
|
|
||||||
if (group != null) {
|
|
||||||
intent.putExtra(GROUP_ID_KEY, group.getId());
|
|
||||||
}
|
|
||||||
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
||||||
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||||
}
|
});
|
||||||
} else {
|
} else {
|
||||||
launch(act, group);
|
launch(activity, group, readOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if ( isFinishing() ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
database = App.getDB();
|
||||||
|
// Likely the app has been killed exit the activity
|
||||||
|
if ( ! database.getLoaded() ) {
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct main view
|
||||||
|
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
|
||||||
|
|
||||||
|
// Initialize views
|
||||||
|
iconView = findViewById(R.id.icon);
|
||||||
|
addNodeButtonView = findViewById(R.id.add_node_button);
|
||||||
|
toolbar = findViewById(R.id.toolbar);
|
||||||
|
searchTitleView = findViewById(R.id.search_title);
|
||||||
|
groupNameView = findViewById(R.id.group_name);
|
||||||
|
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
|
||||||
|
toolbarPaste = findViewById(R.id.toolbar_paste);
|
||||||
|
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
|
||||||
|
// Get arg from intent or instance state
|
||||||
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
|
||||||
|
|
||||||
|
// Retrieve elements after an orientation change
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
|
||||||
|
oldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY);
|
||||||
|
|
||||||
|
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) {
|
||||||
|
nodeToCopy = savedInstanceState.getParcelable(NODE_TO_COPY_KEY);
|
||||||
|
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener());
|
||||||
|
}
|
||||||
|
else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
|
||||||
|
nodeToMove = savedInstanceState.getParcelable(NODE_TO_MOVE_KEY);
|
||||||
|
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootGroup = database.getPwDatabase().getRootGroup();
|
||||||
|
mCurrentGroup = retrieveCurrentGroup(getIntent(), savedInstanceState);
|
||||||
|
currentGroupIsASearch = Intent.ACTION_SEARCH.equals(getIntent().getAction());
|
||||||
|
|
||||||
Log.i(TAG, "Started creating tree");
|
Log.i(TAG, "Started creating tree");
|
||||||
if ( mCurrentGroup == null ) {
|
if ( mCurrentGroup == null ) {
|
||||||
@@ -178,23 +258,9 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct main view
|
|
||||||
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
|
|
||||||
|
|
||||||
attachFragmentToContentView();
|
|
||||||
|
|
||||||
iconView = findViewById(R.id.icon);
|
|
||||||
addNodeButtonView = findViewById(R.id.add_node_button);
|
|
||||||
addNodeButtonView.enableAddGroup(addGroupEnabled);
|
|
||||||
addNodeButtonView.enableAddEntry(addEntryEnabled);
|
|
||||||
|
|
||||||
toolbar = findViewById(R.id.toolbar);
|
|
||||||
toolbar.setTitle("");
|
toolbar.setTitle("");
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
groupNameView = findViewById(R.id.group_name);
|
|
||||||
|
|
||||||
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
|
|
||||||
toolbarPaste = findViewById(R.id.toolbar_paste);
|
|
||||||
toolbarPaste.inflateMenu(R.menu.node_paste_menu);
|
toolbarPaste.inflateMenu(R.menu.node_paste_menu);
|
||||||
toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
|
toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
|
||||||
toolbarPaste.setNavigationOnClickListener(view -> {
|
toolbarPaste.setNavigationOnClickListener(view -> {
|
||||||
@@ -203,104 +269,216 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
nodeToMove = null;
|
nodeToMove = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
// Retrieve the textColor to tint the icon
|
||||||
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
|
int[] attrs = {R.attr.textColorInverse};
|
||||||
oldGroupToUpdate = (PwGroup) savedInstanceState.getSerializable(OLD_GROUP_TO_UPDATE_KEY);
|
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
||||||
|
iconColor = ta.getColor(0, Color.WHITE);
|
||||||
|
|
||||||
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) {
|
String fragmentTag = LIST_NODES_FRAGMENT_TAG;
|
||||||
nodeToCopy = (PwNode) savedInstanceState.getSerializable(NODE_TO_COPY_KEY);
|
if (currentGroupIsASearch)
|
||||||
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener());
|
fragmentTag = SEARCH_FRAGMENT_TAG;
|
||||||
}
|
|
||||||
else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
|
|
||||||
nodeToMove = (PwNode) savedInstanceState.getSerializable(NODE_TO_MOVE_KEY);
|
|
||||||
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addNodeButtonView.setAddGroupClickListener(v -> {
|
// Initialize the fragment with the list
|
||||||
GroupEditDialogFragment.build()
|
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
|
||||||
.show(getSupportFragmentManager(),
|
.findFragmentByTag(fragmentTag);
|
||||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
if (listNodesFragment == null)
|
||||||
});
|
listNodesFragment = ListNodesFragment.newInstance(mCurrentGroup, readOnly, currentGroupIsASearch);
|
||||||
|
|
||||||
|
// Attach fragment to content view
|
||||||
|
getSupportFragmentManager().beginTransaction().replace(
|
||||||
|
R.id.nodes_list_fragment_container,
|
||||||
|
listNodesFragment,
|
||||||
|
fragmentTag)
|
||||||
|
.commit();
|
||||||
|
|
||||||
|
// Add listeners to the add buttons
|
||||||
|
addNodeButtonView.setAddGroupClickListener(v -> GroupEditDialogFragment.build()
|
||||||
|
.show(getSupportFragmentManager(),
|
||||||
|
GroupEditDialogFragment.TAG_CREATE_GROUP));
|
||||||
addNodeButtonView.setAddEntryClickListener(v ->
|
addNodeButtonView.setAddEntryClickListener(v ->
|
||||||
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
|
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
|
||||||
|
|
||||||
Log.i(TAG, "Finished creating tree");
|
// To init autofill
|
||||||
|
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
|
||||||
if (isRoot) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
showWarnings();
|
autofillHelper = new AutofillHelper();
|
||||||
|
autofillHelper.retrieveAssistStructure(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search suggestion
|
||||||
|
searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database);
|
||||||
|
|
||||||
|
Log.i(TAG, "Finished creating tree");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onNewIntent(Intent intent) {
|
||||||
outState.putSerializable(GROUP_ID_KEY, mCurrentGroup.getId());
|
setIntent(intent);
|
||||||
outState.putSerializable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
|
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||||
if (nodeToCopy != null)
|
// only one instance of search in backstack
|
||||||
outState.putSerializable(NODE_TO_COPY_KEY, nodeToCopy);
|
openSearchGroup(retrieveCurrentGroup(intent, null));
|
||||||
if (nodeToMove != null)
|
currentGroupIsASearch = true;
|
||||||
outState.putSerializable(NODE_TO_MOVE_KEY, nodeToMove);
|
} else {
|
||||||
super.onSaveInstanceState(outState);
|
currentGroupIsASearch = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
|
private void openSearchGroup(PwGroup group) {
|
||||||
|
// Delete the previous search fragment
|
||||||
PwGroupId pwGroupId = null; // TODO Parcelable
|
Fragment searchFragment = getSupportFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG);
|
||||||
if (savedInstanceState != null
|
if (searchFragment != null) {
|
||||||
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
|
if ( getSupportFragmentManager()
|
||||||
pwGroupId = (PwGroupId) savedInstanceState.getSerializable(GROUP_ID_KEY);
|
.popBackStackImmediate(SEARCH_FRAGMENT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) )
|
||||||
} else {
|
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
|
||||||
if (getIntent() != null)
|
|
||||||
pwGroupId = (PwGroupId) getIntent().getSerializableExtra(GROUP_ID_KEY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Database db = App.getDB();
|
openGroup(group, true);
|
||||||
readOnly = db.isReadOnly();
|
}
|
||||||
PwGroup root = db.getPwDatabase().getRootGroup();
|
|
||||||
|
|
||||||
Log.w(TAG, "Creating tree view");
|
private void openChildGroup(PwGroup group) {
|
||||||
PwGroup currentGroup;
|
openGroup(group, false);
|
||||||
if ( pwGroupId == null ) {
|
}
|
||||||
currentGroup = root;
|
|
||||||
} else {
|
private void openGroup(PwGroup group, boolean isASearch) {
|
||||||
currentGroup = db.getPwDatabase().getGroupByGroupId(pwGroupId);
|
// Check Timeout
|
||||||
|
if (checkTimeIsAllowedOrFinish(this)) {
|
||||||
|
startRecordTime(this);
|
||||||
|
|
||||||
|
// Open a group in a new fragment
|
||||||
|
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group, readOnly, isASearch);
|
||||||
|
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
// Different animation
|
||||||
|
String fragmentTag;
|
||||||
|
if (isASearch) {
|
||||||
|
fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_out_bottom,
|
||||||
|
R.anim.slide_in_bottom, R.anim.slide_out_top);
|
||||||
|
fragmentTag = SEARCH_FRAGMENT_TAG;
|
||||||
|
} else {
|
||||||
|
fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||||
|
R.anim.slide_in_left, R.anim.slide_out_right);
|
||||||
|
fragmentTag = LIST_NODES_FRAGMENT_TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragmentTransaction.replace(R.id.nodes_list_fragment_container,
|
||||||
|
newListNodeFragment,
|
||||||
|
fragmentTag);
|
||||||
|
fragmentTransaction.addToBackStack(fragmentTag);
|
||||||
|
fragmentTransaction.commit();
|
||||||
|
|
||||||
|
listNodesFragment = newListNodeFragment;
|
||||||
|
mCurrentGroup = group;
|
||||||
|
assignGroupViewElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentGroup != null) {
|
|
||||||
addGroupEnabled = !readOnly;
|
|
||||||
addEntryEnabled = !readOnly; // TODO consultation mode
|
|
||||||
isRoot = (currentGroup == root);
|
|
||||||
if (!currentGroup.allowAddEntryIfIsRoot())
|
|
||||||
addEntryEnabled = !isRoot && addEntryEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentGroup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void assignToolbarElements() {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.assignToolbarElements();
|
if (mCurrentGroup != null)
|
||||||
|
outState.putParcelable(GROUP_ID_KEY, mCurrentGroup.getId());
|
||||||
|
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
|
||||||
|
if (nodeToCopy != null)
|
||||||
|
outState.putParcelable(NODE_TO_COPY_KEY, nodeToCopy);
|
||||||
|
if (nodeToMove != null)
|
||||||
|
outState.putParcelable(NODE_TO_MOVE_KEY, nodeToMove);
|
||||||
|
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
// Assign the group icon depending of IconPack or custom icon
|
protected PwGroup retrieveCurrentGroup(Intent intent, @Nullable Bundle savedInstanceState) {
|
||||||
if ( mCurrentGroup != null ) {
|
|
||||||
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
|
// If it's a search
|
||||||
// Retrieve the textColor to tint the icon
|
if ( Intent.ACTION_SEARCH.equals(intent.getAction()) ) {
|
||||||
int[] attrs = {R.attr.textColorInverse};
|
return database.search(intent.getStringExtra(SearchManager.QUERY).trim());
|
||||||
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
|
}
|
||||||
int iconColor = ta.getColor(0, Color.WHITE);
|
// else a real group
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor);
|
else {
|
||||||
|
PwGroupId pwGroupId = null;
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
|
||||||
|
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY);
|
||||||
} else {
|
} else {
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon());
|
if (getIntent() != null)
|
||||||
|
pwGroupId = intent.getParcelableExtra(GROUP_ID_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolbar != null) {
|
readOnly = database.isReadOnly() || readOnly; // Force read only if the database is like that
|
||||||
if ( mCurrentGroup.containsParent() )
|
|
||||||
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
|
Log.w(TAG, "Creating tree view");
|
||||||
else {
|
PwGroup currentGroup;
|
||||||
toolbar.setNavigationIcon(null);
|
if (pwGroupId == null) {
|
||||||
|
currentGroup = rootGroup;
|
||||||
|
} else {
|
||||||
|
currentGroup = database.getPwDatabase().getGroupByGroupId(pwGroupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assignGroupViewElements() {
|
||||||
|
// Assign title
|
||||||
|
if (mCurrentGroup != null) {
|
||||||
|
String title = mCurrentGroup.getName();
|
||||||
|
if (title != null && title.length() > 0) {
|
||||||
|
if (groupNameView != null) {
|
||||||
|
groupNameView.setText(title);
|
||||||
|
groupNameView.invalidate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (groupNameView != null) {
|
||||||
|
groupNameView.setText(getText(R.string.root));
|
||||||
|
groupNameView.invalidate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (currentGroupIsASearch) {
|
||||||
|
searchTitleView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
searchTitleView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign icon
|
||||||
|
if (currentGroupIsASearch) {
|
||||||
|
if (toolbar != null) {
|
||||||
|
toolbar.setNavigationIcon(null);
|
||||||
|
}
|
||||||
|
iconView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
// Assign the group icon depending of IconPack or custom icon
|
||||||
|
iconView.setVisibility(View.VISIBLE);
|
||||||
|
if (mCurrentGroup != null) {
|
||||||
|
database.getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), iconColor);
|
||||||
|
|
||||||
|
if (toolbar != null) {
|
||||||
|
if (mCurrentGroup.containsParent())
|
||||||
|
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
|
||||||
|
else {
|
||||||
|
toolbar.setNavigationIcon(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show button if allowed
|
||||||
|
if (addNodeButtonView != null) {
|
||||||
|
|
||||||
|
// To enable add button
|
||||||
|
boolean addGroupEnabled = !readOnly && !currentGroupIsASearch;
|
||||||
|
boolean addEntryEnabled = !readOnly && !currentGroupIsASearch;
|
||||||
|
if (mCurrentGroup != null) {
|
||||||
|
boolean isRoot = (mCurrentGroup == rootGroup);
|
||||||
|
if (!mCurrentGroup.allowAddEntryIfIsRoot())
|
||||||
|
addEntryEnabled = !isRoot && addEntryEnabled;
|
||||||
|
if (isRoot) {
|
||||||
|
showWarnings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addNodeButtonView.enableAddGroup(addGroupEnabled);
|
||||||
|
addNodeButtonView.enableAddEntry(addEntryEnabled);
|
||||||
|
|
||||||
|
if (addNodeButtonView.isEnable())
|
||||||
|
addNodeButtonView.showButton();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -309,6 +487,50 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
addNodeButtonView.hideButtonOnScrollListener(dy);
|
addNodeButtonView.hideButtonOnScrollListener(dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNodeClick(PwNode node) {
|
||||||
|
|
||||||
|
// Add event when we have Autofill
|
||||||
|
AssistStructure assistStructure = null;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
assistStructure = autofillHelper.getAssistStructure();
|
||||||
|
if (assistStructure != null) {
|
||||||
|
switch (node.getType()) {
|
||||||
|
case GROUP:
|
||||||
|
openChildGroup((PwGroup) node);
|
||||||
|
break;
|
||||||
|
case ENTRY:
|
||||||
|
// Build response with the entry selected
|
||||||
|
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||||
|
finish();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( assistStructure == null ){
|
||||||
|
if (entrySelectionMode) {
|
||||||
|
switch (node.getType()) {
|
||||||
|
case GROUP:
|
||||||
|
openChildGroup((PwGroup) node);
|
||||||
|
break;
|
||||||
|
case ENTRY:
|
||||||
|
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||||
|
finish();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (node.getType()) {
|
||||||
|
case GROUP:
|
||||||
|
openChildGroup((PwGroup) node);
|
||||||
|
break;
|
||||||
|
case ENTRY:
|
||||||
|
EntryActivity.launch(this, (PwEntry) node, readOnly);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOpenMenuClick(PwNode node) {
|
public boolean onOpenMenuClick(PwNode node) {
|
||||||
onNodeClick(node);
|
onNodeClick(node);
|
||||||
@@ -320,7 +542,7 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
switch (node.getType()) {
|
switch (node.getType()) {
|
||||||
case GROUP:
|
case GROUP:
|
||||||
oldGroupToUpdate = (PwGroup) node;
|
oldGroupToUpdate = (PwGroup) node;
|
||||||
GroupEditDialogFragment.build(node)
|
GroupEditDialogFragment.build(oldGroupToUpdate)
|
||||||
.show(getSupportFragmentManager(),
|
.show(getSupportFragmentManager(),
|
||||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||||
break;
|
break;
|
||||||
@@ -481,9 +703,11 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
// Show button on resume
|
// Refresh the elements
|
||||||
if (addNodeButtonView != null)
|
assignGroupViewElements();
|
||||||
addNodeButtonView.showButton();
|
// Refresh suggestions to change preferences
|
||||||
|
if (searchSuggestionAdapter != null)
|
||||||
|
searchSuggestionAdapter.reInit(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -496,7 +720,8 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
// If no node, show education to add new one
|
// If no node, show education to add new one
|
||||||
if (listNodesFragment != null
|
if (listNodesFragment != null
|
||||||
&& listNodesFragment.isEmpty()) {
|
&& listNodesFragment.isEmpty()) {
|
||||||
if (!PreferencesUtil.isEducationNewNodePerformed(this)) {
|
if (!PreferencesUtil.isEducationNewNodePerformed(this)
|
||||||
|
&& addNodeButtonView.isEnable()) {
|
||||||
|
|
||||||
TapTargetView.showFor(this,
|
TapTargetView.showFor(this,
|
||||||
TapTarget.forView(findViewById(R.id.add_button),
|
TapTarget.forView(findViewById(R.id.add_button),
|
||||||
@@ -633,7 +858,8 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
|
|
||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.search, menu);
|
inflater.inflate(R.menu.search, menu);
|
||||||
inflater.inflate(R.menu.database_master_key, menu);
|
if (!readOnly)
|
||||||
|
inflater.inflate(R.menu.database_master_key, menu);
|
||||||
inflater.inflate(R.menu.database_lock, menu);
|
inflater.inflate(R.menu.database_lock, menu);
|
||||||
|
|
||||||
// Get the SearchView and set the searchable configuration
|
// Get the SearchView and set the searchable configuration
|
||||||
@@ -646,11 +872,26 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
searchView = (SearchView) searchItem.getActionView();
|
searchView = (SearchView) searchItem.getActionView();
|
||||||
}
|
}
|
||||||
if (searchView != null) {
|
if (searchView != null) {
|
||||||
// TODO Flickering when locking, will be better with content provider
|
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, GroupActivity.class)));
|
||||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
|
|
||||||
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
|
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
|
||||||
|
searchView.setSuggestionsAdapter(searchSuggestionAdapter);
|
||||||
|
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onSuggestionClick(int position) {
|
||||||
|
onNodeClick(searchSuggestionAdapter.getEntryFromPosition(position));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onSuggestionSelect(int position) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MenuUtil.contributionMenuInflater(inflater, menu);
|
||||||
|
inflater.inflate(R.menu.default_menu, menu);
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
|
|
||||||
// Launch education screen
|
// Launch education screen
|
||||||
@@ -667,16 +908,23 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||||
String query = intent.getStringExtra(SearchManager.QUERY);
|
String query = intent.getStringExtra(SearchManager.QUERY);
|
||||||
// manually launch the real search activity
|
// manually launch the real search activity
|
||||||
final Intent searchIntent = new Intent(getApplicationContext(), SearchResultsActivity.class);
|
final Intent searchIntent = new Intent(getApplicationContext(), GroupActivity.class);
|
||||||
// add query to the Intent Extras
|
// add query to the Intent Extras
|
||||||
searchIntent.setAction(Intent.ACTION_SEARCH);
|
searchIntent.setAction(Intent.ACTION_SEARCH);
|
||||||
searchIntent.putExtra(SearchManager.QUERY, query);
|
searchIntent.putExtra(SearchManager.QUERY, query);
|
||||||
|
|
||||||
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||||
&& autofillHelper.getAssistStructure() != null ) {
|
&& autofillHelper.getAssistStructure() != null ) {
|
||||||
AutofillHelper.addAssistStructureExtraInIntent(searchIntent, autofillHelper.getAssistStructure());
|
AutofillHelper.addAssistStructureExtraInIntent(searchIntent, autofillHelper.getAssistStructure());
|
||||||
startActivityForResult(searchIntent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
startActivityForResult(searchIntent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||||
customSearchQueryExecuted = true;
|
customSearchQueryExecuted = true;
|
||||||
}
|
}
|
||||||
|
// To get the keyboard response, verify if the current intent contains the EntrySelection key
|
||||||
|
else if (EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent())){
|
||||||
|
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(searchIntent);
|
||||||
|
startActivityForResult(searchIntent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
|
||||||
|
customSearchQueryExecuted = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!customSearchQueryExecuted) {
|
if (!customSearchQueryExecuted) {
|
||||||
@@ -693,7 +941,7 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case R.id.menu_search:
|
case R.id.menu_search:
|
||||||
onSearchRequested();
|
//onSearchRequested();
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case R.id.menu_lock:
|
case R.id.menu_lock:
|
||||||
@@ -703,8 +951,11 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
case R.id.menu_change_master_key:
|
case R.id.menu_change_master_key:
|
||||||
setPassword();
|
setPassword();
|
||||||
return true;
|
return true;
|
||||||
|
default:
|
||||||
|
// Check the time lock before launching settings
|
||||||
|
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPassword() {
|
private void setPassword() {
|
||||||
@@ -728,7 +979,7 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
try {
|
try {
|
||||||
iconStandard = (PwIconStandard) icon;
|
iconStandard = (PwIconStandard) icon;
|
||||||
} catch (Exception ignored) {} // TODO custom icon
|
} catch (Exception ignored) {} // TODO custom icon
|
||||||
newGroup.setIcon(iconStandard);
|
newGroup.setIconStandard(iconStandard);
|
||||||
|
|
||||||
// If group created save it in the database
|
// If group created save it in the database
|
||||||
AddGroupRunnable addGroupRunnable = new AddGroupRunnable(this,
|
AddGroupRunnable addGroupRunnable = new AddGroupRunnable(this,
|
||||||
@@ -750,8 +1001,11 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
updateGroup.setName(name);
|
updateGroup.setName(name);
|
||||||
try {
|
try {
|
||||||
iconStandard = (PwIconStandard) icon;
|
iconStandard = (PwIconStandard) icon;
|
||||||
} catch (Exception ignored) {} // TODO custom icon
|
updateGroup = ((PwGroupV4) oldGroupToUpdate).clone(); // TODO generalize
|
||||||
updateGroup.setIcon(iconStandard);
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} // TODO custom icon
|
||||||
|
updateGroup.setIconStandard(iconStandard);
|
||||||
|
|
||||||
if (listNodesFragment != null)
|
if (listNodesFragment != null)
|
||||||
listNodesFragment.removeNode(oldGroupToUpdate);
|
listNodesFragment.removeNode(oldGroupToUpdate);
|
||||||
@@ -776,6 +1030,7 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
|
|
||||||
class AfterAddNode extends AfterActionNodeOnFinish {
|
class AfterAddNode extends AfterActionNodeOnFinish {
|
||||||
|
|
||||||
|
@Override
|
||||||
public void run(PwNode oldNode, PwNode newNode) {
|
public void run(PwNode oldNode, PwNode newNode) {
|
||||||
super.run();
|
super.run();
|
||||||
|
|
||||||
@@ -794,6 +1049,7 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
|
|
||||||
class AfterUpdateNode extends AfterActionNodeOnFinish {
|
class AfterUpdateNode extends AfterActionNodeOnFinish {
|
||||||
|
|
||||||
|
@Override
|
||||||
public void run(PwNode oldNode, PwNode newNode) {
|
public void run(PwNode oldNode, PwNode newNode) {
|
||||||
super.run();
|
super.run();
|
||||||
|
|
||||||
@@ -879,16 +1135,79 @@ public class GroupActivity extends ListNodesActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void openGroup(PwGroup group) {
|
public void onAssignKeyDialogPositiveClick(
|
||||||
super.openGroup(group);
|
boolean masterPasswordChecked, String masterPassword,
|
||||||
if (addNodeButtonView != null)
|
boolean keyFileChecked, Uri keyFile) {
|
||||||
addNodeButtonView.showButton();
|
|
||||||
|
AssignPasswordHelper assignPasswordHelper =
|
||||||
|
new AssignPasswordHelper(this,
|
||||||
|
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
|
||||||
|
assignPasswordHelper.assignPasswordInDatabase(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAssignKeyDialogNegativeClick(
|
||||||
|
boolean masterPasswordChecked, String masterPassword,
|
||||||
|
boolean keyFileChecked, Uri keyFile) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
|
||||||
|
if (listNodesFragment != null)
|
||||||
|
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
|
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
|
@Override
|
||||||
|
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||||
|
/*
|
||||||
|
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
|
||||||
|
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
|
||||||
|
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
|
||||||
|
*/
|
||||||
|
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||||
|
int flags = intent.getFlags();
|
||||||
|
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
|
intent.setFlags(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||||
|
super.startActivityForResult(intent, requestCode, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeSearchInIntent(Intent intent) {
|
||||||
|
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||||
|
currentGroupIsASearch = false;
|
||||||
|
intent.setAction(Intent.ACTION_DEFAULT);
|
||||||
|
intent.removeExtra(SearchManager.QUERY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
super.onBackPressed();
|
if (checkTimeIsAllowedOrFinish(this)) {
|
||||||
if (addNodeButtonView != null)
|
startRecordTime(this);
|
||||||
addNodeButtonView.showButton();
|
|
||||||
|
super.onBackPressed();
|
||||||
|
|
||||||
|
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
||||||
|
// to refresh fragment
|
||||||
|
listNodesFragment.rebuildList();
|
||||||
|
mCurrentGroup = listNodesFragment.getMainGroup();
|
||||||
|
removeSearchInIntent(getIntent());
|
||||||
|
assignGroupViewElements();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.kunzisoft.keepass.activities;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
public interface IntentBuildLauncher {
|
||||||
|
void startActivityForResult(Intent intent);
|
||||||
|
}
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.activities;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.assist.AssistStructure;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
|
||||||
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
|
||||||
import com.kunzisoft.keepass.app.App;
|
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper;
|
|
||||||
import com.kunzisoft.keepass.database.PwEntry;
|
|
||||||
import com.kunzisoft.keepass.database.PwGroup;
|
|
||||||
import com.kunzisoft.keepass.database.PwNode;
|
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
|
||||||
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
|
|
||||||
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
|
||||||
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
|
||||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
|
||||||
import com.kunzisoft.keepass.password.AssignPasswordHelper;
|
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
|
||||||
|
|
||||||
public abstract class ListNodesActivity extends LockingActivity
|
|
||||||
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
|
||||||
NodeAdapter.NodeClickCallback,
|
|
||||||
SortDialogFragment.SortSelectionListener {
|
|
||||||
|
|
||||||
protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
|
||||||
|
|
||||||
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
|
|
||||||
protected ListNodesFragment listNodesFragment;
|
|
||||||
|
|
||||||
protected PwGroup mCurrentGroup;
|
|
||||||
protected TextView groupNameView;
|
|
||||||
|
|
||||||
protected boolean entrySelectionMode;
|
|
||||||
protected AutofillHelper autofillHelper;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if ( isFinishing() ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Likely the app has been killed exit the activity
|
|
||||||
if ( ! App.getDB().getLoaded() ) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidateOptionsMenu();
|
|
||||||
|
|
||||||
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
|
|
||||||
|
|
||||||
initializeListNodesFragment(mCurrentGroup);
|
|
||||||
|
|
||||||
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
autofillHelper = new AutofillHelper();
|
|
||||||
autofillHelper.retrieveAssistStructure(getIntent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
// Refresh the title
|
|
||||||
assignToolbarElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void initializeListNodesFragment(PwGroup currentGroup) {
|
|
||||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
|
|
||||||
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
|
||||||
if (listNodesFragment == null)
|
|
||||||
listNodesFragment = ListNodesFragment.newInstance(currentGroup.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach the fragment's list of node.
|
|
||||||
* <br />
|
|
||||||
* <strong>R.id.nodes_list_fragment_container</strong> must be the id of the container
|
|
||||||
*/
|
|
||||||
protected void attachFragmentToContentView() {
|
|
||||||
getSupportFragmentManager().beginTransaction().replace(
|
|
||||||
R.id.nodes_list_fragment_container,
|
|
||||||
listNodesFragment,
|
|
||||||
LIST_NODES_FRAGMENT_TAG)
|
|
||||||
.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void assignToolbarElements() {
|
|
||||||
if (mCurrentGroup != null) {
|
|
||||||
String title = mCurrentGroup.getName();
|
|
||||||
if (title != null && title.length() > 0) {
|
|
||||||
if (groupNameView != null) {
|
|
||||||
groupNameView.setText(title);
|
|
||||||
groupNameView.invalidate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (groupNameView != null) {
|
|
||||||
groupNameView.setText(getText(R.string.root));
|
|
||||||
groupNameView.invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
super.onCreateOptionsMenu(menu);
|
|
||||||
|
|
||||||
MenuInflater inflater = getMenuInflater();
|
|
||||||
MenuUtil.contributionMenuInflater(inflater, menu);
|
|
||||||
inflater.inflate(R.menu.default_menu, menu);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch ( item.getItemId() ) {
|
|
||||||
default:
|
|
||||||
// Check the time lock before launching settings
|
|
||||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNodeClick(PwNode node) {
|
|
||||||
|
|
||||||
// Add event when we have Autofill
|
|
||||||
AssistStructure assistStructure = null;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
assistStructure = autofillHelper.getAssistStructure();
|
|
||||||
if (assistStructure != null) {
|
|
||||||
switch (node.getType()) {
|
|
||||||
case GROUP:
|
|
||||||
openGroup((PwGroup) node);
|
|
||||||
break;
|
|
||||||
case ENTRY:
|
|
||||||
// Build response with the entry selected
|
|
||||||
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
|
||||||
finish();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( assistStructure == null ){
|
|
||||||
if (entrySelectionMode) {
|
|
||||||
switch (node.getType()) {
|
|
||||||
case GROUP:
|
|
||||||
openGroup((PwGroup) node);
|
|
||||||
break;
|
|
||||||
case ENTRY:
|
|
||||||
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
|
||||||
finish();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (node.getType()) {
|
|
||||||
case GROUP:
|
|
||||||
openGroup((PwGroup) node);
|
|
||||||
break;
|
|
||||||
case ENTRY:
|
|
||||||
EntryActivity.launch(this, (PwEntry) node);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void openGroup(PwGroup group) {
|
|
||||||
// Check Timeout
|
|
||||||
if (checkTimeIsAllowedOrFinish(this)) {
|
|
||||||
startRecordTime(this);
|
|
||||||
|
|
||||||
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group.getId());
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
|
||||||
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
|
||||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
|
||||||
.replace(R.id.nodes_list_fragment_container,
|
|
||||||
newListNodeFragment,
|
|
||||||
LIST_NODES_FRAGMENT_TAG)
|
|
||||||
.addToBackStack(LIST_NODES_FRAGMENT_TAG)
|
|
||||||
.commit();
|
|
||||||
listNodesFragment = newListNodeFragment;
|
|
||||||
mCurrentGroup = group;
|
|
||||||
assignToolbarElements();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAssignKeyDialogPositiveClick(
|
|
||||||
boolean masterPasswordChecked, String masterPassword,
|
|
||||||
boolean keyFileChecked, Uri keyFile) {
|
|
||||||
|
|
||||||
AssignPasswordHelper assignPasswordHelper =
|
|
||||||
new AssignPasswordHelper(this,
|
|
||||||
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
|
|
||||||
assignPasswordHelper.assignPasswordInDatabase(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAssignKeyDialogNegativeClick(
|
|
||||||
boolean masterPasswordChecked, String masterPassword,
|
|
||||||
boolean keyFileChecked, Uri keyFile) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
|
|
||||||
if (listNodesFragment != null)
|
|
||||||
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
|
||||||
|
|
||||||
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
@Override
|
|
||||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
|
||||||
/*
|
|
||||||
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
|
|
||||||
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
|
|
||||||
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
|
|
||||||
*/
|
|
||||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
|
||||||
int flags = intent.getFlags();
|
|
||||||
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
|
|
||||||
intent.setFlags(flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
|
||||||
super.startActivityForResult(intent, requestCode, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
if (checkTimeIsAllowedOrFinish(this)) {
|
|
||||||
startRecordTime(this);
|
|
||||||
|
|
||||||
super.onBackPressed();
|
|
||||||
|
|
||||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
|
||||||
// to refresh fragment
|
|
||||||
listNodesFragment.rebuildList();
|
|
||||||
mCurrentGroup = listNodesFragment.getMainGroup();
|
|
||||||
assignToolbarElements();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -20,10 +20,8 @@ import android.view.ViewGroup;
|
|||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
import com.kunzisoft.keepass.database.Database;
|
|
||||||
import com.kunzisoft.keepass.database.PwDatabase;
|
import com.kunzisoft.keepass.database.PwDatabase;
|
||||||
import com.kunzisoft.keepass.database.PwGroup;
|
import com.kunzisoft.keepass.database.PwGroup;
|
||||||
import com.kunzisoft.keepass.database.PwGroupId;
|
|
||||||
import com.kunzisoft.keepass.database.PwNode;
|
import com.kunzisoft.keepass.database.PwNode;
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||||
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
||||||
@@ -35,24 +33,32 @@ public class ListNodesFragment extends StylishFragment implements
|
|||||||
|
|
||||||
private static final String TAG = ListNodesFragment.class.getName();
|
private static final String TAG = ListNodesFragment.class.getName();
|
||||||
|
|
||||||
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
private static final String GROUP_KEY = "GROUP_KEY";
|
||||||
|
private static final String IS_SEARCH = "IS_SEARCH";
|
||||||
|
|
||||||
private NodeAdapter.NodeClickCallback nodeClickCallback;
|
private NodeAdapter.NodeClickCallback nodeClickCallback;
|
||||||
private NodeAdapter.NodeMenuListener nodeMenuListener;
|
private NodeAdapter.NodeMenuListener nodeMenuListener;
|
||||||
private OnScrollListener onScrollListener;
|
private OnScrollListener onScrollListener;
|
||||||
|
|
||||||
private RecyclerView listView;
|
private RecyclerView listView;
|
||||||
protected PwGroup mCurrentGroup;
|
private PwGroup currentGroup;
|
||||||
protected NodeAdapter mAdapter;
|
private NodeAdapter mAdapter;
|
||||||
|
|
||||||
|
private View notFoundView;
|
||||||
|
private boolean isASearchResult;
|
||||||
|
|
||||||
// Preferences for sorting
|
// Preferences for sorting
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
|
||||||
public static ListNodesFragment newInstance(PwGroupId groupId) {
|
private boolean readOnly;
|
||||||
Bundle bundle=new Bundle();
|
|
||||||
if (groupId != null) {
|
public static ListNodesFragment newInstance(PwGroup group, boolean readOnly, boolean isASearch) {
|
||||||
bundle.putSerializable(GROUP_ID_KEY, groupId);
|
Bundle bundle = new Bundle();
|
||||||
|
if (group != null) {
|
||||||
|
bundle.putParcelable(GROUP_KEY, group);
|
||||||
}
|
}
|
||||||
|
bundle.putBoolean(IS_SEARCH, isASearch);
|
||||||
|
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly);
|
||||||
ListNodesFragment listNodesFragment = new ListNodesFragment();
|
ListNodesFragment listNodesFragment = new ListNodesFragment();
|
||||||
listNodesFragment.setArguments(bundle);
|
listNodesFragment.setArguments(bundle);
|
||||||
return listNodesFragment;
|
return listNodesFragment;
|
||||||
@@ -90,42 +96,39 @@ public class ListNodesFragment extends StylishFragment implements
|
|||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setHasOptionsMenu(true);
|
if ( getActivity() != null ) {
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
|
||||||
|
|
||||||
|
if (getArguments() != null) {
|
||||||
|
// Contains all the group in element
|
||||||
|
if (getArguments().containsKey(GROUP_KEY)) {
|
||||||
|
currentGroup = getArguments().getParcelable(GROUP_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getArguments().containsKey(IS_SEARCH)) {
|
||||||
|
isASearchResult = getArguments().getBoolean(IS_SEARCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mCurrentGroup = initCurrentGroup();
|
|
||||||
if (getActivity() != null) {
|
|
||||||
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
|
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
|
||||||
|
mAdapter.setReadOnly(readOnly);
|
||||||
|
mAdapter.setIsASearchResult(isASearchResult);
|
||||||
mAdapter.setOnNodeClickListener(nodeClickCallback);
|
mAdapter.setOnNodeClickListener(nodeClickCallback);
|
||||||
|
|
||||||
if (nodeMenuListener != null) {
|
if (nodeMenuListener != null) {
|
||||||
mAdapter.setActivateContextMenu(true);
|
mAdapter.setActivateContextMenu(true);
|
||||||
mAdapter.setNodeMenuListener(nodeMenuListener);
|
mAdapter.setNodeMenuListener(nodeMenuListener);
|
||||||
}
|
}
|
||||||
|
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PwGroup initCurrentGroup() { // TODO Change by parcelable
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
Database db = App.getDB();
|
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
|
||||||
PwGroup root = db.getPwDatabase().getRootGroup();
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
PwGroup currentGroup = null;
|
|
||||||
if (getArguments() != null) {
|
|
||||||
// Contains only the group id, so the group must be retrieve
|
|
||||||
if (getArguments().containsKey(GROUP_ID_KEY)) {
|
|
||||||
PwGroupId pwGroupId = (PwGroupId) getArguments().getSerializable(GROUP_ID_KEY);
|
|
||||||
if ( pwGroupId != null )
|
|
||||||
currentGroup = db.getPwDatabase().getGroupByGroupId(pwGroupId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( currentGroup == null ) {
|
|
||||||
currentGroup = root;
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentGroup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -137,6 +140,7 @@ public class ListNodesFragment extends StylishFragment implements
|
|||||||
View rootView = inflater.cloneInContext(getContextThemed())
|
View rootView = inflater.cloneInContext(getContextThemed())
|
||||||
.inflate(R.layout.list_nodes_fragment, container, false);
|
.inflate(R.layout.list_nodes_fragment, container, false);
|
||||||
listView = rootView.findViewById(R.id.nodes_list);
|
listView = rootView.findViewById(R.id.nodes_list);
|
||||||
|
notFoundView = rootView.findViewById(R.id.not_found_container);
|
||||||
|
|
||||||
if (onScrollListener != null) {
|
if (onScrollListener != null) {
|
||||||
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
@@ -156,11 +160,21 @@ public class ListNodesFragment extends StylishFragment implements
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
rebuildList();
|
rebuildList();
|
||||||
|
|
||||||
|
if (isASearchResult && mAdapter.isEmpty()) {
|
||||||
|
// To show the " no search entry found "
|
||||||
|
listView.setVisibility(View.GONE);
|
||||||
|
notFoundView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
listView.setVisibility(View.VISIBLE);
|
||||||
|
notFoundView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void rebuildList() {
|
public void rebuildList() {
|
||||||
// Add elements to the list
|
// Add elements to the list
|
||||||
mAdapter.rebuildList(mCurrentGroup);
|
if (currentGroup != null)
|
||||||
|
mAdapter.rebuildList(currentGroup);
|
||||||
assignListToNodeAdapter(listView);
|
assignListToNodeAdapter(listView);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +196,7 @@ public class ListNodesFragment extends StylishFragment implements
|
|||||||
|
|
||||||
// Tell the adapter to refresh it's list
|
// Tell the adapter to refresh it's list
|
||||||
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
|
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
|
||||||
mAdapter.rebuildList(mCurrentGroup);
|
mAdapter.rebuildList(currentGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -234,13 +248,13 @@ public class ListNodesFragment extends StylishFragment implements
|
|||||||
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
|
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
|
||||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
|
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
|
||||||
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||||
PwNode newNode = (PwNode) data.getSerializableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
|
PwNode newNode = data.getParcelableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
|
||||||
if (newNode != null) {
|
if (newNode != null) {
|
||||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
||||||
mAdapter.addNode(newNode);
|
mAdapter.addNode(newNode);
|
||||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||||
//mAdapter.updateLastNodeRegister(newNode);
|
//mAdapter.updateLastNodeRegister(newNode);
|
||||||
mAdapter.rebuildList(mCurrentGroup);
|
mAdapter.rebuildList(currentGroup);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
|
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
|
||||||
@@ -267,7 +281,7 @@ public class ListNodesFragment extends StylishFragment implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PwGroup getMainGroup() {
|
public PwGroup getMainGroup() {
|
||||||
return mCurrentGroup;
|
return currentGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnScrollListener {
|
public interface OnScrollListener {
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.kunzisoft.keepass.activities;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
|
|
||||||
|
public class ReadOnlyHelper {
|
||||||
|
|
||||||
|
public static final String READ_ONLY_KEY = "READ_ONLY_KEY";
|
||||||
|
|
||||||
|
public static final boolean READ_ONLY_DEFAULT = false;
|
||||||
|
|
||||||
|
public static boolean retrieveReadOnlyFromInstanceStateOrPreference(Context context, Bundle savedInstanceState) {
|
||||||
|
boolean readOnly;
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||||
|
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
|
||||||
|
} else {
|
||||||
|
readOnly = PreferencesUtil.enableReadOnlyDatabase(context);
|
||||||
|
}
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean retrieveReadOnlyFromInstanceStateOrArguments(Bundle savedInstanceState, Bundle arguments) {
|
||||||
|
boolean readOnly = READ_ONLY_DEFAULT;
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||||
|
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
|
||||||
|
} else if (arguments != null
|
||||||
|
&& arguments.containsKey(READ_ONLY_KEY)) {
|
||||||
|
readOnly = arguments.getBoolean(READ_ONLY_KEY);
|
||||||
|
}
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean retrieveReadOnlyFromInstanceStateOrIntent(Bundle savedInstanceState, Intent intent) {
|
||||||
|
boolean readOnly = READ_ONLY_DEFAULT;
|
||||||
|
if (savedInstanceState != null
|
||||||
|
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||||
|
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
|
||||||
|
} else {
|
||||||
|
if (intent != null)
|
||||||
|
readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT);
|
||||||
|
}
|
||||||
|
return readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putReadOnlyInIntent(Intent intent, boolean readOnly) {
|
||||||
|
intent.putExtra(READ_ONLY_KEY, readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putReadOnlyInBundle(Bundle bundle, boolean readOnly) {
|
||||||
|
bundle.putBoolean(READ_ONLY_KEY, readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void onSaveInstanceState(Bundle outState, boolean readOnly) {
|
||||||
|
outState.putBoolean(READ_ONLY_KEY, readOnly);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ abstract class BasicViewHolder extends RecyclerView.ViewHolder {
|
|||||||
View container;
|
View container;
|
||||||
ImageView icon;
|
ImageView icon;
|
||||||
TextView text;
|
TextView text;
|
||||||
|
TextView subText;
|
||||||
|
|
||||||
BasicViewHolder(View itemView) {
|
BasicViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
|
|||||||
@@ -30,5 +30,6 @@ class EntryViewHolder extends BasicViewHolder {
|
|||||||
container = itemView.findViewById(R.id.entry_container);
|
container = itemView.findViewById(R.id.entry_container);
|
||||||
icon = itemView.findViewById(R.id.entry_icon);
|
icon = itemView.findViewById(R.id.entry_icon);
|
||||||
text = itemView.findViewById(R.id.entry_text);
|
text = itemView.findViewById(R.id.entry_text);
|
||||||
|
subText = itemView.findViewById(R.id.entry_subtext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,5 +30,6 @@ class GroupViewHolder extends BasicViewHolder {
|
|||||||
container = itemView.findViewById(R.id.group_container);
|
container = itemView.findViewById(R.id.group_container);
|
||||||
icon = itemView.findViewById(R.id.group_icon);
|
icon = itemView.findViewById(R.id.group_icon);
|
||||||
text = itemView.findViewById(R.id.group_text);
|
text = itemView.findViewById(R.id.group_text);
|
||||||
|
subText = itemView.findViewById(R.id.group_subtext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.support.v7.util.SortedList;
|
import android.support.v7.util.SortedList;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.support.v7.widget.util.SortedListAdapterCallback;
|
import android.support.v7.widget.util.SortedListAdapterCallback;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -33,16 +34,20 @@ import android.view.MenuInflater;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
|
import com.kunzisoft.keepass.database.Database;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
import com.kunzisoft.keepass.database.PwGroup;
|
import com.kunzisoft.keepass.database.PwGroup;
|
||||||
import com.kunzisoft.keepass.database.PwNode;
|
import com.kunzisoft.keepass.database.PwNode;
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
|
import com.kunzisoft.keepass.utils.Util;
|
||||||
|
|
||||||
public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||||
|
private static final String TAG = NodeAdapter.class.getName();
|
||||||
|
|
||||||
private SortedList<PwNode> nodeSortedList;
|
private SortedList<PwNode> nodeSortedList;
|
||||||
|
|
||||||
@@ -50,14 +55,20 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
|||||||
private LayoutInflater inflater;
|
private LayoutInflater inflater;
|
||||||
private MenuInflater menuInflater;
|
private MenuInflater menuInflater;
|
||||||
private float textSize;
|
private float textSize;
|
||||||
|
private float subtextSize;
|
||||||
private float iconSize;
|
private float iconSize;
|
||||||
private SortNodeEnum listSort;
|
private SortNodeEnum listSort;
|
||||||
private boolean groupsBeforeSort;
|
private boolean groupsBeforeSort;
|
||||||
private boolean ascendingSort;
|
private boolean ascendingSort;
|
||||||
|
private boolean showUsernames;
|
||||||
|
|
||||||
private NodeClickCallback nodeClickCallback;
|
private NodeClickCallback nodeClickCallback;
|
||||||
private NodeMenuListener nodeMenuListener;
|
private NodeMenuListener nodeMenuListener;
|
||||||
private boolean activateContextMenu;
|
private boolean activateContextMenu;
|
||||||
|
private boolean readOnly;
|
||||||
|
private boolean isASearchResult;
|
||||||
|
|
||||||
|
private Database database;
|
||||||
|
|
||||||
private int iconGroupColor;
|
private int iconGroupColor;
|
||||||
private int iconEntryColor;
|
private int iconEntryColor;
|
||||||
@@ -72,6 +83,8 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
assignPreferences();
|
assignPreferences();
|
||||||
this.activateContextMenu = false;
|
this.activateContextMenu = false;
|
||||||
|
this.readOnly = false;
|
||||||
|
this.isASearchResult = false;
|
||||||
|
|
||||||
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
|
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
|
||||||
@Override public int compare(PwNode item1, PwNode item2) {
|
@Override public int compare(PwNode item1, PwNode item2) {
|
||||||
@@ -87,6 +100,9 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Database
|
||||||
|
this.database = App.getDB();
|
||||||
|
|
||||||
// Retrieve the color to tint the icon
|
// Retrieve the color to tint the icon
|
||||||
int[] attrTextColorPrimary = {android.R.attr.textColorPrimary};
|
int[] attrTextColorPrimary = {android.R.attr.textColorPrimary};
|
||||||
TypedArray taTextColorPrimary = context.getTheme().obtainStyledAttributes(attrTextColorPrimary);
|
TypedArray taTextColorPrimary = context.getTheme().obtainStyledAttributes(attrTextColorPrimary);
|
||||||
@@ -98,22 +114,30 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
|||||||
taTextColor.recycle();
|
taTextColor.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setReadOnly(boolean readOnly) {
|
||||||
|
this.readOnly = readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsASearchResult(boolean isASearchResult) {
|
||||||
|
this.isASearchResult = isASearchResult;
|
||||||
|
}
|
||||||
|
|
||||||
public void setActivateContextMenu(boolean activate) {
|
public void setActivateContextMenu(boolean activate) {
|
||||||
this.activateContextMenu = activate;
|
this.activateContextMenu = activate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignPreferences() {
|
private void assignPreferences() {
|
||||||
|
float textSizeDefault = Util.getListTextDefaultSize(context);
|
||||||
this.textSize = PreferencesUtil.getListTextSize(context);
|
this.textSize = PreferencesUtil.getListTextSize(context);
|
||||||
|
this.subtextSize = context.getResources().getInteger(R.integer.list_small_size_default)
|
||||||
|
* textSize / textSizeDefault;
|
||||||
// Retrieve the icon size
|
// Retrieve the icon size
|
||||||
int iconDefaultSize = (int) TypedValue.applyDimension(
|
float iconDefaultSize = context.getResources().getDimension(R.dimen.list_icon_size_default);
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
this.iconSize = iconDefaultSize * textSize / textSizeDefault;
|
||||||
context.getResources().getInteger(R.integer.list_icon_size_default),
|
|
||||||
context.getResources().getDisplayMetrics()
|
|
||||||
);
|
|
||||||
this.iconSize = iconDefaultSize * textSize / Float.parseFloat(context.getString(R.string.list_size_default));
|
|
||||||
this.listSort = PreferencesUtil.getListSort(context);
|
this.listSort = PreferencesUtil.getListSort(context);
|
||||||
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
|
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
|
||||||
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
|
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
|
||||||
|
this.showUsernames = PreferencesUtil.showUsernamesListEntries(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,11 +146,23 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
|||||||
public void rebuildList(PwGroup group) {
|
public void rebuildList(PwGroup group) {
|
||||||
this.nodeSortedList.clear();
|
this.nodeSortedList.clear();
|
||||||
assignPreferences();
|
assignPreferences();
|
||||||
if (group != null) {
|
// TODO verify sort
|
||||||
|
try {
|
||||||
this.nodeSortedList.addAll(group.getDirectChildren());
|
this.nodeSortedList.addAll(group.getDirectChildren());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Can't add node elements to the list", e);
|
||||||
|
Toast.makeText(context, "Can't add node elements to the list : " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the adapter contains or not any element
|
||||||
|
* @return true if the list is empty
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return nodeSortedList.size() <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a node to the list
|
* Add a node to the list
|
||||||
* @param node Node to add
|
* @param node Node to add
|
||||||
@@ -188,35 +224,51 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
|||||||
public void onBindViewHolder(@NonNull BasicViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull BasicViewHolder holder, int position) {
|
||||||
PwNode subNode = nodeSortedList.get(position);
|
PwNode subNode = nodeSortedList.get(position);
|
||||||
// Assign image
|
// Assign image
|
||||||
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
|
int iconColor = Color.BLACK;
|
||||||
int iconColor = Color.BLACK;
|
switch (subNode.getType()) {
|
||||||
switch (subNode.getType()) {
|
case GROUP:
|
||||||
case GROUP:
|
iconColor = iconGroupColor;
|
||||||
iconColor = iconGroupColor;
|
break;
|
||||||
break;
|
case ENTRY:
|
||||||
case ENTRY:
|
iconColor = iconEntryColor;
|
||||||
iconColor = iconEntryColor;
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon(), true, iconColor);
|
|
||||||
} else {
|
|
||||||
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon());
|
|
||||||
}
|
}
|
||||||
|
database.getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon(), iconColor);
|
||||||
// Assign text
|
// Assign text
|
||||||
holder.text.setText(subNode.getDisplayTitle());
|
holder.text.setText(subNode.getTitle());
|
||||||
// Assign click
|
// Assign click
|
||||||
holder.container.setOnClickListener(
|
holder.container.setOnClickListener(
|
||||||
new OnNodeClickListener(subNode));
|
new OnNodeClickListener(subNode));
|
||||||
// Context menu
|
// Context menu
|
||||||
if (activateContextMenu) {
|
if (activateContextMenu) {
|
||||||
holder.container.setOnCreateContextMenuListener(
|
holder.container.setOnCreateContextMenuListener(
|
||||||
new ContextMenuBuilder(subNode, nodeMenuListener));
|
new ContextMenuBuilder(subNode, nodeMenuListener, readOnly));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add username
|
||||||
|
holder.subText.setText("");
|
||||||
|
holder.subText.setVisibility(View.GONE);
|
||||||
|
if (subNode.getType().equals(PwNode.Type.ENTRY)) {
|
||||||
|
PwEntry entry = (PwEntry) subNode;
|
||||||
|
entry.startToManageFieldReferences(database.getPwDatabase());
|
||||||
|
|
||||||
|
holder.text.setText(entry.getVisualTitle());
|
||||||
|
|
||||||
|
String username = entry.getUsername();
|
||||||
|
if (showUsernames && !username.isEmpty()) {
|
||||||
|
holder.subText.setVisibility(View.VISIBLE);
|
||||||
|
holder.subText.setText(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.stopToManageFieldReferences();
|
||||||
|
}
|
||||||
|
|
||||||
// Assign image and text size
|
// Assign image and text size
|
||||||
// Relative size of the icon
|
// Relative size of the icon
|
||||||
holder.icon.getLayoutParams().height = ((int) iconSize);
|
holder.icon.getLayoutParams().height = ((int) iconSize);
|
||||||
holder.icon.getLayoutParams().width = ((int) iconSize);
|
holder.icon.getLayoutParams().width = ((int) iconSize);
|
||||||
holder.text.setTextSize(textSize);
|
holder.text.setTextSize(textSize);
|
||||||
|
holder.subText.setTextSize(subtextSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -280,36 +332,56 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
|||||||
|
|
||||||
private PwNode node;
|
private PwNode node;
|
||||||
private NodeMenuListener menuListener;
|
private NodeMenuListener menuListener;
|
||||||
|
private boolean readOnly;
|
||||||
|
|
||||||
ContextMenuBuilder(PwNode node, NodeMenuListener menuListener) {
|
ContextMenuBuilder(PwNode node, NodeMenuListener menuListener, boolean readOnly) {
|
||||||
this.menuListener = menuListener;
|
this.menuListener = menuListener;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
this.readOnly = readOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
|
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
|
||||||
menuInflater.inflate(R.menu.node_menu, contextMenu);
|
menuInflater.inflate(R.menu.node_menu, contextMenu);
|
||||||
|
|
||||||
// TODO COPY For Group
|
// Opening
|
||||||
if (node.getType().equals(PwNode.Type.GROUP)) {
|
|
||||||
contextMenu.removeItem(R.id.menu_copy);
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem menuItem = contextMenu.findItem(R.id.menu_open);
|
MenuItem menuItem = contextMenu.findItem(R.id.menu_open);
|
||||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||||
if (!App.getDB().isReadOnly() && !node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
|
||||||
// Edition
|
// Edition
|
||||||
|
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
||||||
|
contextMenu.removeItem(R.id.menu_edit);
|
||||||
|
} else {
|
||||||
menuItem = contextMenu.findItem(R.id.menu_edit);
|
menuItem = contextMenu.findItem(R.id.menu_edit);
|
||||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||||
// Copy (not for group)
|
}
|
||||||
if (node.getType().equals(PwNode.Type.ENTRY)) {
|
|
||||||
menuItem = contextMenu.findItem(R.id.menu_copy);
|
// Copy (not for group)
|
||||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
if (readOnly
|
||||||
}
|
|| isASearchResult
|
||||||
// Move
|
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())
|
||||||
|
|| node.getType().equals(PwNode.Type.GROUP)) {
|
||||||
|
// TODO COPY For Group
|
||||||
|
contextMenu.removeItem(R.id.menu_copy);
|
||||||
|
} else {
|
||||||
|
menuItem = contextMenu.findItem(R.id.menu_copy);
|
||||||
|
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move
|
||||||
|
if (readOnly
|
||||||
|
|| isASearchResult
|
||||||
|
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
||||||
|
contextMenu.removeItem(R.id.menu_move);
|
||||||
|
} else {
|
||||||
menuItem = contextMenu.findItem(R.id.menu_move);
|
menuItem = contextMenu.findItem(R.id.menu_move);
|
||||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||||
// Deletion
|
}
|
||||||
|
|
||||||
|
// Deletion
|
||||||
|
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
|
||||||
|
contextMenu.removeItem(R.id.menu_delete);
|
||||||
|
} else {
|
||||||
menuItem = contextMenu.findItem(R.id.menu_delete);
|
menuItem = contextMenu.findItem(R.id.menu_delete);
|
||||||
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||||
|
*
|
||||||
|
* This file is part of KeePass DX.
|
||||||
|
*
|
||||||
|
* KeePass DX is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePass DX is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.kunzisoft.keepass.adapters;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.support.v4.widget.CursorAdapter;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.R;
|
||||||
|
import com.kunzisoft.keepass.database.Database;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
|
import com.kunzisoft.keepass.database.PwIcon;
|
||||||
|
import com.kunzisoft.keepass.database.PwIconFactory;
|
||||||
|
import com.kunzisoft.keepass.database.cursor.EntryCursor;
|
||||||
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SearchEntryCursorAdapter extends CursorAdapter {
|
||||||
|
|
||||||
|
private LayoutInflater cursorInflater;
|
||||||
|
private Database database;
|
||||||
|
private boolean displayUsername;
|
||||||
|
private int iconColor;
|
||||||
|
|
||||||
|
public SearchEntryCursorAdapter(Context context, Database database) {
|
||||||
|
super(context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
|
||||||
|
cursorInflater = (LayoutInflater) context.getSystemService(
|
||||||
|
Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
this.database = database;
|
||||||
|
|
||||||
|
// Get the icon color
|
||||||
|
int[] attrTextColor = {R.attr.textColorInverse};
|
||||||
|
TypedArray taTextColor = context.getTheme().obtainStyledAttributes(attrTextColor);
|
||||||
|
this.iconColor = taTextColor.getColor(0, Color.WHITE);
|
||||||
|
taTextColor.recycle();
|
||||||
|
|
||||||
|
reInit(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reInit(Context context) {
|
||||||
|
this.displayUsername = PreferencesUtil.showUsernamesListEntries(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||||
|
|
||||||
|
View view = cursorInflater.inflate(R.layout.search_entry, parent ,false);
|
||||||
|
ViewHolder viewHolder = new ViewHolder();
|
||||||
|
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon);
|
||||||
|
viewHolder.textViewTitle = view.findViewById(R.id.entry_text);
|
||||||
|
viewHolder.textViewSubTitle = view.findViewById(R.id.entry_subtext);
|
||||||
|
view.setTag(viewHolder);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
|
|
||||||
|
// Retrieve elements from cursor
|
||||||
|
UUID uuid = new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
|
||||||
|
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)));
|
||||||
|
PwIconFactory iconFactory = database.getPwDatabase().getIconFactory();
|
||||||
|
PwIcon icon = iconFactory.getIcon(
|
||||||
|
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||||
|
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||||
|
if (icon.isUnknown()) {
|
||||||
|
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
|
||||||
|
if (icon.isUnknown())
|
||||||
|
icon = iconFactory.getKeyIcon();
|
||||||
|
}
|
||||||
|
String title = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE) );
|
||||||
|
String username = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME) );
|
||||||
|
String url = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL) );
|
||||||
|
|
||||||
|
ViewHolder viewHolder = (ViewHolder) view.getTag();
|
||||||
|
|
||||||
|
// Assign image
|
||||||
|
database.getDrawFactory().assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon, iconColor);
|
||||||
|
|
||||||
|
// Assign title
|
||||||
|
String showTitle = PwEntry.getVisualTitle(false, title, username, url, uuid);
|
||||||
|
viewHolder.textViewTitle.setText(showTitle);
|
||||||
|
if (displayUsername && !username.isEmpty()) {
|
||||||
|
viewHolder.textViewSubTitle.setText(String.format("(%s)", username));
|
||||||
|
} else {
|
||||||
|
viewHolder.textViewSubTitle.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ViewHolder {
|
||||||
|
ImageView imageViewIcon;
|
||||||
|
TextView textViewTitle;
|
||||||
|
TextView textViewSubTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
|
||||||
|
return database.searchEntry(constraint.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PwEntry getEntryFromPosition(int position) {
|
||||||
|
PwEntry pwEntry = null;
|
||||||
|
|
||||||
|
Cursor cursor = this.getCursor();
|
||||||
|
if (cursor.moveToFirst()
|
||||||
|
&&
|
||||||
|
cursor.move(position)) {
|
||||||
|
|
||||||
|
pwEntry = database.createEntry();
|
||||||
|
database.populateEntry(pwEntry, (EntryCursor) cursor);
|
||||||
|
}
|
||||||
|
return pwEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -92,7 +92,7 @@ public class App extends MultiDexApplication {
|
|||||||
@Override
|
@Override
|
||||||
public void onTerminate() {
|
public void onTerminate() {
|
||||||
if ( db != null ) {
|
if ( db != null ) {
|
||||||
db.clear();
|
db.clear(getApplicationContext());
|
||||||
}
|
}
|
||||||
super.onTerminate();
|
super.onTerminate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,20 +19,57 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.utils.MemUtil;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class AutoType implements Cloneable, Serializable {
|
public class AutoType implements Cloneable, Parcelable {
|
||||||
private static final long OBF_OPT_NONE = 0;
|
private static final long OBF_OPT_NONE = 0;
|
||||||
|
|
||||||
public boolean enabled = true;
|
public boolean enabled = true;
|
||||||
public long obfuscationOptions = OBF_OPT_NONE;
|
public long obfuscationOptions = OBF_OPT_NONE;
|
||||||
public String defaultSequence = "";
|
public String defaultSequence = "";
|
||||||
|
|
||||||
private HashMap<String, String> windowSeqPairs = new HashMap<>();
|
private HashMap<String, String> windowSeqPairs = new HashMap<>();
|
||||||
|
|
||||||
|
public AutoType() {}
|
||||||
|
|
||||||
|
public AutoType(Parcel in) {
|
||||||
|
enabled = in.readByte() != 0;
|
||||||
|
obfuscationOptions = in.readLong();
|
||||||
|
defaultSequence = in.readString();
|
||||||
|
windowSeqPairs = MemUtil.readStringParcelableMap(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeByte((byte) (enabled ? 1 : 0));
|
||||||
|
dest.writeLong(obfuscationOptions);
|
||||||
|
dest.writeString(defaultSequence);
|
||||||
|
MemUtil.writeStringParcelableMap(dest, windowSeqPairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<AutoType> CREATOR = new Parcelable.Creator<AutoType>() {
|
||||||
|
@Override
|
||||||
|
public AutoType createFromParcel(Parcel in) {
|
||||||
|
return new AutoType(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AutoType[] newArray(int size) {
|
||||||
|
return new AutoType[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public AutoType clone() {
|
public AutoType clone() {
|
||||||
AutoType auto;
|
AutoType auto;
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ public class BinaryPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
for (Entry<Integer, ProtectedBinary> entry: pool.entrySet())
|
||||||
|
entry.getValue().clear();
|
||||||
pool.clear();
|
pool.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,30 +65,36 @@ public class BinaryPool {
|
|||||||
@Override
|
@Override
|
||||||
public boolean operate(PwEntryV4 entry) {
|
public boolean operate(PwEntryV4 entry) {
|
||||||
for (PwEntryV4 histEntry : entry.getHistory()) {
|
for (PwEntryV4 histEntry : entry.getHistory()) {
|
||||||
poolAdd(histEntry.getBinaries());
|
add(histEntry.getBinaries());
|
||||||
}
|
}
|
||||||
poolAdd(entry.getBinaries());
|
add(entry.getBinaries());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void poolAdd(Map<String, ProtectedBinary> dict) {
|
private void add(Map<String, ProtectedBinary> dict) {
|
||||||
for (ProtectedBinary pb : dict.values()) {
|
for (ProtectedBinary pb : dict.values()) {
|
||||||
poolAdd(pb);
|
add(pb);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void poolAdd(ProtectedBinary pb) {
|
public void add(ProtectedBinary pb) {
|
||||||
assert(pb != null);
|
assert(pb != null);
|
||||||
|
if (findKey(pb) != -1) return;
|
||||||
|
|
||||||
if (poolFind(pb) != -1) return;
|
pool.put(findUnusedKey(), pb);
|
||||||
|
|
||||||
pool.put(pool.size(), pb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int findUnusedKey() {
|
||||||
|
int unusedKey = pool.size();
|
||||||
|
while(get(unusedKey) != null)
|
||||||
|
unusedKey++;
|
||||||
|
return unusedKey;
|
||||||
|
}
|
||||||
|
|
||||||
public int poolFind(ProtectedBinary pb) {
|
public int findKey(ProtectedBinary pb) {
|
||||||
for (Entry<Integer, ProtectedBinary> pair : pool.entrySet()) {
|
for (Entry<Integer, ProtectedBinary> pair : pool.entrySet()) {
|
||||||
if (pair.getValue().equals(pb)) return pair.getKey();
|
if (pair.getValue().equals(pb)) return pair.getKey();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,22 +21,26 @@ package com.kunzisoft.keepass.database;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
|
||||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
|
||||||
|
import com.kunzisoft.keepass.database.cursor.EntryCursor;
|
||||||
import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException;
|
import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException;
|
||||||
import com.kunzisoft.keepass.database.exception.InvalidDBException;
|
import com.kunzisoft.keepass.database.exception.InvalidDBException;
|
||||||
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
|
||||||
import com.kunzisoft.keepass.database.load.Importer;
|
import com.kunzisoft.keepass.database.load.Importer;
|
||||||
import com.kunzisoft.keepass.database.load.ImporterFactory;
|
import com.kunzisoft.keepass.database.load.ImporterFactory;
|
||||||
import com.kunzisoft.keepass.database.save.PwDbOutput;
|
import com.kunzisoft.keepass.database.save.PwDbOutput;
|
||||||
|
import com.kunzisoft.keepass.database.search.SearchDbHelper;
|
||||||
import com.kunzisoft.keepass.icons.IconDrawableFactory;
|
import com.kunzisoft.keepass.icons.IconDrawableFactory;
|
||||||
import com.kunzisoft.keepass.search.SearchDbHelper;
|
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
|
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
|
||||||
import com.kunzisoft.keepass.utils.UriUtil;
|
import com.kunzisoft.keepass.utils.UriUtil;
|
||||||
|
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@@ -56,7 +60,7 @@ public class Database {
|
|||||||
|
|
||||||
private static final String TAG = Database.class.getName();
|
private static final String TAG = Database.class.getName();
|
||||||
|
|
||||||
private PwDatabase pm;
|
private PwDatabase pwDatabase;
|
||||||
private Uri mUri;
|
private Uri mUri;
|
||||||
private SearchDbHelper searchHelper;
|
private SearchDbHelper searchHelper;
|
||||||
private boolean readOnly = false;
|
private boolean readOnly = false;
|
||||||
@@ -67,11 +71,11 @@ public class Database {
|
|||||||
private boolean loaded = false;
|
private boolean loaded = false;
|
||||||
|
|
||||||
public PwDatabase getPwDatabase() {
|
public PwDatabase getPwDatabase() {
|
||||||
return pm;
|
return pwDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPwDatabase(PwDatabase pm) {
|
public void setPwDatabase(PwDatabase pm) {
|
||||||
this.pm = pm;
|
this.pwDatabase = pm;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUri(Uri mUri) {
|
public void setUri(Uri mUri) {
|
||||||
@@ -149,24 +153,25 @@ public class Database {
|
|||||||
// We'll end up reading 8 bytes to identify the header. Might as well use two extra.
|
// We'll end up reading 8 bytes to identify the header. Might as well use two extra.
|
||||||
bis.mark(10);
|
bis.mark(10);
|
||||||
|
|
||||||
Importer databaseImporter = ImporterFactory.createImporter(bis, debug);
|
// Get the file directory to save the attachments
|
||||||
|
Importer databaseImporter = ImporterFactory.createImporter(bis, ctx.getFilesDir(), debug);
|
||||||
|
|
||||||
bis.reset(); // Return to the start
|
bis.reset(); // Return to the start
|
||||||
|
|
||||||
pm = databaseImporter.openDatabase(bis, password, keyFileInputStream, progressTaskUpdater);
|
pwDatabase = databaseImporter.openDatabase(bis, password, keyFileInputStream, progressTaskUpdater);
|
||||||
if ( pm != null ) {
|
if ( pwDatabase != null ) {
|
||||||
try {
|
try {
|
||||||
switch (pm.getVersion()) {
|
switch (pwDatabase.getVersion()) {
|
||||||
case V3:
|
case V3:
|
||||||
PwGroupV3 rootV3 = ((PwDatabaseV3) pm).getRootGroup();
|
PwGroupV3 rootV3 = ((PwDatabaseV3) pwDatabase).getRootGroup();
|
||||||
((PwDatabaseV3) pm).populateGlobals(rootV3);
|
((PwDatabaseV3) pwDatabase).populateGlobals(rootV3);
|
||||||
passwordEncodingError = !pm.validatePasswordEncoding(password);
|
passwordEncodingError = !pwDatabase.validatePasswordEncoding(password);
|
||||||
searchHelper = new SearchDbHelper.SearchDbHelperV3(ctx);
|
searchHelper = new SearchDbHelper.SearchDbHelperV3(ctx);
|
||||||
break;
|
break;
|
||||||
case V4:
|
case V4:
|
||||||
PwGroupV4 rootV4 = ((PwDatabaseV4) pm).getRootGroup();
|
PwGroupV4 rootV4 = ((PwDatabaseV4) pwDatabase).getRootGroup();
|
||||||
((PwDatabaseV4) pm).populateGlobals(rootV4);
|
((PwDatabaseV4) pwDatabase).populateGlobals(rootV4);
|
||||||
passwordEncodingError = !pm.validatePasswordEncoding(password);
|
passwordEncodingError = !pwDatabase.validatePasswordEncoding(password);
|
||||||
searchHelper = new SearchDbHelper.SearchDbHelperV4(ctx);
|
searchHelper = new SearchDbHelper.SearchDbHelperV4(ctx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -179,13 +184,17 @@ public class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PwGroup search(String str) {
|
public PwGroup search(String str) {
|
||||||
|
return search(str, Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PwGroup search(String str, int max) {
|
||||||
if (searchHelper == null) { return null; }
|
if (searchHelper == null) { return null; }
|
||||||
try {
|
try {
|
||||||
switch (pm.getVersion()) {
|
switch (pwDatabase.getVersion()) {
|
||||||
case V3:
|
case V3:
|
||||||
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str);
|
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pwDatabase), str, max);
|
||||||
case V4:
|
case V4:
|
||||||
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str);
|
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pwDatabase), str, max);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Search can't be performed with this SearchHelper", e);
|
Log.e(TAG, "Search can't be performed with this SearchHelper", e);
|
||||||
@@ -193,6 +202,54 @@ public class Database {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Cursor searchEntry(String query) {
|
||||||
|
final EntryCursor cursor = new EntryCursor();
|
||||||
|
|
||||||
|
// TODO real content provider
|
||||||
|
if (!query.isEmpty()) {
|
||||||
|
PwGroup searchResult = search(query, 6);
|
||||||
|
PwVersion version = getPwDatabase().getVersion();
|
||||||
|
if (searchResult != null) {
|
||||||
|
for (int i = 0; i < searchResult.numbersOfChildEntries(); i++) {
|
||||||
|
PwEntry entry = searchResult.getChildEntryAt(i);
|
||||||
|
if (!entry.isMetaStream()) { // TODO metastream
|
||||||
|
try {
|
||||||
|
switch (version) {
|
||||||
|
case V3:
|
||||||
|
cursor.addEntry((PwEntryV3) entry);
|
||||||
|
continue;
|
||||||
|
case V4:
|
||||||
|
cursor.addEntry((PwEntryV4) entry);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Can't add PwEntry to the cursor", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateEntry(PwEntry pwEntry, EntryCursor cursor) {
|
||||||
|
PwIconFactory iconFactory = getPwDatabase().getIconFactory();
|
||||||
|
try {
|
||||||
|
switch (getPwDatabase().getVersion()) {
|
||||||
|
case V3:
|
||||||
|
cursor.populateEntry((PwEntryV3) pwEntry, iconFactory);
|
||||||
|
break;
|
||||||
|
case V4:
|
||||||
|
// TODO invert field reference manager
|
||||||
|
pwEntry.startToManageFieldReferences(getPwDatabase());
|
||||||
|
cursor.populateEntry((PwEntryV4) pwEntry, iconFactory);
|
||||||
|
pwEntry.stopToManageFieldReferences();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "This version of PwGroup can't be populated", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void saveData(Context ctx) throws IOException, PwDbOutputException {
|
public void saveData(Context ctx) throws IOException, PwDbOutputException {
|
||||||
saveData(ctx, mUri);
|
saveData(ctx, mUri);
|
||||||
}
|
}
|
||||||
@@ -207,7 +264,7 @@ public class Database {
|
|||||||
FileOutputStream fos = null;
|
FileOutputStream fos = null;
|
||||||
try {
|
try {
|
||||||
fos = new FileOutputStream(tempFile);
|
fos = new FileOutputStream(tempFile);
|
||||||
PwDbOutput pmo = PwDbOutput.getInstance(pm, fos);
|
PwDbOutput pmo = PwDbOutput.getInstance(pwDatabase, fos);
|
||||||
if (pmo != null)
|
if (pmo != null)
|
||||||
pmo.output();
|
pmo.output();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -235,7 +292,7 @@ public class Database {
|
|||||||
OutputStream os = null;
|
OutputStream os = null;
|
||||||
try {
|
try {
|
||||||
os = ctx.getContentResolver().openOutputStream(uri);
|
os = ctx.getContentResolver().openOutputStream(uri);
|
||||||
PwDbOutput pmo = PwDbOutput.getInstance(pm, os);
|
PwDbOutput pmo = PwDbOutput.getInstance(pwDatabase, os);
|
||||||
if (pmo != null)
|
if (pmo != null)
|
||||||
pmo.output();
|
pmo.output();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -250,10 +307,19 @@ public class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO Clear database when lock broadcast is receive in backstage
|
// TODO Clear database when lock broadcast is receive in backstage
|
||||||
public void clear() {
|
public void clear(Context context) {
|
||||||
drawFactory.clearCache();
|
drawFactory.clearCache();
|
||||||
|
// Delete the cache of the database if present
|
||||||
|
if (pwDatabase != null)
|
||||||
|
pwDatabase.clearCache();
|
||||||
|
// In all cases, delete all the files in the temp dir
|
||||||
|
try {
|
||||||
|
FileUtils.cleanDirectory(context.getFilesDir());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Unable to clear the directory cache.", e);
|
||||||
|
}
|
||||||
|
|
||||||
pm = null;
|
pwDatabase = null;
|
||||||
mUri = null;
|
mUri = null;
|
||||||
loaded = false;
|
loaded = false;
|
||||||
passwordEncodingError = false;
|
passwordEncodingError = false;
|
||||||
@@ -464,19 +530,22 @@ public class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwEntry createEntry(PwGroup parent) {
|
public PwEntry createEntry() {
|
||||||
PwEntry newPwEntry = null;
|
return createEntry(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PwEntry createEntry(@Nullable PwGroup parent) {
|
||||||
try {
|
try {
|
||||||
switch (getPwDatabase().getVersion()) {
|
switch (getPwDatabase().getVersion()) {
|
||||||
case V3:
|
case V3:
|
||||||
newPwEntry = new PwEntryV3((PwGroupV3) parent);
|
return new PwEntryV3((PwGroupV3) parent);
|
||||||
case V4:
|
case V4:
|
||||||
newPwEntry = new PwEntryV4((PwGroupV4) parent);
|
return new PwEntryV4((PwGroupV4) parent);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "This version of PwEntry can't be created", e);
|
Log.e(TAG, "This version of PwEntry can't be created", e);
|
||||||
}
|
}
|
||||||
return newPwEntry;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwGroup createGroup(PwGroup parent) {
|
public PwGroup createGroup(PwGroup parent) {
|
||||||
@@ -488,7 +557,7 @@ public class Database {
|
|||||||
case V4:
|
case V4:
|
||||||
newPwGroup = new PwGroupV4((PwGroupV4) parent);
|
newPwGroup = new PwGroupV4((PwGroupV4) parent);
|
||||||
}
|
}
|
||||||
newPwGroup.setId(pm.newGroupId());
|
newPwGroup.setId(pwDatabase.newGroupId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "This version of PwGroup can't be created", e);
|
Log.e(TAG, "This version of PwGroup can't be created", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||||
|
import com.kunzisoft.keepass.utils.MemUtil;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -32,7 +35,7 @@ import static com.kunzisoft.keepass.database.PwEntryV4.STR_TITLE;
|
|||||||
import static com.kunzisoft.keepass.database.PwEntryV4.STR_URL;
|
import static com.kunzisoft.keepass.database.PwEntryV4.STR_URL;
|
||||||
import static com.kunzisoft.keepass.database.PwEntryV4.STR_USERNAME;
|
import static com.kunzisoft.keepass.database.PwEntryV4.STR_USERNAME;
|
||||||
|
|
||||||
public class ExtraFields implements Serializable, Cloneable {
|
public class ExtraFields implements Parcelable, Cloneable {
|
||||||
|
|
||||||
private Map<String, ProtectedString> fields;
|
private Map<String, ProtectedString> fields;
|
||||||
|
|
||||||
@@ -40,6 +43,32 @@ public class ExtraFields implements Serializable, Cloneable {
|
|||||||
fields = new HashMap<>();
|
fields = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ExtraFields(Parcel in) {
|
||||||
|
fields = MemUtil.readStringParcelableMap(in, ProtectedString.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
MemUtil.writeStringParcelableMap(dest, flags, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<ExtraFields> CREATOR = new Parcelable.Creator<ExtraFields>() {
|
||||||
|
@Override
|
||||||
|
public ExtraFields createFromParcel(Parcel in) {
|
||||||
|
return new ExtraFields(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExtraFields[] newArray(int size) {
|
||||||
|
return new ExtraFields[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public boolean containsCustomFields() {
|
public boolean containsCustomFields() {
|
||||||
return !getCustomProtectedFields().keySet().isEmpty();
|
return !getCustomProtectedFields().keySet().isEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database;
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
|
import com.kunzisoft.keepass.database.exception.InvalidKeyFileException;
|
||||||
import com.kunzisoft.keepass.database.exception.KeyFileEmptyException;
|
import com.kunzisoft.keepass.database.exception.KeyFileEmptyException;
|
||||||
import com.kunzisoft.keepass.utils.Util;
|
import com.kunzisoft.keepass.utils.MemUtil;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -35,7 +35,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB, PwEntryDB>,
|
public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwEntryDB>,
|
||||||
PwEntryDB extends PwEntry<PwGroupDB>> {
|
PwEntryDB extends PwEntry<PwGroupDB>> {
|
||||||
|
|
||||||
public static final UUID UUID_ZERO = new UUID(0,0);
|
public static final UUID UUID_ZERO = new UUID(0,0);
|
||||||
@@ -128,7 +128,7 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
|
|||||||
assert(keyInputStream != null);
|
assert(keyInputStream != null);
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
Util.copyStream(keyInputStream, bos);
|
MemUtil.copyStream(keyInputStream, bos);
|
||||||
byte[] keyData = bos.toByteArray();
|
byte[] keyData = bos.toByteArray();
|
||||||
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(keyData);
|
ByteArrayInputStream bis = new ByteArrayInputStream(keyData);
|
||||||
@@ -430,4 +430,6 @@ public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB,
|
|||||||
*/
|
*/
|
||||||
public abstract void initNew(String dbPath);
|
public abstract void initNew(String dbPath);
|
||||||
|
|
||||||
|
public abstract void clearCache();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
|||||||
PwGroupV3 group = createGroup();
|
PwGroupV3 group = createGroup();
|
||||||
group.setId(newGroupId());
|
group.setId(newGroupId());
|
||||||
group.setName(name);
|
group.setName(name);
|
||||||
group.setIcon(iconFactory.getIcon(iconId));
|
group.setIconStandard(iconFactory.getIcon(iconId));
|
||||||
addGroupTo(group, parent);
|
addGroupTo(group, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
|||||||
*/
|
*/
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
PwEntryV3 ent = entries.get(i);
|
PwEntryV3 ent = entries.get(i);
|
||||||
if (ent.getGroupId() == parent.getGroupId())
|
if (ent.getParent().getGroupId() == parent.getGroupId())
|
||||||
kids.add(ent);
|
kids.add(ent);
|
||||||
}
|
}
|
||||||
return kids;
|
return kids;
|
||||||
@@ -376,4 +376,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
|
|||||||
|
|
||||||
return !(omitBackup && isBackup(group));
|
return !(omitBackup && isBackup(group));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCache() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -754,4 +754,8 @@ public class PwDatabaseV4 extends PwDatabase<PwGroupV4, PwEntryV4> {
|
|||||||
return filename.substring(0, lastExtDot);
|
return filename.substring(0, lastExtDot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCache() {
|
||||||
|
binPool.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -19,10 +19,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
import com.kunzisoft.keepass.utils.Types;
|
import com.kunzisoft.keepass.utils.Types;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -33,15 +35,14 @@ import java.util.Date;
|
|||||||
* @author bpellin
|
* @author bpellin
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class PwDate implements Cloneable, Serializable {
|
public class PwDate implements Cloneable, Parcelable {
|
||||||
|
|
||||||
private static final int DATE_SIZE = 5;
|
private static final int DATE_SIZE = 5;
|
||||||
|
|
||||||
private boolean cDateBuilt = false;
|
private Date jDate;
|
||||||
private boolean jDateBuilt = false;
|
private boolean jDateBuilt = false;
|
||||||
|
transient private byte[] cDate;
|
||||||
private Date jDate;
|
transient private boolean cDateBuilt = false;
|
||||||
private byte[] cDate;
|
|
||||||
|
|
||||||
public static final Date NEVER_EXPIRE = getNeverExpire();
|
public static final Date NEVER_EXPIRE = getNeverExpire();
|
||||||
public static final Date DEFAULT_DATE = getDefaultDate();
|
public static final Date DEFAULT_DATE = getDefaultDate();
|
||||||
@@ -93,6 +94,35 @@ public class PwDate implements Cloneable, Serializable {
|
|||||||
jDate = new Date();
|
jDate = new Date();
|
||||||
jDateBuilt = true;
|
jDateBuilt = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PwDate(Parcel in) {
|
||||||
|
jDate = (Date) in.readSerializable();
|
||||||
|
jDateBuilt = in.readByte() != 0;
|
||||||
|
cDateBuilt = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeSerializable(getDate());
|
||||||
|
dest.writeByte((byte) (jDateBuilt ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwDate> CREATOR = new Creator<PwDate>() {
|
||||||
|
@Override
|
||||||
|
public PwDate createFromParcel(Parcel in) {
|
||||||
|
return new PwDate(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwDate[] newArray(int size) {
|
||||||
|
return new PwDate[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwDate clone() {
|
public PwDate clone() {
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
|
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||||
|
|
||||||
@@ -30,6 +32,19 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
|
|||||||
|
|
||||||
protected UUID uuid = PwDatabase.UUID_ZERO;
|
protected UUID uuid = PwDatabase.UUID_ZERO;
|
||||||
|
|
||||||
|
public PwEntry() {}
|
||||||
|
|
||||||
|
public PwEntry(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
uuid = (UUID) in.readSerializable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeSerializable(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void construct(Parent parent) {
|
protected void construct(Parent parent) {
|
||||||
super.construct(parent);
|
super.construct(parent);
|
||||||
@@ -61,7 +76,7 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void startToManageFieldReferences(PwDatabase db) {}
|
public void startToManageFieldReferences(PwDatabase db) {}
|
||||||
public void endToManageFieldReferences() {}
|
public void stopToManageFieldReferences() {}
|
||||||
|
|
||||||
public abstract String getTitle();
|
public abstract String getTitle();
|
||||||
public abstract void setTitle(String title);
|
public abstract void setTitle(String title);
|
||||||
@@ -82,15 +97,31 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
|
|||||||
return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0);
|
return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String getDisplayTitle() {
|
* {@inheritDoc}
|
||||||
if ( isTan() ) {
|
* Get the display title from an entry, <br />
|
||||||
return PMS_TAN_ENTRY + " " + getUsername();
|
* {@link #startToManageFieldReferences(PwDatabase)} and {@link #stopToManageFieldReferences()} must be called
|
||||||
} else {
|
* before and after {@link #getVisualTitle()}
|
||||||
return getTitle();
|
*/
|
||||||
}
|
public String getVisualTitle() {
|
||||||
|
// only used to compare, don't car if it's a reference
|
||||||
|
return getVisualTitle(isTan(), getTitle(), getUsername(), getUrl(), getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getVisualTitle(boolean isTAN, String title, String username, String url, UUID uuid) {
|
||||||
|
if ( isTAN ) {
|
||||||
|
return PMS_TAN_ENTRY + " " + username;
|
||||||
|
} else {
|
||||||
|
if (title.isEmpty())
|
||||||
|
if (url.isEmpty())
|
||||||
|
return uuid.toString();
|
||||||
|
else
|
||||||
|
return url;
|
||||||
|
else
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO encapsulate extra fields
|
// TODO encapsulate extra fields
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|||||||
|
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -75,15 +77,11 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
|
|||||||
private static final String PMS_ID_USER = "SYSTEM";
|
private static final String PMS_ID_USER = "SYSTEM";
|
||||||
private static final String PMS_ID_URL = "$";
|
private static final String PMS_ID_URL = "$";
|
||||||
|
|
||||||
// TODO Parent ID to remove
|
|
||||||
private int groupId;
|
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
private String username;
|
private String username;
|
||||||
private byte[] password;
|
private byte[] password;
|
||||||
private String url;
|
private String url;
|
||||||
private String additional;
|
private String additional;
|
||||||
|
|
||||||
/** A string describing what is in pBinaryData */
|
/** A string describing what is in pBinaryData */
|
||||||
private String binaryDesc;
|
private String binaryDesc;
|
||||||
private byte[] binaryData;
|
private byte[] binaryData;
|
||||||
@@ -94,12 +92,45 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
|
|||||||
|
|
||||||
public PwEntryV3(PwGroupV3 p) {
|
public PwEntryV3(PwGroupV3 p) {
|
||||||
construct(p);
|
construct(p);
|
||||||
groupId = ((PwGroupIdV3) this.parent.getId()).getId(); // TODO remove
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PwEntryV3(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
title = in.readString();
|
||||||
|
username = in.readString();
|
||||||
|
in.readByteArray(password);
|
||||||
|
url = in.readString();
|
||||||
|
additional = in.readString();
|
||||||
|
binaryDesc = in.readString();
|
||||||
|
in.readByteArray(binaryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeString(title);
|
||||||
|
dest.writeString(username);
|
||||||
|
dest.writeByteArray(password);
|
||||||
|
dest.writeString(url);
|
||||||
|
dest.writeString(additional);
|
||||||
|
dest.writeString(binaryDesc);
|
||||||
|
dest.writeByteArray(binaryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwEntryV3> CREATOR = new Creator<PwEntryV3>() {
|
||||||
|
@Override
|
||||||
|
public PwEntryV3 createFromParcel(Parcel in) {
|
||||||
|
return new PwEntryV3(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwEntryV3[] newArray(int size) {
|
||||||
|
return new PwEntryV3[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected void updateWith(PwEntryV3 source) {
|
protected void updateWith(PwEntryV3 source) {
|
||||||
super.assign(source);
|
super.assign(source);
|
||||||
groupId = source.groupId;
|
|
||||||
|
|
||||||
title = source.title;
|
title = source.title;
|
||||||
username = source.username;
|
username = source.username;
|
||||||
@@ -145,12 +176,9 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
|
|||||||
return newEntry;
|
return newEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getGroupId() {
|
|
||||||
return groupId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGroupId(int groupId) {
|
public void setGroupId(int groupId) {
|
||||||
this.groupId = groupId;
|
this.parent = new PwGroupV3();
|
||||||
|
this.parent.setGroupId(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,8 +19,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedBinary;
|
import com.kunzisoft.keepass.database.security.ProtectedBinary;
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||||
|
import com.kunzisoft.keepass.utils.MemUtil;
|
||||||
import com.kunzisoft.keepass.utils.SprEngineV4;
|
import com.kunzisoft.keepass.utils.SprEngineV4;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -37,7 +40,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
public static final String STR_URL = "URL";
|
public static final String STR_URL = "URL";
|
||||||
public static final String STR_NOTES = "Notes";
|
public static final String STR_NOTES = "Notes";
|
||||||
|
|
||||||
// To decode each field not serializable
|
// To decode each field not parcelable
|
||||||
private transient PwDatabaseV4 mDatabase = null;
|
private transient PwDatabaseV4 mDatabase = null;
|
||||||
private transient boolean mDecodeRef = false;
|
private transient boolean mDecodeRef = false;
|
||||||
|
|
||||||
@@ -45,7 +48,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
private long usageCount = 0;
|
private long usageCount = 0;
|
||||||
private PwDate parentGroupLastMod = new PwDate();
|
private PwDate parentGroupLastMod = new PwDate();
|
||||||
private Map<String, String> customData = new HashMap<>();
|
private Map<String, String> customData = new HashMap<>();
|
||||||
|
|
||||||
private ExtraFields fields = new ExtraFields();
|
private ExtraFields fields = new ExtraFields();
|
||||||
private HashMap<String, ProtectedBinary> binaries = new HashMap<>();
|
private HashMap<String, ProtectedBinary> binaries = new HashMap<>();
|
||||||
private String foregroundColor = "";
|
private String foregroundColor = "";
|
||||||
@@ -53,7 +55,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
private String overrideURL = "";
|
private String overrideURL = "";
|
||||||
private AutoType autoType = new AutoType();
|
private AutoType autoType = new AutoType();
|
||||||
private ArrayList<PwEntryV4> history = new ArrayList<>();
|
private ArrayList<PwEntryV4> history = new ArrayList<>();
|
||||||
|
|
||||||
private String url = "";
|
private String url = "";
|
||||||
private String additional = "";
|
private String additional = "";
|
||||||
private String tags = "";
|
private String tags = "";
|
||||||
@@ -71,8 +72,8 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
customIcon = source.customIcon;
|
customIcon = source.customIcon;
|
||||||
usageCount = source.usageCount;
|
usageCount = source.usageCount;
|
||||||
parentGroupLastMod = source.parentGroupLastMod;
|
parentGroupLastMod = source.parentGroupLastMod;
|
||||||
// TODO customData
|
customData.clear();
|
||||||
|
customData.putAll(source.customData); // Add all custom elements in map
|
||||||
fields = source.fields;
|
fields = source.fields;
|
||||||
binaries = source.binaries;
|
binaries = source.binaries;
|
||||||
foregroundColor = source.foregroundColor;
|
foregroundColor = source.foregroundColor;
|
||||||
@@ -80,12 +81,60 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
overrideURL = source.overrideURL;
|
overrideURL = source.overrideURL;
|
||||||
autoType = source.autoType;
|
autoType = source.autoType;
|
||||||
history = source.history;
|
history = source.history;
|
||||||
|
|
||||||
url = source.url;
|
url = source.url;
|
||||||
additional = source.additional;
|
additional = source.additional;
|
||||||
tags = source.tags;
|
tags = source.tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PwEntryV4(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
customIcon = in.readParcelable(PwIconCustom.class.getClassLoader());
|
||||||
|
usageCount = in.readLong();
|
||||||
|
parentGroupLastMod = in.readParcelable(PwDate.class.getClassLoader());
|
||||||
|
customData = MemUtil.readStringParcelableMap(in);
|
||||||
|
fields = in.readParcelable(ExtraFields.class.getClassLoader());
|
||||||
|
binaries = MemUtil.readStringParcelableMap(in, ProtectedBinary.class);
|
||||||
|
foregroundColor = in.readString();
|
||||||
|
backgroupColor = in.readString();
|
||||||
|
overrideURL = in.readString();
|
||||||
|
autoType = in.readParcelable(AutoType.class.getClassLoader());
|
||||||
|
history = in.readArrayList(PwEntryV4.class.getClassLoader()); // TODO verify
|
||||||
|
url = in.readString();
|
||||||
|
additional = in.readString();
|
||||||
|
tags = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeParcelable(customIcon, flags);
|
||||||
|
dest.writeLong(usageCount);
|
||||||
|
dest.writeParcelable(parentGroupLastMod, flags);
|
||||||
|
MemUtil.writeStringParcelableMap(dest, customData);
|
||||||
|
dest.writeParcelable(fields, flags);
|
||||||
|
// TODO MemUtil.writeStringParcelableMap(dest, flags, binaries);
|
||||||
|
dest.writeString(foregroundColor);
|
||||||
|
dest.writeString(backgroupColor);
|
||||||
|
dest.writeString(overrideURL);
|
||||||
|
dest.writeParcelable(autoType, flags);
|
||||||
|
dest.writeList(history);
|
||||||
|
dest.writeString(url);
|
||||||
|
dest.writeString(additional);
|
||||||
|
dest.writeString(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwEntryV4> CREATOR = new Creator<PwEntryV4>() {
|
||||||
|
@Override
|
||||||
|
public PwEntryV4 createFromParcel(Parcel in) {
|
||||||
|
return new PwEntryV4(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwEntryV4[] newArray(int size) {
|
||||||
|
return new PwEntryV4[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public PwEntryV4 clone() {
|
public PwEntryV4 clone() {
|
||||||
@@ -120,7 +169,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endToManageFieldReferences() {
|
public void stopToManageFieldReferences() {
|
||||||
this.mDatabase = null;
|
this.mDatabase = null;
|
||||||
this.mDecodeRef = false;
|
this.mDecodeRef = false;
|
||||||
}
|
}
|
||||||
@@ -156,41 +205,31 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setTitle(String title) {
|
public void setTitle(String title) {
|
||||||
PwDatabaseV4 db = mDatabase;
|
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectTitle;
|
||||||
boolean protect = db.getMemoryProtection().protectTitle;
|
|
||||||
|
|
||||||
setProtectedString(STR_TITLE, title, protect);
|
setProtectedString(STR_TITLE, title, protect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUsername(String user) {
|
public void setUsername(String user) {
|
||||||
PwDatabaseV4 db = mDatabase;
|
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUserName;
|
||||||
boolean protect = db.getMemoryProtection().protectUserName;
|
|
||||||
|
|
||||||
setProtectedString(STR_USERNAME, user, protect);
|
setProtectedString(STR_USERNAME, user, protect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPassword(String pass) {
|
public void setPassword(String pass) {
|
||||||
PwDatabaseV4 db = mDatabase;
|
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectPassword;
|
||||||
boolean protect = db.getMemoryProtection().protectPassword;
|
|
||||||
|
|
||||||
setProtectedString(STR_PASSWORD, pass, protect);
|
setProtectedString(STR_PASSWORD, pass, protect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUrl(String url) {
|
public void setUrl(String url) {
|
||||||
PwDatabaseV4 db = mDatabase;
|
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUrl;
|
||||||
boolean protect = db.getMemoryProtection().protectUrl;
|
|
||||||
|
|
||||||
setProtectedString(STR_URL, url, protect);
|
setProtectedString(STR_URL, url, protect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNotes(String notes) {
|
public void setNotes(String notes) {
|
||||||
PwDatabaseV4 db = mDatabase;
|
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectNotes;
|
||||||
boolean protect = db.getMemoryProtection().protectNotes;
|
|
||||||
|
|
||||||
setProtectedString(STR_NOTES, notes, protect);
|
setProtectedString(STR_NOTES, notes, protect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,14 +241,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
fields.putProtectedString(key, value, protect);
|
fields.putProtectedString(key, value, protect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwIconCustom getCustomIcon() {
|
|
||||||
return customIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomIcon(PwIconCustom icon) {
|
|
||||||
this.customIcon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PwDate getLocationChanged() {
|
public PwDate getLocationChanged() {
|
||||||
return parentGroupLastMod;
|
return parentGroupLastMod;
|
||||||
}
|
}
|
||||||
@@ -236,15 +267,28 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
|
|||||||
return decodeRefKey(mDecodeRef, STR_URL);
|
return decodeRefKey(mDecodeRef, STR_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwIcon getIcon() {
|
public PwIcon getIcon() {
|
||||||
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
|
if (customIcon == null || customIcon.isUnknown()) {
|
||||||
return super.getIcon();
|
return super.getIcon();
|
||||||
} else {
|
} else {
|
||||||
return customIcon;
|
return customIcon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIconCustom(PwIconCustom icon) {
|
||||||
|
this.customIcon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PwIconCustom getIconCustom() {
|
||||||
|
return customIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconStandard(PwIconStandard icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.customIcon = PwIconCustom.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean allowExtraFields() {
|
public boolean allowExtraFields() {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -19,16 +19,34 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup, ChildEntry extends PwEntry>
|
public abstract class PwGroup<GroupG extends PwGroup, EntryE extends PwEntry>
|
||||||
extends PwNode<Parent> {
|
extends PwNode<GroupG> {
|
||||||
|
|
||||||
protected String name = "";
|
protected String name = "";
|
||||||
|
|
||||||
protected List<ChildGroup> childGroups = new ArrayList<>();
|
// TODO verify children not needed
|
||||||
protected List<ChildEntry> childEntries = new ArrayList<>();
|
transient protected List<GroupG> childGroups = new ArrayList<>();
|
||||||
|
transient protected List<EntryE> childEntries = new ArrayList<>();
|
||||||
|
|
||||||
|
protected PwGroup() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PwGroup(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
name = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeString(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwGroup clone() {
|
public PwGroup clone() {
|
||||||
@@ -36,48 +54,48 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
|
|||||||
return (PwGroup) super.clone();
|
return (PwGroup) super.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assign(PwGroup<Parent, ChildGroup, ChildEntry> source) {
|
protected void assign(PwGroup<GroupG, EntryE> source) {
|
||||||
super.assign(source);
|
super.assign(source);
|
||||||
name = source.name;
|
name = source.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ChildGroup> getChildGroups() {
|
public List<GroupG> getChildGroups() {
|
||||||
return childGroups;
|
return childGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ChildEntry> getChildEntries() {
|
public List<EntryE> getChildEntries() {
|
||||||
return childEntries;
|
return childEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setGroups(List<ChildGroup> groups) {
|
public void setGroups(List<GroupG> groups) {
|
||||||
childGroups = groups;
|
childGroups = groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEntries(List<ChildEntry> entries) {
|
public void setEntries(List<EntryE> entries) {
|
||||||
childEntries = entries;
|
childEntries = entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addChildGroup(ChildGroup group) {
|
public void addChildGroup(GroupG group) {
|
||||||
this.childGroups.add(group);
|
this.childGroups.add(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addChildEntry(ChildEntry entry) {
|
public void addChildEntry(EntryE entry) {
|
||||||
this.childEntries.add(entry);
|
this.childEntries.add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChildGroup getChildGroupAt(int number) {
|
public GroupG getChildGroupAt(int number) {
|
||||||
return this.childGroups.get(number);
|
return this.childGroups.get(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChildEntry getChildEntryAt(int number) {
|
public EntryE getChildEntryAt(int number) {
|
||||||
return this.childEntries.get(number);
|
return this.childEntries.get(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeChildGroup(ChildGroup group) {
|
public void removeChildGroup(GroupG group) {
|
||||||
this.childGroups.remove(group);
|
this.childGroups.remove(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeChildEntry(ChildEntry entry) {
|
public void removeChildEntry(EntryE entry) {
|
||||||
this.childEntries.remove(entry);
|
this.childEntries.remove(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +119,7 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
|
|||||||
public List<PwNode> getDirectChildren() {
|
public List<PwNode> getDirectChildren() {
|
||||||
List<PwNode> children = new ArrayList<>();
|
List<PwNode> children = new ArrayList<>();
|
||||||
children.addAll(childGroups);
|
children.addAll(childGroups);
|
||||||
for(ChildEntry child : childEntries) {
|
for(EntryE child : childEntries) {
|
||||||
if (!child.isMetaStream())
|
if (!child.isMetaStream())
|
||||||
children.add(child);
|
children.add(child);
|
||||||
}
|
}
|
||||||
@@ -112,10 +130,18 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
|
|||||||
public abstract void setId(PwGroupId id);
|
public abstract void setId(PwGroupId id);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDisplayTitle() {
|
protected String getVisualTitle() {
|
||||||
|
return getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTitle() {
|
||||||
return getName();
|
return getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same thing as {@link #getTitle()}
|
||||||
|
*/
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@@ -128,15 +154,15 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean preOrderTraverseTree(GroupHandler<ChildGroup> groupHandler,
|
public boolean preOrderTraverseTree(GroupHandler<GroupG> groupHandler,
|
||||||
EntryHandler<ChildEntry> entryHandler) {
|
EntryHandler<EntryE> entryHandler) {
|
||||||
if (entryHandler != null) {
|
if (entryHandler != null) {
|
||||||
for (ChildEntry entry : childEntries) {
|
for (EntryE entry : childEntries) {
|
||||||
if (!entryHandler.operate(entry)) return false;
|
if (!entryHandler.operate(entry)) return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ChildGroup group : childGroups) {
|
for (GroupG group : childGroups) {
|
||||||
if ((groupHandler != null) && !groupHandler.operate(group)) return false;
|
if ((groupHandler != null) && !groupHandler.operate(group)) return false;
|
||||||
group.preOrderTraverseTree(groupHandler, entryHandler);
|
group.preOrderTraverseTree(groupHandler, entryHandler);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,20 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
public abstract class PwGroupId implements Serializable {
|
public abstract class PwGroupId implements Parcelable {
|
||||||
|
|
||||||
|
public PwGroupId() {}
|
||||||
|
|
||||||
|
public PwGroupId(Parcel in) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,39 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
public class PwGroupIdV3 extends PwGroupId {
|
public class PwGroupIdV3 extends PwGroupId {
|
||||||
|
|
||||||
private int id;
|
private int id;
|
||||||
|
|
||||||
public PwGroupIdV3(int i) {
|
public PwGroupIdV3(int groupId) {
|
||||||
id = i;
|
super();
|
||||||
|
this.id = groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PwGroupIdV3(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
id = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeInt(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwGroupIdV3> CREATOR = new Creator<PwGroupIdV3>() {
|
||||||
|
@Override
|
||||||
|
public PwGroupIdV3 createFromParcel(Parcel in) {
|
||||||
|
return new PwGroupIdV3(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwGroupIdV3[] newArray(int size) {
|
||||||
|
return new PwGroupIdV3[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object compare) {
|
public boolean equals(Object compare) {
|
||||||
|
|||||||
@@ -19,15 +19,42 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class PwGroupIdV4 extends PwGroupId {
|
public class PwGroupIdV4 extends PwGroupId {
|
||||||
|
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
|
|
||||||
public PwGroupIdV4(UUID u) {
|
public PwGroupIdV4(UUID uuid) {
|
||||||
uuid = u;
|
super();
|
||||||
|
this.uuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PwGroupIdV4(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
uuid = (UUID) in.readSerializable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeSerializable(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwGroupIdV4> CREATOR = new Creator<PwGroupIdV4>() {
|
||||||
|
@Override
|
||||||
|
public PwGroupIdV4 createFromParcel(Parcel in) {
|
||||||
|
return new PwGroupIdV4(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwGroupIdV4[] newArray(int size) {
|
||||||
|
return new PwGroupIdV4[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object id) {
|
public boolean equals(Object id) {
|
||||||
if ( ! (id instanceof PwGroupIdV4) ) {
|
if ( ! (id instanceof PwGroupIdV4) ) {
|
||||||
@@ -36,12 +63,12 @@ public class PwGroupIdV4 extends PwGroupId {
|
|||||||
PwGroupIdV4 v4 = (PwGroupIdV4) id;
|
PwGroupIdV4 v4 = (PwGroupIdV4) id;
|
||||||
return uuid.equals(v4.uuid);
|
return uuid.equals(v4.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return uuid.hashCode();
|
return uuid.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getId() {
|
public UUID getId() {
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,19 +20,13 @@
|
|||||||
|
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
/**
|
import android.os.Parcel;
|
||||||
* @author Brian Pellin <bpellin@gmail.com>
|
|
||||||
* @author Naomaru Itoi <nao@phoneid.org>
|
public class PwGroupV3 extends PwGroup<PwGroupV3, PwEntryV3> {
|
||||||
* @author Bill Zwicky <wrzwicky@pobox.com>
|
|
||||||
* @author Dominik Reichl <dominik.reichl@t-online.de>
|
|
||||||
*/
|
|
||||||
public class PwGroupV3 extends PwGroup<PwGroupV3, PwGroupV3, PwEntryV3> {
|
|
||||||
|
|
||||||
// for tree traversing
|
// for tree traversing
|
||||||
private int groupId;
|
private int groupId;
|
||||||
|
|
||||||
private int level = 0; // short
|
private int level = 0; // short
|
||||||
|
|
||||||
/** Used by KeePass internally, don't use */
|
/** Used by KeePass internally, don't use */
|
||||||
private int flags;
|
private int flags;
|
||||||
|
|
||||||
@@ -40,6 +34,33 @@ public class PwGroupV3 extends PwGroup<PwGroupV3, PwGroupV3, PwEntryV3> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PwGroupV3(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
groupId = in.readInt();
|
||||||
|
level = in.readInt();
|
||||||
|
flags = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeInt(groupId);
|
||||||
|
dest.writeInt(level);
|
||||||
|
dest.writeInt(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwGroupV3> CREATOR = new Creator<PwGroupV3>() {
|
||||||
|
@Override
|
||||||
|
public PwGroupV3 createFromParcel(Parcel in) {
|
||||||
|
return new PwGroupV3(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwGroupV3[] newArray(int size) {
|
||||||
|
return new PwGroupV3[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public PwGroupV3(PwGroupV3 p) {
|
public PwGroupV3(PwGroupV3 p) {
|
||||||
construct(p);
|
construct(p);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,15 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.utils.MemUtil;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implements ITimeLogger {
|
public class PwGroupV4 extends PwGroup<PwGroupV4, PwEntryV4> implements ITimeLogger {
|
||||||
|
|
||||||
public static final boolean DEFAULT_SEARCHING_ENABLED = true;
|
public static final boolean DEFAULT_SEARCHING_ENABLED = true;
|
||||||
|
|
||||||
@@ -32,9 +36,7 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
|
|||||||
private long usageCount = 0;
|
private long usageCount = 0;
|
||||||
private PwDate parentGroupLastMod = new PwDate();
|
private PwDate parentGroupLastMod = new PwDate();
|
||||||
private Map<String, String> customData = new HashMap<>();
|
private Map<String, String> customData = new HashMap<>();
|
||||||
|
|
||||||
private boolean expires = false;
|
private boolean expires = false;
|
||||||
|
|
||||||
private String notes = "";
|
private String notes = "";
|
||||||
private boolean isExpanded = true;
|
private boolean isExpanded = true;
|
||||||
private String defaultAutoTypeSequence = "";
|
private String defaultAutoTypeSequence = "";
|
||||||
@@ -57,6 +59,53 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
|
|||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PwGroupV4(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
uuid = (UUID) in.readSerializable();
|
||||||
|
customIcon = in.readParcelable(PwIconCustom.class.getClassLoader());
|
||||||
|
usageCount = in.readLong();
|
||||||
|
parentGroupLastMod = in.readParcelable(PwDate.class.getClassLoader());
|
||||||
|
// TODO customData = MemUtil.readStringParcelableMap(in);
|
||||||
|
expires = in.readByte() != 0;
|
||||||
|
notes = in.readString();
|
||||||
|
isExpanded = in.readByte() != 0;
|
||||||
|
defaultAutoTypeSequence = in.readString();
|
||||||
|
byte autoTypeByte = in.readByte();
|
||||||
|
enableAutoType = (autoTypeByte == -1) ? null : autoTypeByte != 0;
|
||||||
|
byte enableSearchingByte = in.readByte();
|
||||||
|
enableSearching = (enableSearchingByte == -1) ? null : enableSearchingByte != 0;
|
||||||
|
lastTopVisibleEntry = (UUID) in.readSerializable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeSerializable(uuid);
|
||||||
|
dest.writeParcelable(customIcon, flags);
|
||||||
|
dest.writeLong(usageCount);
|
||||||
|
dest.writeParcelable(parentGroupLastMod, flags);
|
||||||
|
// TODO MemUtil.writeStringParcelableMap(dest, customData);
|
||||||
|
dest.writeByte((byte) (expires ? 1 : 0));
|
||||||
|
dest.writeString(notes);
|
||||||
|
dest.writeByte((byte) (isExpanded ? 1 : 0));
|
||||||
|
dest.writeString(defaultAutoTypeSequence);
|
||||||
|
dest.writeByte((byte) (enableAutoType == null ? -1 : (enableAutoType ? 1 : 0)));
|
||||||
|
dest.writeByte((byte) (enableAutoType == null ? -1 : (enableAutoType ? 1 : 0)));
|
||||||
|
dest.writeSerializable(lastTopVisibleEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwGroupV4> CREATOR = new Creator<PwGroupV4>() {
|
||||||
|
@Override
|
||||||
|
public PwGroupV4 createFromParcel(Parcel in) {
|
||||||
|
return new PwGroupV4(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwGroupV4[] newArray(int size) {
|
||||||
|
return new PwGroupV4[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected void updateWith(PwGroupV4 source) {
|
protected void updateWith(PwGroupV4 source) {
|
||||||
super.assign(source);
|
super.assign(source);
|
||||||
uuid = source.uuid;
|
uuid = source.uuid;
|
||||||
@@ -120,14 +169,6 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
|
|||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwIconCustom getCustomIcon() {
|
|
||||||
return customIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomIcon(PwIconCustom icon) {
|
|
||||||
this.customIcon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwGroupId getId() {
|
public PwGroupId getId() {
|
||||||
return new PwGroupIdV4(uuid);
|
return new PwGroupIdV4(uuid);
|
||||||
@@ -176,13 +217,26 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PwIcon getIcon() {
|
public PwIcon getIcon() {
|
||||||
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
|
if (customIcon == null || customIcon.getUUID().equals(PwDatabase.UUID_ZERO)) {
|
||||||
return super.getIcon();
|
return super.getIcon();
|
||||||
} else {
|
} else {
|
||||||
return customIcon;
|
return customIcon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PwIconCustom getIconCustom() {
|
||||||
|
return customIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconCustom(PwIconCustom icon) {
|
||||||
|
this.customIcon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconStandard(PwIconStandard icon) { // TODO Encapsulate with PwEntryV4
|
||||||
|
this.icon = icon;
|
||||||
|
this.customIcon = PwIconCustom.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
public void putCustomData(String key, String value) {
|
public void putCustomData(String key, String value) {
|
||||||
customData.put(key, value);
|
customData.put(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,23 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
public abstract class PwIcon implements Serializable {
|
public abstract class PwIcon implements Parcelable {
|
||||||
|
|
||||||
public boolean isMetaStreamIcon() {
|
public boolean isMetaStreamIcon() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PwIcon() {}
|
||||||
|
|
||||||
|
protected PwIcon(Parcel in) {}
|
||||||
|
|
||||||
|
public abstract boolean isUnknown();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,25 +19,71 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class PwIconCustom extends PwIcon {
|
public class PwIconCustom extends PwIcon {
|
||||||
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
|
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
|
||||||
|
|
||||||
public final UUID uuid;
|
private final UUID uuid;
|
||||||
public byte[] imageData;
|
transient private byte[] imageData;
|
||||||
|
|
||||||
public PwIconCustom(UUID u, byte[] data) {
|
public PwIconCustom(UUID uuid, byte[] data) {
|
||||||
uuid = u;
|
super();
|
||||||
imageData = data;
|
this.uuid = uuid;
|
||||||
|
this.imageData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwIconCustom(PwIconCustom icon) {
|
public PwIconCustom(PwIconCustom icon) {
|
||||||
|
super();
|
||||||
uuid = icon.uuid;
|
uuid = icon.uuid;
|
||||||
imageData = icon.imageData;
|
imageData = icon.imageData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PwIconCustom(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
uuid = (UUID) in.readSerializable();
|
||||||
|
// TODO Take too much memories
|
||||||
|
// in.readByteArray(imageData);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public boolean isUnknown() {
|
||||||
|
return uuid == null || this.equals(ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUUID() {
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getImageData() {
|
||||||
|
return imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageData(byte[] imageData) {
|
||||||
|
this.imageData = imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeSerializable(uuid);
|
||||||
|
// Too big for a parcelable dest.writeByteArray(imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwIconCustom> CREATOR = new Creator<PwIconCustom>() {
|
||||||
|
@Override
|
||||||
|
public PwIconCustom createFromParcel(Parcel in) {
|
||||||
|
return new PwIconCustom(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwIconCustom[] newArray(int size) {
|
||||||
|
return new PwIconCustom[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = 1;
|
int result = 1;
|
||||||
@@ -55,10 +101,7 @@ public class PwIconCustom extends PwIcon {
|
|||||||
return false;
|
return false;
|
||||||
PwIconCustom other = (PwIconCustom) obj;
|
PwIconCustom other = (PwIconCustom) obj;
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
if (other.uuid != null)
|
return other.uuid == null;
|
||||||
return false;
|
} else return uuid.equals(other.uuid);
|
||||||
} else if (!uuid.equals(other.uuid))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ public class PwIconFactory {
|
|||||||
*/
|
*/
|
||||||
private ReferenceMap customCache = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
private ReferenceMap customCache = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||||
|
|
||||||
|
public PwIconStandard getUnknownIcon() {
|
||||||
|
return getIcon(PwIconStandard.UNKNOWN);
|
||||||
|
}
|
||||||
|
|
||||||
public PwIconStandard getKeyIcon() {
|
public PwIconStandard getKeyIcon() {
|
||||||
return getIcon(PwIconStandard.KEY);
|
return getIcon(PwIconStandard.KEY);
|
||||||
}
|
}
|
||||||
@@ -46,8 +50,8 @@ public class PwIconFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PwIconStandard getFolderIcon() {
|
public PwIconStandard getFolderIcon() {
|
||||||
return getIcon(PwIconStandard.FOLDER);
|
return getIcon(PwIconStandard.FOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwIconStandard getIcon(int iconId) {
|
public PwIconStandard getIcon(int iconId) {
|
||||||
PwIconStandard icon = (PwIconStandard) cache.get(iconId);
|
PwIconStandard icon = (PwIconStandard) cache.get(iconId);
|
||||||
@@ -71,25 +75,8 @@ public class PwIconFactory {
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwIconCustom getIcon(UUID iconUuid, byte[] data) {
|
|
||||||
PwIconCustom icon = (PwIconCustom) customCache.get(iconUuid);
|
|
||||||
|
|
||||||
if (icon == null) {
|
|
||||||
icon = new PwIconCustom(iconUuid, data);
|
|
||||||
customCache.put(iconUuid, icon);
|
|
||||||
} else {
|
|
||||||
icon.imageData = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIconData(UUID iconUuid, byte[] data) {
|
|
||||||
getIcon(iconUuid, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void put(PwIconCustom icon) {
|
public void put(PwIconCustom icon) {
|
||||||
customCache.put(icon.uuid, icon);
|
customCache.put(icon.getUUID(), icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,20 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
public class PwIconStandard extends PwIcon {
|
import android.os.Parcel;
|
||||||
public final int iconId;
|
|
||||||
|
|
||||||
|
public class PwIconStandard extends PwIcon {
|
||||||
|
private final int iconId;
|
||||||
|
|
||||||
|
public static final int UNKNOWN = -1;
|
||||||
public static final int KEY = 0;
|
public static final int KEY = 0;
|
||||||
public static final int TRASH = 43;
|
public static final int TRASH = 43;
|
||||||
public static final int FOLDER = 48;
|
public static final int FOLDER = 48;
|
||||||
|
|
||||||
|
public PwIconStandard() {
|
||||||
|
this.iconId = KEY;
|
||||||
|
}
|
||||||
|
|
||||||
public PwIconStandard(int iconId) {
|
public PwIconStandard(int iconId) {
|
||||||
this.iconId = iconId;
|
this.iconId = iconId;
|
||||||
}
|
}
|
||||||
@@ -34,6 +41,37 @@ public class PwIconStandard extends PwIcon {
|
|||||||
this.iconId = icon.iconId;
|
this.iconId = icon.iconId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected PwIconStandard(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
iconId = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUnknown() {
|
||||||
|
return iconId == UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIconId() {
|
||||||
|
return iconId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(iconId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<PwIconStandard> CREATOR = new Creator<PwIconStandard>() {
|
||||||
|
@Override
|
||||||
|
public PwIconStandard createFromParcel(Parcel in) {
|
||||||
|
return new PwIconStandard(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PwIconStandard[] newArray(int size) {
|
||||||
|
return new PwIconStandard[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMetaStreamIcon() {
|
public boolean isMetaStreamIcon() {
|
||||||
return iconId == 0;
|
return iconId == 0;
|
||||||
|
|||||||
@@ -20,33 +20,69 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database;
|
||||||
|
|
||||||
import org.joda.time.LocalDate;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import com.kunzisoft.keepass.app.App;
|
||||||
|
|
||||||
|
import org.joda.time.LocalDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class who manage Groups and Entries
|
* Abstract class who manage Groups and Entries
|
||||||
*/
|
*/
|
||||||
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Serializable, Cloneable {
|
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Parcelable, Cloneable {
|
||||||
|
|
||||||
protected Parent parent = null;
|
protected Parent parent = null;
|
||||||
|
protected PwIconStandard icon = new PwIconStandard();
|
||||||
protected PwIconStandard icon = new PwIconStandard(0);
|
|
||||||
|
|
||||||
protected PwDate creation = new PwDate();
|
protected PwDate creation = new PwDate();
|
||||||
protected PwDate lastMod = new PwDate();
|
protected PwDate lastMod = new PwDate();
|
||||||
protected PwDate lastAccess = new PwDate();
|
protected PwDate lastAccess = new PwDate();
|
||||||
protected PwDate expireDate = PwDate.PW_NEVER_EXPIRE;
|
protected PwDate expireDate = PwDate.PW_NEVER_EXPIRE;
|
||||||
|
|
||||||
|
protected PwNode() {}
|
||||||
|
|
||||||
|
protected PwNode(Parcel in) {
|
||||||
|
// TODO better technique ?
|
||||||
|
try {
|
||||||
|
PwGroupId pwGroupId = in.readParcelable(PwGroupId.class.getClassLoader());
|
||||||
|
parent = (Parent) App.getDB().getPwDatabase().getGroupByGroupId(pwGroupId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
icon = in.readParcelable(PwIconStandard.class.getClassLoader());
|
||||||
|
creation = in.readParcelable(PwDate.class.getClassLoader());
|
||||||
|
lastMod = in.readParcelable(PwDate.class.getClassLoader());
|
||||||
|
lastAccess = in.readParcelable(PwDate.class.getClassLoader());
|
||||||
|
expireDate = in.readParcelable(PwDate.class.getClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
PwGroupId parentId = null;
|
||||||
|
if (parent != null)
|
||||||
|
parentId = parent.getId();
|
||||||
|
dest.writeParcelable(parentId, flags);
|
||||||
|
|
||||||
|
dest.writeParcelable(icon, flags);
|
||||||
|
dest.writeParcelable(creation, flags);
|
||||||
|
dest.writeParcelable(lastMod, flags);
|
||||||
|
dest.writeParcelable(lastAccess, flags);
|
||||||
|
dest.writeParcelable(expireDate, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
protected void construct(Parent parent) {
|
protected void construct(Parent parent) {
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assign(PwNode<Parent> source) {
|
protected void assign(PwNode<Parent> source) {
|
||||||
this.parent = source.parent;
|
this.parent = source.parent;
|
||||||
|
|
||||||
this.icon = source.icon;
|
this.icon = source.icon;
|
||||||
|
|
||||||
this.creation = source.creation;
|
this.creation = source.creation;
|
||||||
this.lastMod = source.lastMod;
|
this.lastMod = source.lastMod;
|
||||||
this.lastAccess = source.lastAccess;
|
this.lastAccess = source.lastAccess;
|
||||||
@@ -59,9 +95,7 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
|
|||||||
try {
|
try {
|
||||||
newNode = (PwNode) super.clone();
|
newNode = (PwNode) super.clone();
|
||||||
// newNode.parent stay the same in copy
|
// newNode.parent stay the same in copy
|
||||||
|
|
||||||
newNode.icon = new PwIconStandard(this.icon);
|
newNode.icon = new PwIconStandard(this.icon);
|
||||||
|
|
||||||
newNode.creation = creation.clone();
|
newNode.creation = creation.clone();
|
||||||
newNode.lastMod = lastMod.clone();
|
newNode.lastMod = lastMod.clone();
|
||||||
newNode.lastAccess = lastAccess.clone();
|
newNode.lastAccess = lastAccess.clone();
|
||||||
@@ -85,22 +119,27 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
|
|||||||
public abstract Type getType();
|
public abstract Type getType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Title to display as view
|
* @return Title
|
||||||
*/
|
*/
|
||||||
public abstract String getDisplayTitle();
|
public abstract String getTitle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Title to display, typically return alternative title if {@link #getTitle()} is empty
|
||||||
|
*/
|
||||||
|
protected abstract String getVisualTitle();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Visual icon
|
* @return Visual icon
|
||||||
*/
|
*/
|
||||||
public PwIcon getIcon() {
|
public PwIcon getIcon() {
|
||||||
return icon;
|
return getIconStandard();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwIconStandard getIconStandard() {
|
public PwIconStandard getIconStandard() {
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIcon(PwIconStandard icon) {
|
public void setIconStandard(PwIconStandard icon) {
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +215,7 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
|
|||||||
*/
|
*/
|
||||||
public boolean isContentVisuallyTheSame(PwNode o) {
|
public boolean isContentVisuallyTheSame(PwNode o) {
|
||||||
return getType().equals(o.getType())
|
return getType().equals(o.getType())
|
||||||
&& getDisplayTitle().equals(o.getDisplayTitle())
|
&& getVisualTitle().equals(o.getVisualTitle())
|
||||||
&& getIcon().equals(o.getIcon());
|
&& getIcon().equals(o.getIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ public enum SortNodeEnum {
|
|||||||
new EntryNameComparator(ascending),
|
new EntryNameComparator(ascending),
|
||||||
object1,
|
object1,
|
||||||
object2,
|
object2,
|
||||||
object1.getDisplayTitle()
|
object1.getTitle()
|
||||||
.compareToIgnoreCase(object2.getDisplayTitle()));
|
.compareToIgnoreCase(object2.getTitle()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,14 +69,12 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
|
|||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
mDatabase.loadData(mContext, mUri, mPass, mKey, mStatus);
|
mDatabase.loadData(mContext, mUri, mPass, mKey, mStatus);
|
||||||
|
|
||||||
saveFileData(mUri, mKey);
|
saveFileData(mUri, mKey);
|
||||||
|
|
||||||
} catch (ArcFourException e) {
|
} catch (ArcFourException e) {
|
||||||
catchError(e, R.string.error_arc4);
|
catchError(e, R.string.error_arc4);
|
||||||
return;
|
return;
|
||||||
} catch (InvalidPasswordException e) {
|
} catch (InvalidPasswordException e) {
|
||||||
catchError(e, R.string.InvalidPassword);
|
catchError(e, R.string.invalid_password);
|
||||||
return;
|
return;
|
||||||
} catch (ContentFileNotFoundException e) {
|
} catch (ContentFileNotFoundException e) {
|
||||||
catchError(e, R.string.file_not_found_content);
|
catchError(e, R.string.file_not_found_content);
|
||||||
@@ -85,8 +83,10 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
|
|||||||
catchError(e, R.string.file_not_found);
|
catchError(e, R.string.file_not_found);
|
||||||
return;
|
return;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Database can't be read", e);
|
if (e.getMessage().contains("Hash failed with code"))
|
||||||
finish(false, e.getMessage());
|
catchError(e, R.string.error_load_database_KDF_memory, true);
|
||||||
|
else
|
||||||
|
catchError(e, R.string.error_load_database, true);
|
||||||
return;
|
return;
|
||||||
} catch (KeyFileEmptyException e) {
|
} catch (KeyFileEmptyException e) {
|
||||||
catchError(e, R.string.keyfile_is_empty);
|
catchError(e, R.string.keyfile_is_empty);
|
||||||
@@ -107,22 +107,24 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
|
|||||||
catchError(e, R.string.error_invalid_db);
|
catchError(e, R.string.error_invalid_db);
|
||||||
return;
|
return;
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
String errorMessage = mContext.getString(R.string.error_out_of_memory);
|
catchError(e, R.string.error_out_of_memory);
|
||||||
Log.e(TAG, errorMessage, e);
|
|
||||||
finish(false, errorMessage);
|
|
||||||
return;
|
return;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Database can't be load", e);
|
catchError(e, R.string.error_load_database, true);
|
||||||
finish(false, e.getMessage());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
finish(true);
|
finish(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void catchError(Exception e, @StringRes int messageId) {
|
private void catchError(Throwable e, @StringRes int messageId) {
|
||||||
|
catchError(e, messageId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void catchError(Throwable e, @StringRes int messageId, boolean addThrowableMessage) {
|
||||||
String errorMessage = mContext.getString(messageId);
|
String errorMessage = mContext.getString(messageId);
|
||||||
Log.e(TAG, errorMessage, e);
|
Log.e(TAG, errorMessage, e);
|
||||||
|
if (addThrowableMessage)
|
||||||
|
errorMessage = errorMessage + " " + e.getLocalizedMessage();
|
||||||
finish(false, errorMessage);
|
finish(false, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,15 +31,15 @@ import java.util.List;
|
|||||||
|
|
||||||
public class DeleteGroupRunnable extends ActionNodeDatabaseRunnable {
|
public class DeleteGroupRunnable extends ActionNodeDatabaseRunnable {
|
||||||
|
|
||||||
private PwGroup<PwGroup, PwGroup, PwEntry> mGroupToDelete;
|
private PwGroup<PwGroup, PwEntry> mGroupToDelete;
|
||||||
private PwGroup mParent;
|
private PwGroup mParent;
|
||||||
private boolean mRecycle;
|
private boolean mRecycle;
|
||||||
|
|
||||||
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish) {
|
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwEntry> group, AfterActionNodeOnFinish finish) {
|
||||||
this(ctx, db, group, finish, false);
|
this(ctx, db, group, finish, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish, boolean dontSave) {
|
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwEntry> group, AfterActionNodeOnFinish finish, boolean dontSave) {
|
||||||
super(ctx, db, finish, dontSave);
|
super(ctx, db, finish, dontSave);
|
||||||
mGroupToDelete = group;
|
mGroupToDelete = group;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package com.kunzisoft.keepass.database.cursor;
|
||||||
|
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.PwDatabase;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||||
|
import com.kunzisoft.keepass.database.PwIconCustom;
|
||||||
|
import com.kunzisoft.keepass.database.PwIconFactory;
|
||||||
|
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class EntryCursor extends MatrixCursor {
|
||||||
|
|
||||||
|
private long entryId;
|
||||||
|
public static final String _ID = BaseColumns._ID;
|
||||||
|
public static final String COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUID_most_significant_bits";
|
||||||
|
public static final String COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUID_least_significant_bits";
|
||||||
|
public static final String COLUMN_INDEX_TITLE = "title";
|
||||||
|
public static final String COLUMN_INDEX_ICON_STANDARD = "icon_standard";
|
||||||
|
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS = "icon_custom_UUID_most_significant_bits";
|
||||||
|
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS = "icon_custom_UUID_least_significant_bits";
|
||||||
|
public static final String COLUMN_INDEX_USERNAME = "username";
|
||||||
|
public static final String COLUMN_INDEX_PASSWORD = "password";
|
||||||
|
public static final String COLUMN_INDEX_URL = "URL";
|
||||||
|
public static final String COLUMN_INDEX_NOTES = "notes";
|
||||||
|
|
||||||
|
private ExtraFieldCursor extraFieldCursor;
|
||||||
|
|
||||||
|
public EntryCursor() {
|
||||||
|
super(new String[]{ _ID,
|
||||||
|
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
|
||||||
|
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
|
||||||
|
COLUMN_INDEX_TITLE,
|
||||||
|
COLUMN_INDEX_ICON_STANDARD,
|
||||||
|
COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS,
|
||||||
|
COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS,
|
||||||
|
COLUMN_INDEX_USERNAME,
|
||||||
|
COLUMN_INDEX_PASSWORD,
|
||||||
|
COLUMN_INDEX_URL,
|
||||||
|
COLUMN_INDEX_NOTES});
|
||||||
|
entryId = 0;
|
||||||
|
extraFieldCursor = new ExtraFieldCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntry(PwEntryV3 entry) {
|
||||||
|
addRow(new Object[] {entryId,
|
||||||
|
entry.getUUID().getMostSignificantBits(),
|
||||||
|
entry.getUUID().getLeastSignificantBits(),
|
||||||
|
entry.getTitle(),
|
||||||
|
entry.getIconStandard().getIconId(),
|
||||||
|
PwDatabase.UUID_ZERO.getMostSignificantBits(),
|
||||||
|
PwDatabase.UUID_ZERO.getLeastSignificantBits(),
|
||||||
|
entry.getUsername(),
|
||||||
|
entry.getPassword(),
|
||||||
|
entry.getUrl(),
|
||||||
|
entry.getNotes()});
|
||||||
|
entryId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEntry(PwEntryV4 entry) {
|
||||||
|
addRow(new Object[] {entryId,
|
||||||
|
entry.getUUID().getMostSignificantBits(),
|
||||||
|
entry.getUUID().getLeastSignificantBits(),
|
||||||
|
entry.getTitle(),
|
||||||
|
entry.getIconStandard().getIconId(),
|
||||||
|
entry.getIconCustom().getUUID().getMostSignificantBits(),
|
||||||
|
entry.getIconCustom().getUUID().getLeastSignificantBits(),
|
||||||
|
entry.getUsername(),
|
||||||
|
entry.getPassword(),
|
||||||
|
entry.getUrl(),
|
||||||
|
entry.getNotes()});
|
||||||
|
|
||||||
|
entry.getFields().doActionToAllCustomProtectedField((key, value) -> {
|
||||||
|
extraFieldCursor.addExtraField(entryId, key, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
entryId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateEntryBaseVersion(PwEntry pwEntry, PwIconFactory iconFactory) {
|
||||||
|
pwEntry.setUUID(
|
||||||
|
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
|
||||||
|
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||||
|
pwEntry.setTitle(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE)));
|
||||||
|
|
||||||
|
PwIconStandard iconStandard = iconFactory.getIcon(getInt(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
|
||||||
|
pwEntry.setIconStandard(iconStandard);
|
||||||
|
|
||||||
|
pwEntry.setUsername(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME)));
|
||||||
|
pwEntry.setPassword(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_PASSWORD)));
|
||||||
|
pwEntry.setUrl(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_URL)));
|
||||||
|
pwEntry.setNotes(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_NOTES)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateEntry(PwEntryV3 pwEntry, PwIconFactory iconFactory) {
|
||||||
|
populateEntryBaseVersion(pwEntry, iconFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateEntry(PwEntryV4 pwEntry, PwIconFactory iconFactory) {
|
||||||
|
populateEntryBaseVersion(pwEntry, iconFactory);
|
||||||
|
|
||||||
|
// Retrieve custom icon
|
||||||
|
PwIconCustom iconCustom = iconFactory.getIcon(
|
||||||
|
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||||
|
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||||
|
pwEntry.setIconCustom(iconCustom);
|
||||||
|
|
||||||
|
// Retrieve extra fields
|
||||||
|
if (extraFieldCursor.moveToFirst()) {
|
||||||
|
while (!extraFieldCursor.isAfterLast()) {
|
||||||
|
// Add a new extra field only if entryId is the one we want
|
||||||
|
if (extraFieldCursor.getLong(extraFieldCursor.getColumnIndex(ExtraFieldCursor.FOREIGN_KEY_ENTRY_ID))
|
||||||
|
== getLong(getColumnIndex(EntryCursor._ID))) {
|
||||||
|
extraFieldCursor.populateExtraFieldInEntry(pwEntry);
|
||||||
|
}
|
||||||
|
extraFieldCursor.moveToNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.kunzisoft.keepass.database.cursor;
|
||||||
|
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.provider.BaseColumns;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||||
|
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||||
|
|
||||||
|
public class ExtraFieldCursor extends MatrixCursor {
|
||||||
|
|
||||||
|
private long fieldId;
|
||||||
|
public static final String _ID = BaseColumns._ID;
|
||||||
|
public static final String FOREIGN_KEY_ENTRY_ID = "entry_id";
|
||||||
|
public static final String COLUMN_LABEL = "label";
|
||||||
|
public static final String COLUMN_PROTECTION = "protection";
|
||||||
|
public static final String COLUMN_VALUE = "value";
|
||||||
|
|
||||||
|
public ExtraFieldCursor() {
|
||||||
|
super(new String[]{ _ID,
|
||||||
|
FOREIGN_KEY_ENTRY_ID,
|
||||||
|
COLUMN_LABEL,
|
||||||
|
COLUMN_PROTECTION,
|
||||||
|
COLUMN_VALUE});
|
||||||
|
fieldId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void addExtraField(long entryId, String label, ProtectedString value) {
|
||||||
|
addRow(new Object[] {fieldId,
|
||||||
|
entryId,
|
||||||
|
label,
|
||||||
|
(value.isProtected()) ? 1 : 0,
|
||||||
|
value.toString()});
|
||||||
|
fieldId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateExtraFieldInEntry(PwEntryV4 pwEntry) {
|
||||||
|
|
||||||
|
pwEntry.addExtraField(getString(getColumnIndex(ExtraFieldCursor.COLUMN_LABEL)),
|
||||||
|
new ProtectedString((getInt(getColumnIndex(ExtraFieldCursor.COLUMN_PROTECTION)) > 0),
|
||||||
|
getString(getColumnIndex(ExtraFieldCursor.COLUMN_VALUE))));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.database.iterator;
|
|||||||
import com.kunzisoft.keepass.database.PwEntry;
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||||
import com.kunzisoft.keepass.database.SearchParameters;
|
import com.kunzisoft.keepass.database.search.SearchParameters;
|
||||||
import com.kunzisoft.keepass.database.SearchParametersV4;
|
import com.kunzisoft.keepass.database.search.SearchParametersV4;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.database.iterator;
|
package com.kunzisoft.keepass.database.iterator;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||||
import com.kunzisoft.keepass.database.SearchParameters;
|
import com.kunzisoft.keepass.database.search.SearchParameters;
|
||||||
|
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
package com.kunzisoft.keepass.database.iterator;
|
package com.kunzisoft.keepass.database.iterator;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||||
import com.kunzisoft.keepass.database.SearchParametersV4;
|
import com.kunzisoft.keepass.database.search.SearchParametersV4;
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -78,18 +78,19 @@ public class EntrySearchStringIteratorV4 extends EntrySearchStringIterator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean searchInField(String key) {
|
private boolean searchInField(String key) {
|
||||||
if (key.equals(PwEntryV4.STR_TITLE)) {
|
switch (key) {
|
||||||
return sp.searchInTitles;
|
case PwEntryV4.STR_TITLE:
|
||||||
} else if (key.equals(PwEntryV4.STR_USERNAME)) {
|
return sp.searchInTitles;
|
||||||
return sp.searchInUserNames;
|
case PwEntryV4.STR_USERNAME:
|
||||||
} else if (key.equals(PwEntryV4.STR_PASSWORD)) {
|
return sp.searchInUserNames;
|
||||||
return sp.searchInPasswords;
|
case PwEntryV4.STR_PASSWORD:
|
||||||
} else if (key.equals(PwEntryV4.STR_URL)) {
|
return sp.searchInPasswords;
|
||||||
return sp.searchInUrls;
|
case PwEntryV4.STR_URL:
|
||||||
} else if (key.equals(PwEntryV4.STR_NOTES)) {
|
return sp.searchInUrls;
|
||||||
return sp.searchInNotes;
|
case PwEntryV4.STR_NOTES:
|
||||||
} else {
|
return sp.searchInNotes;
|
||||||
return sp.searchInOther;
|
default:
|
||||||
|
return sp.searchInOther;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,15 +24,16 @@ import com.kunzisoft.keepass.database.PwDbHeaderV4;
|
|||||||
import com.kunzisoft.keepass.database.exception.InvalidDBSignatureException;
|
import com.kunzisoft.keepass.database.exception.InvalidDBSignatureException;
|
||||||
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
import com.kunzisoft.keepass.stream.LEDataInputStream;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class ImporterFactory {
|
public class ImporterFactory {
|
||||||
public static Importer createImporter(InputStream is) throws InvalidDBSignatureException, IOException {
|
public static Importer createImporter(InputStream is, File streamDir) throws InvalidDBSignatureException, IOException {
|
||||||
return createImporter(is, false);
|
return createImporter(is, streamDir,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Importer createImporter(InputStream is, boolean debug) throws InvalidDBSignatureException, IOException {
|
public static Importer createImporter(InputStream is, File streamDir, boolean debug) throws InvalidDBSignatureException, IOException {
|
||||||
int sig1 = LEDataInputStream.readInt(is);
|
int sig1 = LEDataInputStream.readInt(is);
|
||||||
int sig2 = LEDataInputStream.readInt(is);
|
int sig2 = LEDataInputStream.readInt(is);
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ public class ImporterFactory {
|
|||||||
|
|
||||||
return new ImporterV3();
|
return new ImporterV3();
|
||||||
} else if ( PwDbHeaderV4.matchesHeader(sig1, sig2) ) {
|
} else if ( PwDbHeaderV4.matchesHeader(sig1, sig2) ) {
|
||||||
return new ImporterV4();
|
return new ImporterV4(streamDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new InvalidDBSignatureException();
|
throw new InvalidDBSignatureException();
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ public class ImporterV3 extends Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (progressTaskUpdater != null)
|
if (progressTaskUpdater != null)
|
||||||
progressTaskUpdater.updateMessage(R.string.creating_db_key);
|
progressTaskUpdater.updateMessage(R.string.retrieving_db_key);
|
||||||
databaseToOpen = createDB();
|
databaseToOpen = createDB();
|
||||||
databaseToOpen.retrieveMasterKey(password, kfIs);
|
databaseToOpen.retrieveMasterKey(password, kfIs);
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ public class ImporterV3 extends Importer {
|
|||||||
grp.setExpiryTime(new PwDate(buf, offset));
|
grp.setExpiryTime(new PwDate(buf, offset));
|
||||||
break;
|
break;
|
||||||
case 0x0007 :
|
case 0x0007 :
|
||||||
grp.setIcon(db.getIconFactory().getIcon(LEDataInputStream.readInt(buf, offset)));
|
grp.setIconStandard(db.getIconFactory().getIcon(LEDataInputStream.readInt(buf, offset)));
|
||||||
break;
|
break;
|
||||||
case 0x0008 :
|
case 0x0008 :
|
||||||
grp.setLevel(LEDataInputStream.readUShort(buf, offset));
|
grp.setLevel(LEDataInputStream.readUShort(buf, offset));
|
||||||
@@ -353,7 +353,7 @@ public class ImporterV3 extends Importer {
|
|||||||
iconId = 0;
|
iconId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ent.setIcon(db.getIconFactory().getIcon(iconId));
|
ent.setIconStandard(db.getIconFactory().getIcon(iconId));
|
||||||
break;
|
break;
|
||||||
case 0x0004 :
|
case 0x0004 :
|
||||||
ent.setTitle(Types.readCString(buf, offset));
|
ent.setTitle(Types.readCString(buf, offset));
|
||||||
|
|||||||
@@ -54,6 +54,8 @@ import org.xmlpull.v1.XmlPullParser;
|
|||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@@ -62,10 +64,8 @@ import java.security.InvalidKeyException;
|
|||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
@@ -80,12 +80,11 @@ public class ImporterV4 extends Importer {
|
|||||||
private PwDatabaseV4 db;
|
private PwDatabaseV4 db;
|
||||||
|
|
||||||
private byte[] hashOfHeader = null;
|
private byte[] hashOfHeader = null;
|
||||||
private byte[] pbHeader = null;
|
|
||||||
private long version;
|
private long version;
|
||||||
Calendar utcCal;
|
private File streamDir;
|
||||||
|
|
||||||
public ImporterV4() {
|
public ImporterV4(File streamDir) {
|
||||||
utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
this.streamDir = streamDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,7 +100,7 @@ public class ImporterV4 extends Importer {
|
|||||||
InvalidDBException {
|
InvalidDBException {
|
||||||
|
|
||||||
if (progressTaskUpdater != null)
|
if (progressTaskUpdater != null)
|
||||||
progressTaskUpdater.updateMessage(R.string.creating_db_key);
|
progressTaskUpdater.updateMessage(R.string.retrieving_db_key);
|
||||||
db = new PwDatabaseV4();
|
db = new PwDatabaseV4();
|
||||||
|
|
||||||
PwDbHeaderV4 header = new PwDbHeaderV4(db);
|
PwDbHeaderV4 header = new PwDbHeaderV4(db);
|
||||||
@@ -111,7 +110,7 @@ public class ImporterV4 extends Importer {
|
|||||||
version = header.getVersion();
|
version = header.getVersion();
|
||||||
|
|
||||||
hashOfHeader = hh.hash;
|
hashOfHeader = hh.hash;
|
||||||
pbHeader = hh.header;
|
byte[] pbHeader = hh.header;
|
||||||
|
|
||||||
db.retrieveMasterKey(password, keyInputStream);
|
db.retrieveMasterKey(password, keyInputStream);
|
||||||
db.makeFinalKey(header.masterSeed);
|
db.makeFinalKey(header.masterSeed);
|
||||||
@@ -185,7 +184,6 @@ public class ImporterV4 extends Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( header.innerRandomStreamKey == null ) {
|
if ( header.innerRandomStreamKey == null ) {
|
||||||
assert(false);
|
|
||||||
throw new IOException("Invalid stream key.");
|
throw new IOException("Invalid stream key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +210,10 @@ public class ImporterV4 extends Importer {
|
|||||||
while(true) {
|
while(true) {
|
||||||
if (!ReadInnerHeader(lis, header)) break;
|
if (!ReadInnerHeader(lis, header)) break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getUnusedCacheFileName() {
|
||||||
|
return String.valueOf(db.getBinPool().findUnusedKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean ReadInnerHeader(LEDataInputStream lis, PwDbHeaderV4 header) throws IOException {
|
private boolean ReadInnerHeader(LEDataInputStream lis, PwDbHeaderV4 header) throws IOException {
|
||||||
@@ -223,7 +224,8 @@ public class ImporterV4 extends Importer {
|
|||||||
|
|
||||||
byte[] data = new byte[0];
|
byte[] data = new byte[0];
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
data = lis.readBytes(size);
|
if (fieldId != PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary)
|
||||||
|
data = lis.readBytes(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean result = true;
|
boolean result = true;
|
||||||
@@ -238,22 +240,20 @@ public class ImporterV4 extends Importer {
|
|||||||
header.innerRandomStreamKey = data;
|
header.innerRandomStreamKey = data;
|
||||||
break;
|
break;
|
||||||
case PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary:
|
case PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary:
|
||||||
if (data.length < 1) throw new IOException("Invalid binary format");
|
byte flag = lis.readBytes(1)[0];
|
||||||
byte flag = data[0];
|
boolean protectedFlag = (flag & PwDbHeaderV4.KdbxBinaryFlags.Protected) !=
|
||||||
boolean prot = (flag & PwDbHeaderV4.KdbxBinaryFlags.Protected) !=
|
PwDbHeaderV4.KdbxBinaryFlags.None;
|
||||||
PwDbHeaderV4.KdbxBinaryFlags.None;
|
int byteLength = size - 1;
|
||||||
|
// Read in a file
|
||||||
byte[] bin = new byte[data.length - 1];
|
File file = new File(streamDir, getUnusedCacheFileName());
|
||||||
System.arraycopy(data, 1, bin, 0, data.length-1);
|
try (FileOutputStream outputStream = new FileOutputStream(file)) {
|
||||||
ProtectedBinary pb = new ProtectedBinary(prot, bin);
|
lis.readBytes(byteLength, outputStream::write);
|
||||||
db.getBinPool().poolAdd(pb);
|
|
||||||
|
|
||||||
if (prot) {
|
|
||||||
Arrays.fill(data, (byte)0);
|
|
||||||
}
|
}
|
||||||
|
ProtectedBinary protectedBinary = new ProtectedBinary(protectedFlag, file, byteLength);
|
||||||
|
db.getBinPool().add(protectedBinary);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,9 +289,9 @@ public class ImporterV4 extends Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final long DEFAULT_HISTORY_DAYS = 365;
|
private static final long DEFAULT_HISTORY_DAYS = 365;
|
||||||
|
|
||||||
private boolean readNextNode = true;
|
private boolean readNextNode = true;
|
||||||
private Stack<PwGroupV4> ctxGroups = new Stack<PwGroupV4>();
|
private Stack<PwGroupV4> ctxGroups = new Stack<>();
|
||||||
private PwGroupV4 ctxGroup = null;
|
private PwGroupV4 ctxGroup = null;
|
||||||
private PwEntryV4 ctxEntry = null;
|
private PwEntryV4 ctxEntry = null;
|
||||||
private String ctxStringName = null;
|
private String ctxStringName = null;
|
||||||
@@ -337,7 +337,7 @@ public class ImporterV4 extends Importer {
|
|||||||
ctxGroups.clear();
|
ctxGroups.clear();
|
||||||
|
|
||||||
KdbContext ctx = KdbContext.Null;
|
KdbContext ctx = KdbContext.Null;
|
||||||
|
|
||||||
readNextNode = true;
|
readNextNode = true;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -348,20 +348,17 @@ public class ImporterV4 extends Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch ( xpp.getEventType() ) {
|
switch ( xpp.getEventType() ) {
|
||||||
case XmlPullParser.START_TAG:
|
case XmlPullParser.START_TAG:
|
||||||
ctx = ReadXmlElement(ctx, xpp);
|
ctx = ReadXmlElement(ctx, xpp);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case XmlPullParser.END_TAG:
|
|
||||||
ctx = EndXmlElement(ctx, xpp);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
case XmlPullParser.END_TAG:
|
||||||
assert(false);
|
ctx = EndXmlElement(ctx, xpp);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error checks
|
// Error checks
|
||||||
@@ -533,8 +530,8 @@ public class ImporterV4 extends Importer {
|
|||||||
|
|
||||||
case Root:
|
case Root:
|
||||||
if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemGroup) ) {
|
if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemGroup) ) {
|
||||||
assert(ctxGroups.size() == 0);
|
if ( ctxGroups.size() != 0 )
|
||||||
if ( ctxGroups.size() != 0 ) throw new IOException("Group list should be empty.");
|
throw new IOException("Group list should be empty.");
|
||||||
|
|
||||||
db.setRootGroup(new PwGroupV4());
|
db.setRootGroup(new PwGroupV4());
|
||||||
ctxGroups.push(db.getRootGroup());
|
ctxGroups.push(db.getRootGroup());
|
||||||
@@ -556,9 +553,9 @@ public class ImporterV4 extends Importer {
|
|||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemNotes) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemNotes) ) {
|
||||||
ctxGroup.setNotes(ReadString(xpp));
|
ctxGroup.setNotes(ReadString(xpp));
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
|
||||||
ctxGroup.setIcon(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
|
ctxGroup.setIconStandard(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
|
||||||
ctxGroup.setCustomIcon(db.getIconFactory().getIcon(ReadUuid(xpp)));
|
ctxGroup.setIconCustom(db.getIconFactory().getIcon(ReadUuid(xpp)));
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemTimes) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemTimes) ) {
|
||||||
return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
|
return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIsExpanded) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIsExpanded) ) {
|
||||||
@@ -611,9 +608,9 @@ public class ImporterV4 extends Importer {
|
|||||||
if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemUuid) ) {
|
if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemUuid) ) {
|
||||||
ctxEntry.setUUID(ReadUuid(xpp));
|
ctxEntry.setUUID(ReadUuid(xpp));
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
|
||||||
ctxEntry.setIcon(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
|
ctxEntry.setIconStandard(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
|
||||||
ctxEntry.setCustomIcon(db.getIconFactory().getIcon(ReadUuid(xpp)));
|
ctxEntry.setIconCustom(db.getIconFactory().getIcon(ReadUuid(xpp)));
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemFgColor) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemFgColor) ) {
|
||||||
ctxEntry.setForegroundColor(ReadString(xpp));
|
ctxEntry.setForegroundColor(ReadString(xpp));
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemBgColor) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemBgColor) ) {
|
||||||
@@ -633,8 +630,6 @@ public class ImporterV4 extends Importer {
|
|||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomData)) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomData)) {
|
||||||
return SwitchContext(ctx, KdbContext.EntryCustomData, xpp);
|
return SwitchContext(ctx, KdbContext.EntryCustomData, xpp);
|
||||||
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemHistory) ) {
|
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemHistory) ) {
|
||||||
assert(!entryInHistory);
|
|
||||||
|
|
||||||
if ( ! entryInHistory ) {
|
if ( ! entryInHistory ) {
|
||||||
ctxHistoryBase = ctxEntry;
|
ctxHistoryBase = ctxEntry;
|
||||||
return SwitchContext(ctx, KdbContext.EntryHistory, xpp);
|
return SwitchContext(ctx, KdbContext.EntryHistory, xpp);
|
||||||
@@ -774,8 +769,8 @@ public class ImporterV4 extends Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private KdbContext EndXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException {
|
private KdbContext EndXmlElement(KdbContext ctx, XmlPullParser xpp) throws XmlPullParserException {
|
||||||
assert(xpp.getEventType() == XmlPullParser.END_TAG);
|
// (xpp.getEventType() == XmlPullParser.END_TAG);
|
||||||
|
|
||||||
String name = xpp.getName();
|
String name = xpp.getName();
|
||||||
if ( ctx == KdbContext.KeePassFile && name.equalsIgnoreCase(PwDatabaseV4XML.ElemDocNode) ) {
|
if ( ctx == KdbContext.KeePassFile && name.equalsIgnoreCase(PwDatabaseV4XML.ElemDocNode) ) {
|
||||||
return KdbContext.Null;
|
return KdbContext.Null;
|
||||||
@@ -792,7 +787,7 @@ public class ImporterV4 extends Importer {
|
|||||||
PwIconCustom icon = new PwIconCustom(customIconID, customIconData);
|
PwIconCustom icon = new PwIconCustom(customIconID, customIconData);
|
||||||
db.addCustomIcon(icon);
|
db.addCustomIcon(icon);
|
||||||
db.getIconFactory().put(icon);
|
db.getIconFactory().put(icon);
|
||||||
} else assert(false);
|
}
|
||||||
|
|
||||||
customIconID = PwDatabase.UUID_ZERO;
|
customIconID = PwDatabase.UUID_ZERO;
|
||||||
customIconData = null;
|
customIconData = null;
|
||||||
@@ -805,7 +800,7 @@ public class ImporterV4 extends Importer {
|
|||||||
} else if ( ctx == KdbContext.CustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem) ) {
|
} else if ( ctx == KdbContext.CustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem) ) {
|
||||||
if ( customDataKey != null && customDataValue != null) {
|
if ( customDataKey != null && customDataValue != null) {
|
||||||
db.putCustomData(customDataKey, customDataValue);
|
db.putCustomData(customDataKey, customDataValue);
|
||||||
} else assert(false);
|
}
|
||||||
|
|
||||||
customDataKey = null;
|
customDataKey = null;
|
||||||
customDataValue = null;
|
customDataValue = null;
|
||||||
@@ -832,8 +827,6 @@ public class ImporterV4 extends Importer {
|
|||||||
} else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) {
|
} else if ( ctx == KdbContext.GroupCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) {
|
||||||
if (groupCustomDataKey != null && groupCustomDataValue != null) {
|
if (groupCustomDataKey != null && groupCustomDataValue != null) {
|
||||||
ctxGroup.putCustomData(groupCustomDataKey, groupCustomDataKey);
|
ctxGroup.putCustomData(groupCustomDataKey, groupCustomDataKey);
|
||||||
} else {
|
|
||||||
assert(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
groupCustomDataKey = null;
|
groupCustomDataKey = null;
|
||||||
@@ -879,8 +872,6 @@ public class ImporterV4 extends Importer {
|
|||||||
} else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) {
|
} else if ( ctx == KdbContext.EntryCustomDataItem && name.equalsIgnoreCase(PwDatabaseV4XML.ElemStringDictExItem)) {
|
||||||
if (entryCustomDataKey != null && entryCustomDataValue != null) {
|
if (entryCustomDataKey != null && entryCustomDataValue != null) {
|
||||||
ctxEntry.putCustomData(entryCustomDataKey, entryCustomDataValue);
|
ctxEntry.putCustomData(entryCustomDataKey, entryCustomDataValue);
|
||||||
} else {
|
|
||||||
assert(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entryCustomDataKey = null;
|
entryCustomDataKey = null;
|
||||||
@@ -896,8 +887,6 @@ public class ImporterV4 extends Importer {
|
|||||||
ctxDeletedObject = null;
|
ctxDeletedObject = null;
|
||||||
return KdbContext.RootDeletedObjects;
|
return KdbContext.RootDeletedObjects;
|
||||||
} else {
|
} else {
|
||||||
assert(false);
|
|
||||||
|
|
||||||
String contextName = "";
|
String contextName = "";
|
||||||
if (ctx != null) {
|
if (ctx != null) {
|
||||||
contextName = ctx.name();
|
contextName = ctx.name();
|
||||||
@@ -943,35 +932,24 @@ public class ImporterV4 extends Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ReadUnknown(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
private void ReadUnknown(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||||
assert(false);
|
|
||||||
|
|
||||||
if ( xpp.isEmptyElementTag() ) return;
|
if ( xpp.isEmptyElementTag() ) return;
|
||||||
|
|
||||||
String unknownName = xpp.getName();
|
|
||||||
ProcessNode(xpp);
|
ProcessNode(xpp);
|
||||||
|
|
||||||
while (xpp.next() != XmlPullParser.END_DOCUMENT ) {
|
while (xpp.next() != XmlPullParser.END_DOCUMENT ) {
|
||||||
if ( xpp.getEventType() == XmlPullParser.END_TAG ) break;
|
if ( xpp.getEventType() == XmlPullParser.END_TAG ) break;
|
||||||
if ( xpp.getEventType() == XmlPullParser.START_TAG ) continue;
|
if ( xpp.getEventType() == XmlPullParser.START_TAG ) continue;
|
||||||
|
|
||||||
ReadUnknown(xpp);
|
ReadUnknown(xpp);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(xpp.getName().equals(unknownName));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean ReadBool(XmlPullParser xpp, boolean bDefault) throws IOException, XmlPullParserException {
|
private boolean ReadBool(XmlPullParser xpp, boolean bDefault) throws IOException, XmlPullParserException {
|
||||||
String str = ReadString(xpp);
|
String str = ReadString(xpp);
|
||||||
|
|
||||||
if ( str.equalsIgnoreCase("true") ) {
|
return str.equalsIgnoreCase("true")
|
||||||
return true;
|
|| !str.equalsIgnoreCase("false")
|
||||||
} else if ( str.equalsIgnoreCase("false") ) {
|
&& bDefault;
|
||||||
return false;
|
}
|
||||||
} else {
|
|
||||||
return bDefault;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private UUID ReadUuid(XmlPullParser xpp) throws IOException, XmlPullParserException {
|
private UUID ReadUuid(XmlPullParser xpp) throws IOException, XmlPullParserException {
|
||||||
String encoded = ReadString(xpp);
|
String encoded = ReadString(xpp);
|
||||||
@@ -1050,7 +1028,19 @@ public class ImporterV4 extends Importer {
|
|||||||
|
|
||||||
return new ProtectedString(false, ReadString(xpp));
|
return new ProtectedString(false, ReadString(xpp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ProtectedBinary createProtectedBinaryFromData(boolean protection, byte[] data) throws IOException {
|
||||||
|
if (data.length > MemUtil.BUFFER_SIZE_BYTES) {
|
||||||
|
File file = new File(streamDir, getUnusedCacheFileName());
|
||||||
|
try (FileOutputStream outputStream = new FileOutputStream(file)) {
|
||||||
|
outputStream.write(data);
|
||||||
|
}
|
||||||
|
return new ProtectedBinary(protection, file, data.length);
|
||||||
|
} else {
|
||||||
|
return new ProtectedBinary(protection, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ProtectedBinary ReadProtectedBinary(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
private ProtectedBinary ReadProtectedBinary(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||||
String ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef);
|
String ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef);
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
@@ -1068,10 +1058,13 @@ public class ImporterV4 extends Importer {
|
|||||||
|
|
||||||
byte[] buf = ProcessNode(xpp);
|
byte[] buf = ProcessNode(xpp);
|
||||||
|
|
||||||
if ( buf != null ) return new ProtectedBinary(true, buf);
|
if ( buf != null ) {
|
||||||
|
createProtectedBinaryFromData(true, buf);
|
||||||
|
}
|
||||||
|
|
||||||
String base64 = ReadString(xpp);
|
String base64 = ReadString(xpp);
|
||||||
if ( base64.length() == 0 ) return ProtectedBinary.EMPTY;
|
if ( base64.length() == 0 )
|
||||||
|
return ProtectedBinary.EMPTY;
|
||||||
|
|
||||||
byte[] data = Base64Coder.decode(base64);
|
byte[] data = Base64Coder.decode(base64);
|
||||||
|
|
||||||
@@ -1079,7 +1072,7 @@ public class ImporterV4 extends Importer {
|
|||||||
data = MemUtil.decompress(data);
|
data = MemUtil.decompress(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ProtectedBinary(false, data);
|
return createProtectedBinaryFromData(false, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String ReadString(XmlPullParser xpp) throws IOException, XmlPullParserException {
|
private String ReadString(XmlPullParser xpp) throws IOException, XmlPullParserException {
|
||||||
@@ -1105,13 +1098,14 @@ public class ImporterV4 extends Importer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ProcessNode(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
private byte[] ProcessNode(XmlPullParser xpp) throws XmlPullParserException, IOException {
|
||||||
assert(xpp.getEventType() == XmlPullParser.START_TAG);
|
//(xpp.getEventType() == XmlPullParser.START_TAG);
|
||||||
|
|
||||||
byte[] buf = null;
|
byte[] buf = null;
|
||||||
|
|
||||||
if ( xpp.getAttributeCount() > 0 ) {
|
if ( xpp.getAttributeCount() > 0 ) {
|
||||||
String protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected);
|
String protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected);
|
||||||
if ( protect != null && protect.equalsIgnoreCase(PwDatabaseV4XML.ValTrue) ) {
|
if ( protect != null && protect.equalsIgnoreCase(PwDatabaseV4XML.ValTrue) ) {
|
||||||
|
// TODO stream for encrypted data
|
||||||
String encrypted = ReadStringRaw(xpp);
|
String encrypted = ReadStringRaw(xpp);
|
||||||
|
|
||||||
if ( encrypted.length() > 0 ) {
|
if ( encrypted.length() > 0 ) {
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import com.kunzisoft.keepass.database.PwDatabaseV4;
|
|||||||
import com.kunzisoft.keepass.database.PwDbHeaderV4;
|
import com.kunzisoft.keepass.database.PwDbHeaderV4;
|
||||||
import com.kunzisoft.keepass.database.security.ProtectedBinary;
|
import com.kunzisoft.keepass.database.security.ProtectedBinary;
|
||||||
import com.kunzisoft.keepass.stream.LEDataOutputStream;
|
import com.kunzisoft.keepass.stream.LEDataOutputStream;
|
||||||
|
import com.kunzisoft.keepass.utils.MemUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class PwDbInnerHeaderOutputV4 {
|
public class PwDbInnerHeaderOutputV4 {
|
||||||
private PwDatabaseV4 db;
|
private PwDatabaseV4 db;
|
||||||
@@ -50,19 +50,18 @@ public class PwDbInnerHeaderOutputV4 {
|
|||||||
los.writeInt(streamKeySize);
|
los.writeInt(streamKeySize);
|
||||||
los.write(header.innerRandomStreamKey);
|
los.write(header.innerRandomStreamKey);
|
||||||
|
|
||||||
for (ProtectedBinary bin : db.getBinPool().binaries()) {
|
for (ProtectedBinary protectedBinary : db.getBinPool().binaries()) {
|
||||||
byte flag = PwDbHeaderV4.KdbxBinaryFlags.None;
|
byte flag = PwDbHeaderV4.KdbxBinaryFlags.None;
|
||||||
if (bin.isProtected()) {
|
if (protectedBinary.isProtected()) {
|
||||||
flag |= PwDbHeaderV4.KdbxBinaryFlags.Protected;
|
flag |= PwDbHeaderV4.KdbxBinaryFlags.Protected;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] binData = bin.getData();
|
|
||||||
los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary);
|
los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary);
|
||||||
los.writeInt(bin.length() + 1);
|
los.writeInt((int) protectedBinary.length() + 1); // TODO verify
|
||||||
los.write(flag);
|
los.write(flag);
|
||||||
los.write(binData);
|
|
||||||
|
|
||||||
Arrays.fill(binData, (byte)0);
|
MemUtil.readBytes(protectedBinary.getData(),
|
||||||
|
buffer -> los.write(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader);
|
los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader);
|
||||||
|
|||||||
@@ -350,10 +350,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
|||||||
writeObject(PwDatabaseV4XML.ElemUuid, group.getUUID());
|
writeObject(PwDatabaseV4XML.ElemUuid, group.getUUID());
|
||||||
writeObject(PwDatabaseV4XML.ElemName, group.getName());
|
writeObject(PwDatabaseV4XML.ElemName, group.getName());
|
||||||
writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes());
|
writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes());
|
||||||
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().iconId);
|
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().getIconId());
|
||||||
|
|
||||||
if (!group.getCustomIcon().equals(PwIconCustom.ZERO)) {
|
if (!group.getIconCustom().equals(PwIconCustom.ZERO)) {
|
||||||
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getCustomIcon().uuid);
|
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getIconCustom().getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
writeList(PwDatabaseV4XML.ElemTimes, group);
|
writeList(PwDatabaseV4XML.ElemTimes, group);
|
||||||
@@ -375,10 +375,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
|||||||
xml.startTag(null, PwDatabaseV4XML.ElemEntry);
|
xml.startTag(null, PwDatabaseV4XML.ElemEntry);
|
||||||
|
|
||||||
writeObject(PwDatabaseV4XML.ElemUuid, entry.getUUID());
|
writeObject(PwDatabaseV4XML.ElemUuid, entry.getUUID());
|
||||||
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().iconId);
|
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().getIconId());
|
||||||
|
|
||||||
if (!entry.getCustomIcon().equals(PwIconCustom.ZERO)) {
|
if (!entry.getIconCustom().equals(PwIconCustom.ZERO)) {
|
||||||
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getCustomIcon().uuid);
|
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getIconCustom().getUUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor());
|
writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor());
|
||||||
@@ -394,15 +394,14 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
|||||||
|
|
||||||
if (!isHistory) {
|
if (!isHistory) {
|
||||||
writeList(PwDatabaseV4XML.ElemHistory, entry.getHistory(), true);
|
writeList(PwDatabaseV4XML.ElemHistory, entry.getHistory(), true);
|
||||||
} else {
|
|
||||||
assert(entry.sizeOfHistory() == 0);
|
|
||||||
}
|
}
|
||||||
|
// else entry.sizeOfHistory() == 0
|
||||||
|
|
||||||
xml.endTag(null, PwDatabaseV4XML.ElemEntry);
|
xml.endTag(null, PwDatabaseV4XML.ElemEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void writeObject(String key, ProtectedBinary value, boolean allowRef) throws IllegalArgumentException, IllegalStateException, IOException {
|
private void writeObject(String key, ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||||
assert(key != null && value != null);
|
assert(key != null && value != null);
|
||||||
|
|
||||||
xml.startTag(null, PwDatabaseV4XML.ElemBinary);
|
xml.startTag(null, PwDatabaseV4XML.ElemBinary);
|
||||||
@@ -411,11 +410,8 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
|||||||
xml.endTag(null, PwDatabaseV4XML.ElemKey);
|
xml.endTag(null, PwDatabaseV4XML.ElemKey);
|
||||||
|
|
||||||
xml.startTag(null, PwDatabaseV4XML.ElemValue);
|
xml.startTag(null, PwDatabaseV4XML.ElemValue);
|
||||||
String strRef = null;
|
int ref = mPM.getBinPool().findKey(value);
|
||||||
if (allowRef) {
|
String strRef = Integer.toString(ref);
|
||||||
int ref = mPM.getBinPool().poolFind(value);
|
|
||||||
strRef = Integer.toString(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strRef != null) {
|
if (strRef != null) {
|
||||||
xml.attribute(null, PwDatabaseV4XML.AttrRef, strRef);
|
xml.attribute(null, PwDatabaseV4XML.AttrRef, strRef);
|
||||||
@@ -427,33 +423,85 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
|||||||
|
|
||||||
xml.endTag(null, PwDatabaseV4XML.ElemBinary);
|
xml.endTag(null, PwDatabaseV4XML.ElemBinary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO Make with pipe
|
||||||
private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException {
|
private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||||
if (value.isProtected()) {
|
try (InputStream inputStream = value.getData()) {
|
||||||
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue);
|
if (inputStream == null) {
|
||||||
|
Log.e(TAG, "Can't write a null input stream.");
|
||||||
int valLength = value.length();
|
return;
|
||||||
if (valLength > 0) {
|
}
|
||||||
byte[] encoded = new byte[valLength];
|
|
||||||
randomStream.processBytes(value.getData(), 0, valLength, encoded, 0);
|
if (value.isProtected()) {
|
||||||
|
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue);
|
||||||
xml.text(String.valueOf(Base64Coder.encode(encoded)));
|
|
||||||
}
|
try (InputStream cypherInputStream =
|
||||||
|
IOUtil.pipe(inputStream,
|
||||||
} else {
|
o -> new org.spongycastle.crypto.io.CipherOutputStream(o, randomStream))) {
|
||||||
if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) {
|
writeInputStreamInBase64(cypherInputStream);
|
||||||
xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue);
|
}
|
||||||
byte[] raw = value.getData();
|
|
||||||
byte[] compressed = MemUtil.compress(raw);
|
} else {
|
||||||
xml.text(String.valueOf(Base64Coder.encode(compressed)));
|
if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) {
|
||||||
} else {
|
|
||||||
byte[] raw = value.getData();
|
xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue);
|
||||||
xml.text(String.valueOf(Base64Coder.encode(raw)));
|
|
||||||
}
|
try (InputStream gZipInputStream =
|
||||||
|
IOUtil.pipe(inputStream, GZIPOutputStream::new, (int) value.length())) {
|
||||||
}
|
writeInputStreamInBase64(gZipInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
writeInputStreamInBase64(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeInputStreamInBase64(InputStream inputStream) throws IOException {
|
||||||
|
try (InputStream base64InputStream =
|
||||||
|
IOUtil.pipe(inputStream,
|
||||||
|
o -> new Base64OutputStream(o, DEFAULT))) {
|
||||||
|
MemUtil.readBytes(base64InputStream,
|
||||||
|
buffer -> xml.text(Arrays.toString(buffer)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*/
|
||||||
|
|
||||||
|
//*
|
||||||
|
private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||||
|
|
||||||
|
int valLength = (int) value.length();
|
||||||
|
if (valLength > 0) {
|
||||||
|
byte[] buffer = new byte[valLength];
|
||||||
|
if (valLength == value.getData().read(buffer, 0, valLength)) {
|
||||||
|
|
||||||
|
if (value.isProtected()) {
|
||||||
|
xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue);
|
||||||
|
|
||||||
|
byte[] encoded = new byte[valLength];
|
||||||
|
randomStream.processBytes(buffer, 0, valLength, encoded, 0);
|
||||||
|
xml.text(String.valueOf(Base64Coder.encode(encoded)));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (mPM.getCompressionAlgorithm() == PwCompressionAlgorithm.Gzip) {
|
||||||
|
xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue);
|
||||||
|
|
||||||
|
byte[] compressData = MemUtil.compress(buffer);
|
||||||
|
xml.text(String.valueOf(Base64Coder.encode(compressData)));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
xml.text(String.valueOf(Base64Coder.encode(buffer)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unable to read the stream of the protected binary");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//*/
|
||||||
|
|
||||||
private void writeObject(String name, String value, boolean filterXmlChars) throws IllegalArgumentException, IllegalStateException, IOException {
|
private void writeObject(String name, String value, boolean filterXmlChars) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||||
assert(name != null && value != null);
|
assert(name != null && value != null);
|
||||||
|
|
||||||
@@ -616,7 +664,7 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
|||||||
assert(binaries != null);
|
assert(binaries != null);
|
||||||
|
|
||||||
for (Entry<String, ProtectedBinary> pair : binaries.entrySet()) {
|
for (Entry<String, ProtectedBinary> pair : binaries.entrySet()) {
|
||||||
writeObject(pair.getKey(), pair.getValue(), true);
|
writeObject(pair.getKey(), pair.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,8 +749,8 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
|
|||||||
for (PwIconCustom icon : customIcons) {
|
for (PwIconCustom icon : customIcons) {
|
||||||
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem);
|
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem);
|
||||||
|
|
||||||
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid);
|
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.getUUID());
|
||||||
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.imageData)));
|
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.getImageData())));
|
||||||
|
|
||||||
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem);
|
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,12 +79,12 @@ public class PwEntryOutputV3 {
|
|||||||
// Group ID
|
// Group ID
|
||||||
mOS.write(GROUPID_FIELD_TYPE);
|
mOS.write(GROUPID_FIELD_TYPE);
|
||||||
mOS.write(LONG_FOUR);
|
mOS.write(LONG_FOUR);
|
||||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getGroupId()));
|
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getParent().getGroupId()));
|
||||||
|
|
||||||
// Image ID
|
// Image ID
|
||||||
mOS.write(IMAGEID_FIELD_TYPE);
|
mOS.write(IMAGEID_FIELD_TYPE);
|
||||||
mOS.write(LONG_FOUR);
|
mOS.write(LONG_FOUR);
|
||||||
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().iconId));
|
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().getIconId()));
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
//byte[] title = mPE.title.getBytes("UTF-8");
|
//byte[] title = mPE.title.getBytes("UTF-8");
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class PwGroupOutputV3 {
|
|||||||
// Image ID
|
// Image ID
|
||||||
mOS.write(IMAGEID_FIELD_TYPE);
|
mOS.write(IMAGEID_FIELD_TYPE);
|
||||||
mOS.write(IMAGEID_FIELD_SIZE);
|
mOS.write(IMAGEID_FIELD_SIZE);
|
||||||
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().iconId));
|
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().getIconId()));
|
||||||
|
|
||||||
// Level
|
// Level
|
||||||
mOS.write(LEVEL_FIELD_TYPE);
|
mOS.write(LEVEL_FIELD_TYPE);
|
||||||
|
|||||||
@@ -17,8 +17,10 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database.search;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.EntryHandler;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
|
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -17,7 +17,10 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database.search;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.EntryHandler;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database.search;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||||
|
import com.kunzisoft.keepass.database.PwGroup;
|
||||||
import com.kunzisoft.keepass.utils.StrUtil;
|
import com.kunzisoft.keepass.utils.StrUtil;
|
||||||
import com.kunzisoft.keepass.utils.UuidUtil;
|
import com.kunzisoft.keepass.utils.UuidUtil;
|
||||||
|
|
||||||
@@ -17,8 +17,11 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database.search;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.database.EntryHandler;
|
||||||
|
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||||
|
import com.kunzisoft.keepass.database.PwGroupV4;
|
||||||
import com.kunzisoft.keepass.utils.StrUtil;
|
import com.kunzisoft.keepass.utils.StrUtil;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.search;
|
package com.kunzisoft.keepass.database.search;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@@ -42,7 +42,7 @@ import java.util.Locale;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch, PwEntrySearch>,
|
public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch, PwEntrySearch>,
|
||||||
PwGroupSearch extends PwGroup<PwGroupSearch, PwGroupSearch, PwEntrySearch>,
|
PwGroupSearch extends PwGroup<PwGroupSearch, PwEntrySearch>,
|
||||||
PwEntrySearch extends PwEntry<PwGroupSearch>> {
|
PwEntrySearch extends PwEntry<PwGroupSearch>> {
|
||||||
|
|
||||||
private final Context mCtx;
|
private final Context mCtx;
|
||||||
@@ -54,31 +54,33 @@ public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch,
|
|||||||
private boolean omitBackup() {
|
private boolean omitBackup() {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mCtx);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mCtx);
|
||||||
return prefs.getBoolean(mCtx.getString(R.string.omitbackup_key), mCtx.getResources().getBoolean(R.bool.omitbackup_default));
|
return prefs.getBoolean(mCtx.getString(R.string.omitbackup_key), mCtx.getResources().getBoolean(R.bool.omitbackup_default));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PwGroupSearch search(PwDatabaseVersion pm, String qStr) {
|
public PwGroupSearch search(PwDatabaseVersion pm, String qStr, int max) {
|
||||||
|
|
||||||
PwGroupSearch group = pm.createGroup();
|
PwGroupSearch group = pm.createGroup();
|
||||||
group.setName(mCtx.getString(R.string.search_results));
|
group.setName("\"" + qStr + "\"");
|
||||||
group.setEntries(new ArrayList<>());
|
group.setEntries(new ArrayList<>());
|
||||||
|
|
||||||
// Search all entries
|
// Search all entries
|
||||||
Locale loc = Locale.getDefault();
|
Locale loc = Locale.getDefault();
|
||||||
qStr = qStr.toLowerCase(loc);
|
qStr = qStr.toLowerCase(loc);
|
||||||
boolean isOmitBackup = omitBackup();
|
boolean isOmitBackup = omitBackup();
|
||||||
|
|
||||||
|
// TODO Search from the current group
|
||||||
Queue<PwGroupSearch> worklist = new LinkedList<>();
|
Queue<PwGroupSearch> worklist = new LinkedList<>();
|
||||||
if (pm.getRootGroup() != null) {
|
if (pm.getRootGroup() != null) {
|
||||||
worklist.add(pm.getRootGroup());
|
worklist.add(pm.getRootGroup());
|
||||||
}
|
}
|
||||||
|
|
||||||
while (worklist.size() != 0) {
|
while (worklist.size() != 0) {
|
||||||
PwGroupSearch top = worklist.remove();
|
PwGroupSearch top = worklist.remove();
|
||||||
|
|
||||||
if (pm.isGroupSearchable(top, isOmitBackup)) {
|
if (pm.isGroupSearchable(top, isOmitBackup)) {
|
||||||
for (PwEntrySearch entry : top.getChildEntries()) {
|
for (PwEntrySearch entry : top.getChildEntries()) {
|
||||||
processEntries(entry, group.getChildEntries(), qStr, loc);
|
processEntries(entry, group.getChildEntries(), qStr, loc);
|
||||||
|
if (group.numbersOfChildEntries() >= max)
|
||||||
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (PwGroupSearch childGroup : top.getChildGroups()) {
|
for (PwGroupSearch childGroup : top.getChildGroups()) {
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database.search;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author bpellin
|
* @author bpellin
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database;
|
package com.kunzisoft.keepass.database.search;
|
||||||
|
|
||||||
public class SearchParametersV4 extends SearchParameters implements Cloneable {
|
public class SearchParametersV4 extends SearchParameters implements Cloneable {
|
||||||
public static SearchParametersV4 DEFAULT = new SearchParametersV4();
|
public static SearchParametersV4 DEFAULT = new SearchParametersV4();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||||
*
|
*
|
||||||
* This file is part of KeePass DX.
|
* This file is part of KeePass DX.
|
||||||
*
|
*
|
||||||
@@ -19,48 +19,131 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.security;
|
package com.kunzisoft.keepass.database.security;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class ProtectedBinary implements Serializable {
|
public class ProtectedBinary implements Parcelable {
|
||||||
|
|
||||||
|
private static final String TAG = ProtectedBinary.class.getName();
|
||||||
public final static ProtectedBinary EMPTY = new ProtectedBinary();
|
public final static ProtectedBinary EMPTY = new ProtectedBinary();
|
||||||
|
|
||||||
private byte[] data;
|
|
||||||
private boolean protect;
|
private boolean protect;
|
||||||
|
private byte[] data;
|
||||||
|
private File dataFile;
|
||||||
|
private int size;
|
||||||
|
|
||||||
public boolean isProtected() {
|
public boolean isProtected() {
|
||||||
return protect;
|
return protect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int length() {
|
public long length() {
|
||||||
if (data == null) {
|
if (data != null)
|
||||||
return 0;
|
return data.length;
|
||||||
}
|
if (dataFile != null)
|
||||||
|
return size;
|
||||||
return data.length;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtectedBinary() {
|
private ProtectedBinary() {
|
||||||
this(false, new byte[0]);
|
this.protect = false;
|
||||||
|
this.data = null;
|
||||||
|
this.dataFile = null;
|
||||||
|
this.size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtectedBinary(boolean enableProtection, byte[] data) {
|
public ProtectedBinary(boolean enableProtection, byte[] data) {
|
||||||
protect = enableProtection;
|
this.protect = enableProtection;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.dataFile = null;
|
||||||
}
|
if (data != null)
|
||||||
|
this.size = data.length;
|
||||||
|
else
|
||||||
// TODO: replace the byte[] with something like ByteBuffer to make the return
|
this.size = 0;
|
||||||
// value immutable, so we don't have to worry about making deep copies
|
|
||||||
public byte[] getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals(ProtectedBinary rhs) {
|
|
||||||
return (protect == rhs.protect) && Arrays.equals(data, rhs.data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ProtectedBinary(boolean enableProtection, File dataFile, int size) {
|
||||||
|
this.protect = enableProtection;
|
||||||
|
this.data = null;
|
||||||
|
this.dataFile = dataFile;
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProtectedBinary(Parcel in) {
|
||||||
|
protect = in.readByte() != 0;
|
||||||
|
in.readByteArray(data);
|
||||||
|
dataFile = new File(in.readString());
|
||||||
|
size = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getData() throws IOException {
|
||||||
|
if (data != null)
|
||||||
|
return new ByteArrayInputStream(data);
|
||||||
|
else if (dataFile != null)
|
||||||
|
return new FileInputStream(dataFile);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
data = null;
|
||||||
|
if (dataFile != null && !dataFile.delete())
|
||||||
|
Log.e(TAG, "Unable to delete temp file " + dataFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ProtectedBinary that = (ProtectedBinary) o;
|
||||||
|
return protect == that.protect &&
|
||||||
|
size == that.size &&
|
||||||
|
Arrays.equals(data, that.data) &&
|
||||||
|
dataFile != null &&
|
||||||
|
dataFile.equals(that.dataFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
|
||||||
|
int result = 0;
|
||||||
|
result = 31 * result + (protect ? 1 : 0);
|
||||||
|
result = 31 * result + dataFile.hashCode();
|
||||||
|
result = 31 * result + Integer.valueOf(size).hashCode();
|
||||||
|
result = 31 * result + Arrays.hashCode(data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeByte((byte) (protect ? 1 : 0));
|
||||||
|
dest.writeByteArray(data);
|
||||||
|
dest.writeString(dataFile.getAbsolutePath());
|
||||||
|
dest.writeInt(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<ProtectedBinary> CREATOR = new Creator<ProtectedBinary>() {
|
||||||
|
@Override
|
||||||
|
public ProtectedBinary createFromParcel(Parcel in) {
|
||||||
|
return new ProtectedBinary(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtectedBinary[] newArray(int size) {
|
||||||
|
return new ProtectedBinary[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,38 +19,66 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.database.security;
|
package com.kunzisoft.keepass.database.security;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public class ProtectedString implements Parcelable {
|
||||||
|
|
||||||
public class ProtectedString implements Serializable {
|
|
||||||
|
|
||||||
private String string;
|
|
||||||
private boolean protect;
|
private boolean protect;
|
||||||
|
private String string;
|
||||||
public boolean isProtected() {
|
|
||||||
return protect;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int length() {
|
|
||||||
if (string == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProtectedString() {
|
public ProtectedString() {
|
||||||
this(false, "");
|
this(false, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtectedString(ProtectedString toCopy) {
|
public ProtectedString(ProtectedString toCopy) {
|
||||||
this.string = toCopy.string;
|
|
||||||
this.protect = toCopy.protect;
|
this.protect = toCopy.protect;
|
||||||
|
this.string = toCopy.string;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProtectedString(boolean enableProtection, String string) {
|
public ProtectedString(boolean enableProtection, String string) {
|
||||||
protect = enableProtection;
|
this.protect = enableProtection;
|
||||||
this.string = string;
|
this.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProtectedString(Parcel in) {
|
||||||
|
protect = in.readByte() != 0;
|
||||||
|
string = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeByte((byte) (protect ? 1 : 0));
|
||||||
|
dest.writeString(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<ProtectedString> CREATOR = new Parcelable.Creator<ProtectedString>() {
|
||||||
|
@Override
|
||||||
|
public ProtectedString createFromParcel(Parcel in) {
|
||||||
|
return new ProtectedString(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProtectedString[] newArray(int size) {
|
||||||
|
return new ProtectedString[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public boolean isProtected() {
|
||||||
|
return protect;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int length() {
|
||||||
|
if (string == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
|
import com.kunzisoft.keepass.database.Database;
|
||||||
|
import com.kunzisoft.keepass.database.PwGroup;
|
||||||
import com.kunzisoft.keepass.database.PwIcon;
|
import com.kunzisoft.keepass.database.PwIcon;
|
||||||
import com.kunzisoft.keepass.database.PwNode;
|
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
|
||||||
|
|
||||||
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION;
|
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION;
|
||||||
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE;
|
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE;
|
||||||
@@ -49,9 +49,11 @@ public class GroupEditDialogFragment extends DialogFragment
|
|||||||
public static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
|
public static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
|
||||||
|
|
||||||
public static final String KEY_NAME = "KEY_NAME";
|
public static final String KEY_NAME = "KEY_NAME";
|
||||||
public static final String KEY_ICON_ID = "KEY_ICON_ID";
|
public static final String KEY_ICON = "KEY_ICON";
|
||||||
public static final String KEY_ACTION_ID = "KEY_ACTION_ID";
|
public static final String KEY_ACTION_ID = "KEY_ACTION_ID";
|
||||||
|
|
||||||
|
private Database database;
|
||||||
|
|
||||||
private EditGroupListener editGroupListener;
|
private EditGroupListener editGroupListener;
|
||||||
|
|
||||||
private EditGroupDialogAction editGroupDialogAction;
|
private EditGroupDialogAction editGroupDialogAction;
|
||||||
@@ -59,6 +61,7 @@ public class GroupEditDialogFragment extends DialogFragment
|
|||||||
private PwIcon iconGroup;
|
private PwIcon iconGroup;
|
||||||
|
|
||||||
private ImageView iconButton;
|
private ImageView iconButton;
|
||||||
|
private int iconColor;
|
||||||
|
|
||||||
public enum EditGroupDialogAction {
|
public enum EditGroupDialogAction {
|
||||||
CREATION, UPDATE, NONE;
|
CREATION, UPDATE, NONE;
|
||||||
@@ -76,10 +79,10 @@ public class GroupEditDialogFragment extends DialogFragment
|
|||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GroupEditDialogFragment build(PwNode group) {
|
public static GroupEditDialogFragment build(PwGroup group) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString(KEY_NAME, group.getDisplayTitle());
|
bundle.putString(KEY_NAME, group.getName());
|
||||||
bundle.putSerializable(KEY_ICON_ID, group.getIcon());
|
bundle.putParcelable(KEY_ICON, group.getIcon());
|
||||||
bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal());
|
bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal());
|
||||||
GroupEditDialogFragment fragment = new GroupEditDialogFragment();
|
GroupEditDialogFragment fragment = new GroupEditDialogFragment();
|
||||||
fragment.setArguments(bundle);
|
fragment.setArguments(bundle);
|
||||||
@@ -112,20 +115,21 @@ public class GroupEditDialogFragment extends DialogFragment
|
|||||||
// Retrieve the textColor to tint the icon
|
// Retrieve the textColor to tint the icon
|
||||||
int[] attrs = {android.R.attr.textColorPrimary};
|
int[] attrs = {android.R.attr.textColorPrimary};
|
||||||
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(attrs);
|
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(attrs);
|
||||||
int iconColor = ta.getColor(0, Color.WHITE);
|
iconColor = ta.getColor(0, Color.WHITE);
|
||||||
|
|
||||||
// Init elements
|
// Init elements
|
||||||
|
database = App.getDB();
|
||||||
editGroupDialogAction = EditGroupDialogAction.NONE;
|
editGroupDialogAction = EditGroupDialogAction.NONE;
|
||||||
nameGroup = "";
|
nameGroup = "";
|
||||||
iconGroup = App.getDB().getPwDatabase().getIconFactory().getFolderIcon();
|
iconGroup = database.getPwDatabase().getIconFactory().getFolderIcon();
|
||||||
|
|
||||||
if (savedInstanceState != null
|
if (savedInstanceState != null
|
||||||
&& savedInstanceState.containsKey(KEY_ACTION_ID)
|
&& savedInstanceState.containsKey(KEY_ACTION_ID)
|
||||||
&& savedInstanceState.containsKey(KEY_NAME)
|
&& savedInstanceState.containsKey(KEY_NAME)
|
||||||
&& savedInstanceState.containsKey(KEY_ICON_ID)) {
|
&& savedInstanceState.containsKey(KEY_ICON)) {
|
||||||
editGroupDialogAction = getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID));
|
editGroupDialogAction = getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID));
|
||||||
nameGroup = savedInstanceState.getString(KEY_NAME);
|
nameGroup = savedInstanceState.getString(KEY_NAME);
|
||||||
iconGroup = (PwIcon) savedInstanceState.getSerializable(KEY_ICON_ID);
|
iconGroup = savedInstanceState.getParcelable(KEY_ICON);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
@@ -135,30 +139,16 @@ public class GroupEditDialogFragment extends DialogFragment
|
|||||||
|
|
||||||
if (getArguments() != null
|
if (getArguments() != null
|
||||||
&& getArguments().containsKey(KEY_NAME)
|
&& getArguments().containsKey(KEY_NAME)
|
||||||
&& getArguments().containsKey(KEY_ICON_ID)) {
|
&& getArguments().containsKey(KEY_ICON)) {
|
||||||
nameGroup = getArguments().getString(KEY_NAME);
|
nameGroup = getArguments().getString(KEY_NAME);
|
||||||
iconGroup = (PwIcon) getArguments().getSerializable(KEY_ICON_ID);
|
iconGroup = getArguments().getParcelable(KEY_ICON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate the name
|
// populate the name
|
||||||
nameField.setText(nameGroup);
|
nameField.setText(nameGroup);
|
||||||
// populate the icon
|
// populate the icon
|
||||||
if (IconPackChooser.getSelectedIconPack(getContext()).tintable()) {
|
assignIconView();
|
||||||
App.getDB().getDrawFactory()
|
|
||||||
.assignDatabaseIconTo(
|
|
||||||
getContext(),
|
|
||||||
iconButton,
|
|
||||||
iconGroup,
|
|
||||||
true,
|
|
||||||
iconColor);
|
|
||||||
} else {
|
|
||||||
App.getDB().getDrawFactory()
|
|
||||||
.assignDatabaseIconTo(
|
|
||||||
getContext(),
|
|
||||||
iconButton,
|
|
||||||
iconGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
builder.setView(root)
|
builder.setView(root)
|
||||||
@@ -195,18 +185,26 @@ public class GroupEditDialogFragment extends DialogFragment
|
|||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assignIconView() {
|
||||||
|
database.getDrawFactory()
|
||||||
|
.assignDatabaseIconTo(
|
||||||
|
getContext(),
|
||||||
|
iconButton,
|
||||||
|
iconGroup,
|
||||||
|
iconColor);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void iconPicked(Bundle bundle) {
|
public void iconPicked(Bundle bundle) {
|
||||||
int selectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
iconGroup = bundle.getParcelable(IconPickerDialogFragment.KEY_ICON_STANDARD);
|
||||||
iconButton.setImageResource(IconPackChooser.getSelectedIconPack(getContext()).iconToResId(selectedIconID));
|
assignIconView();
|
||||||
iconGroup = App.getDB().getPwDatabase().getIconFactory().getIcon(selectedIconID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
outState.putInt(KEY_ACTION_ID, editGroupDialogAction.ordinal());
|
outState.putInt(KEY_ACTION_ID, editGroupDialogAction.ordinal());
|
||||||
outState.putString(KEY_NAME, nameGroup);
|
outState.putString(KEY_NAME, nameGroup);
|
||||||
outState.putSerializable(KEY_ICON_ID, iconGroup);
|
outState.putParcelable(KEY_ICON, iconGroup);
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,14 +37,16 @@ import android.widget.GridView;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
|
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||||
import com.kunzisoft.keepass.icons.IconPack;
|
import com.kunzisoft.keepass.icons.IconPack;
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||||
import com.kunzisoft.keepass.stylish.StylishActivity;
|
import com.kunzisoft.keepass.stylish.StylishActivity;
|
||||||
|
|
||||||
|
|
||||||
public class IconPickerDialogFragment extends DialogFragment {
|
public class IconPickerDialogFragment extends DialogFragment {
|
||||||
public static final String KEY_ICON_ID = "icon_id";
|
|
||||||
public static final int UNDEFINED_ICON_ID = -1;
|
public static final String KEY_ICON_STANDARD = "KEY_ICON_STANDARD";
|
||||||
|
|
||||||
private IconPickerListener iconPickerListener;
|
private IconPickerListener iconPickerListener;
|
||||||
private IconPack iconPack;
|
private IconPack iconPack;
|
||||||
|
|
||||||
@@ -85,7 +87,7 @@ public class IconPickerDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
currIconGridView.setOnItemClickListener((parent, v, position, id) -> {
|
currIconGridView.setOnItemClickListener((parent, v, position, id) -> {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(KEY_ICON_ID, position);
|
bundle.putParcelable(KEY_ICON_STANDARD, new PwIconStandard(position));
|
||||||
iconPickerListener.iconPicked(bundle);
|
iconPickerListener.iconPicked(bundle);
|
||||||
dismiss();
|
dismiss();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.keyboard;
|
package com.kunzisoft.keepass.dialogs;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -28,16 +28,14 @@ import android.support.v4.app.DialogFragment;
|
|||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.BuildConfig;
|
import com.kunzisoft.keepass.BuildConfig;
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.utils.Util;
|
import com.kunzisoft.keepass.utils.Util;
|
||||||
|
|
||||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
|
||||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||||
|
|
||||||
public class KeyboardExplanationDialog extends DialogFragment {
|
public class KeyboardExplanationDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
package com.kunzisoft.keepass.fileselect;
|
package com.kunzisoft.keepass.fileselect;
|
||||||
|
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
@@ -38,6 +37,9 @@ public class FileInformationDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
private static final String FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG";
|
private static final String FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG";
|
||||||
|
|
||||||
|
private View fileSizeContainerView;
|
||||||
|
private View fileModificationContainerView;
|
||||||
|
|
||||||
public static FileInformationDialogFragment newInstance(FileSelectBean fileSelectBean) {
|
public static FileInformationDialogFragment newInstance(FileSelectBean fileSelectBean) {
|
||||||
FileInformationDialogFragment fileInformationDialogFragment =
|
FileInformationDialogFragment fileInformationDialogFragment =
|
||||||
new FileInformationDialogFragment();
|
new FileInformationDialogFragment();
|
||||||
@@ -53,38 +55,44 @@ public class FileInformationDialogFragment extends DialogFragment {
|
|||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||||
View root = inflater.inflate(R.layout.file_selection_information, null);
|
View root = inflater.inflate(R.layout.file_selection_information, null);
|
||||||
|
TextView fileNameView = root.findViewById(R.id.file_filename);
|
||||||
|
TextView filePathView = root.findViewById(R.id.file_path);
|
||||||
|
fileSizeContainerView = root.findViewById(R.id.file_size_container);
|
||||||
|
TextView fileSizeView = root.findViewById(R.id.file_size);
|
||||||
|
fileModificationContainerView = root.findViewById(R.id.file_modification_container);
|
||||||
|
TextView fileModificationView = root.findViewById(R.id.file_modification);
|
||||||
|
|
||||||
if (getArguments() != null && getArguments().containsKey(FILE_SELECT_BEEN_ARG)) {
|
if (getArguments() != null && getArguments().containsKey(FILE_SELECT_BEEN_ARG)) {
|
||||||
FileSelectBean fileSelectBean = (FileSelectBean) getArguments().getSerializable(FILE_SELECT_BEEN_ARG);
|
FileSelectBean fileSelectBean = (FileSelectBean) getArguments().getSerializable(FILE_SELECT_BEEN_ARG);
|
||||||
TextView fileWarningView = (TextView) root.findViewById(R.id.file_warning);
|
|
||||||
if(fileSelectBean != null) {
|
if(fileSelectBean != null) {
|
||||||
TextView fileNameView = (TextView) root.findViewById(R.id.file_filename);
|
|
||||||
TextView filePathView = (TextView) root.findViewById(R.id.file_path);
|
|
||||||
TextView fileSizeView = (TextView) root.findViewById(R.id.file_size);
|
|
||||||
TextView fileModificationView = (TextView) root.findViewById(R.id.file_modification);
|
|
||||||
fileWarningView.setVisibility(View.GONE);
|
|
||||||
fileNameView.setText(fileSelectBean.getFileName());
|
|
||||||
filePathView.setText(Uri.decode(fileSelectBean.getFileUri().toString()));
|
filePathView.setText(Uri.decode(fileSelectBean.getFileUri().toString()));
|
||||||
fileSizeView.setText(String.valueOf(fileSelectBean.getSize()));
|
fileNameView.setText(fileSelectBean.getFileName());
|
||||||
fileModificationView.setText(DateFormat.getDateTimeInstance()
|
|
||||||
.format(fileSelectBean.getLastModification()));
|
if(fileSelectBean.notFound()) {
|
||||||
if(fileSelectBean.notFound())
|
hideFileInfo();
|
||||||
showFileNotFound(fileWarningView);
|
} else {
|
||||||
|
showFileInfo();
|
||||||
|
fileSizeView.setText(String.valueOf(fileSelectBean.getSize()));
|
||||||
|
fileModificationView.setText(DateFormat.getDateTimeInstance()
|
||||||
|
.format(fileSelectBean.getLastModification()));
|
||||||
|
}
|
||||||
} else
|
} else
|
||||||
showFileNotFound(fileWarningView);
|
hideFileInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setView(root);
|
builder.setView(root);
|
||||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(android.R.string.ok, (dialog, id) -> {});
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showFileNotFound(TextView fileWarningView) {
|
private void showFileInfo() {
|
||||||
fileWarningView.setVisibility(View.VISIBLE);
|
fileSizeContainerView.setVisibility(View.VISIBLE);
|
||||||
fileWarningView.setText(R.string.file_not_found);
|
fileModificationContainerView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideFileInfo() {
|
||||||
|
fileSizeContainerView.setVisibility(View.GONE);
|
||||||
|
fileModificationContainerView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
private static final String EXTRA_STAY = "EXTRA_STAY";
|
private static final String EXTRA_STAY = "EXTRA_STAY";
|
||||||
|
|
||||||
private FileSelectAdapter mAdapter;
|
private FileSelectAdapter mAdapter;
|
||||||
private View fileListTitle;
|
private View fileListContainer;
|
||||||
private View createButtonView;
|
private View createButtonView;
|
||||||
private View browseButtonView;
|
private View browseButtonView;
|
||||||
private View openButtonView;
|
private View openButtonView;
|
||||||
@@ -148,7 +148,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
fileHistory = App.getFileHistory();
|
fileHistory = App.getFileHistory();
|
||||||
|
|
||||||
setContentView(R.layout.file_selection);
|
setContentView(R.layout.file_selection);
|
||||||
fileListTitle = findViewById(R.id.file_list_title);
|
fileListContainer = findViewById(R.id.container_file_list);
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
toolbar.setTitle("");
|
toolbar.setTitle("");
|
||||||
@@ -284,7 +284,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
int warning = -1;
|
int warning = -1;
|
||||||
String state = Environment.getExternalStorageState();
|
String state = Environment.getExternalStorageState();
|
||||||
if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
|
if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
|
||||||
warning = R.string.warning_read_only;
|
warning = R.string.read_only_warning;
|
||||||
} else if (!state.equals(Environment.MEDIA_MOUNTED)) {
|
} else if (!state.equals(Environment.MEDIA_MOUNTED)) {
|
||||||
warning = R.string.warning_unmounted;
|
warning = R.string.warning_unmounted;
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
updateExternalStorageWarning();
|
updateExternalStorageWarning();
|
||||||
updateTitleFileListView();
|
updateFileListVisibility();
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,11 +434,11 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment");
|
createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTitleFileListView() {
|
private void updateFileListVisibility() {
|
||||||
if(mAdapter.getItemCount() == 0)
|
if(mAdapter.getItemCount() == 0)
|
||||||
fileListTitle.setVisibility(View.INVISIBLE);
|
fileListContainer.setVisibility(View.INVISIBLE);
|
||||||
else
|
else
|
||||||
fileListTitle.setVisibility(View.VISIBLE);
|
fileListContainer.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -593,7 +593,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
// Add to recent files
|
// Add to recent files
|
||||||
fileHistory.createFile(mUri, getFilename());
|
fileHistory.createFile(mUri, getFilename());
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
updateTitleFileListView();
|
updateFileListVisibility();
|
||||||
GroupActivity.launch(FileSelectActivity.this);
|
GroupActivity.launch(FileSelectActivity.this);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -629,7 +629,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
R.string.file_not_found, Toast.LENGTH_LONG)
|
R.string.file_not_found, Toast.LENGTH_LONG)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
updateTitleFileListView();
|
updateFileListVisibility();
|
||||||
}, fileHistory).execute(itemPosition);
|
}, fileHistory).execute(itemPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -647,7 +647,7 @@ public class FileSelectActivity extends StylishActivity implements
|
|||||||
new DeleteFileHistoryAsyncTask(() -> {
|
new DeleteFileHistoryAsyncTask(() -> {
|
||||||
fileHistory.deleteFile(fileSelectBean.getFileUri());
|
fileHistory.deleteFile(fileSelectBean.getFileUri());
|
||||||
mAdapter.notifyDataSetChanged();
|
mAdapter.notifyDataSetChanged();
|
||||||
updateTitleFileListView();
|
updateFileListVisibility();
|
||||||
}, fileHistory, mAdapter).execute(fileSelectBean);
|
}, fileHistory, mAdapter).execute(fileSelectBean);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,16 +88,6 @@ public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder
|
|||||||
else
|
else
|
||||||
holder.fileName.setText(fileSelectBean.getFileName());
|
holder.fileName.setText(fileSelectBean.getFileName());
|
||||||
holder.fileName.setTextSize(PreferencesUtil.getListTextSize(context));
|
holder.fileName.setTextSize(PreferencesUtil.getListTextSize(context));
|
||||||
// Set warning
|
|
||||||
if (fileSelectBean.notFound()) {
|
|
||||||
holder.fileInformation.setColorFilter(
|
|
||||||
warningColor,
|
|
||||||
android.graphics.PorterDuff.Mode.MULTIPLY);
|
|
||||||
} else {
|
|
||||||
holder.fileInformation.setColorFilter(
|
|
||||||
defaultColor,
|
|
||||||
android.graphics.PorterDuff.Mode.MULTIPLY);
|
|
||||||
}
|
|
||||||
// Click on information
|
// Click on information
|
||||||
if (fileInformationShowListener != null)
|
if (fileInformationShowListener != null)
|
||||||
holder.fileInformation.setOnClickListener(new FileInformationClickListener(fileSelectBean));
|
holder.fileInformation.setOnClickListener(new FileInformationClickListener(fileSelectBean));
|
||||||
|
|||||||
@@ -65,52 +65,50 @@ public class IconDrawableFactory {
|
|||||||
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign a default database icon to an ImageView
|
* Assign a default database icon to an ImageView and tint it if needed
|
||||||
*
|
*
|
||||||
* @param context Context to build the drawable
|
* @param context Context to build the drawable
|
||||||
* @param iv ImageView that will host the drawable
|
* @param iconView ImageView that will host the drawable
|
||||||
|
* @param tintColor Use this color to tint tintable icon
|
||||||
*/
|
*/
|
||||||
public void assignDefaultDatabaseIconTo(Context context, ImageView iv) {
|
public void assignDefaultDatabaseIconTo(Context context, ImageView iconView, int tintColor) {
|
||||||
assignDefaultDatabaseIconTo(context, iv, false, Color.WHITE);
|
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
|
||||||
|
assignDrawableTo(context,
|
||||||
|
iconView,
|
||||||
|
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
|
||||||
|
true,
|
||||||
|
tintColor);
|
||||||
|
} else {
|
||||||
|
assignDrawableTo(context,
|
||||||
|
iconView,
|
||||||
|
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
|
||||||
|
false,
|
||||||
|
Color.WHITE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign a default database icon to an ImageView and tint it
|
* Assign a database icon to an ImageView and tint it if needed
|
||||||
*
|
*
|
||||||
* @param context Context to build the drawable
|
* @param context Context to build the drawable
|
||||||
* @param iv ImageView that will host the drawable
|
* @param iconView ImageView that will host the drawable
|
||||||
*/
|
|
||||||
public void assignDefaultDatabaseIconTo(Context context, ImageView iv, boolean tint, int tintColor) {
|
|
||||||
assignDrawableTo(context, iv, IconPackChooser.getSelectedIconPack(context).getDefaultIconId(), tint, tintColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign a database icon to an ImageView
|
|
||||||
*
|
|
||||||
* @param context Context to build the drawable
|
|
||||||
* @param iv ImageView that will host the drawable
|
|
||||||
* @param icon The icon from the database
|
* @param icon The icon from the database
|
||||||
|
* @param tintColor Use this color to tint tintable icon
|
||||||
*/
|
*/
|
||||||
public void assignDatabaseIconTo(Context context, ImageView iv, PwIcon icon) {
|
public void assignDatabaseIconTo(Context context, ImageView iconView, PwIcon icon, int tintColor) {
|
||||||
assignDatabaseIconTo(context, iv, icon, false, Color.WHITE);
|
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
|
||||||
|
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
|
||||||
|
iconView,
|
||||||
|
true,
|
||||||
|
tintColor);
|
||||||
|
} else {
|
||||||
|
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
|
||||||
|
iconView,
|
||||||
|
false,
|
||||||
|
Color.WHITE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign a database icon to an ImageView and tint it
|
|
||||||
*
|
|
||||||
* @param context Context to build the drawable
|
|
||||||
* @param imageView ImageView that will host the drawable
|
|
||||||
* @param icon The icon from the database
|
|
||||||
* @param tint true will tint the drawable with tintColor
|
|
||||||
* @param tintColor Use this color if tint is true
|
|
||||||
*/
|
|
||||||
public void assignDatabaseIconTo(Context context, ImageView imageView, PwIcon icon, boolean tint, int tintColor) {
|
|
||||||
assignDrawableToImageView(getIconDrawable(context, icon, tint, tintColor),
|
|
||||||
imageView,
|
|
||||||
tint,
|
|
||||||
tintColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assign an image by its resourceId to an ImageView and tint it
|
* Assign an image by its resourceId to an ImageView and tint it
|
||||||
*
|
*
|
||||||
@@ -221,7 +219,7 @@ public class IconDrawableFactory {
|
|||||||
* @return The drawable
|
* @return The drawable
|
||||||
*/
|
*/
|
||||||
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
|
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
|
||||||
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.iconId);
|
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
|
||||||
|
|
||||||
return getIconDrawable(context, resId, isTint, tintColor);
|
return getIconDrawable(context, resId, isTint, tintColor);
|
||||||
}
|
}
|
||||||
@@ -289,14 +287,14 @@ public class IconDrawableFactory {
|
|||||||
return blank;
|
return blank;
|
||||||
}
|
}
|
||||||
|
|
||||||
Drawable draw = (Drawable) customIconMap.get(icon.uuid);
|
Drawable draw = (Drawable) customIconMap.get(icon.getUUID());
|
||||||
|
|
||||||
if (draw == null) {
|
if (draw == null) {
|
||||||
if (icon.imageData == null) {
|
if (icon.getImageData() == null) {
|
||||||
return blank;
|
return blank;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.length);
|
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
|
||||||
|
|
||||||
// Could not understand custom icon
|
// Could not understand custom icon
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
@@ -306,7 +304,7 @@ public class IconDrawableFactory {
|
|||||||
bitmap = resize(bitmap);
|
bitmap = resize(bitmap);
|
||||||
|
|
||||||
draw = new BitmapDrawable(context.getResources(), bitmap);
|
draw = new BitmapDrawable(context.getResources(), bitmap);
|
||||||
customIconMap.put(icon.uuid, draw);
|
customIconMap.put(icon.getUUID(), draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
return draw;
|
return draw;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.os.Bundle;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
import com.kunzisoft.keepass.stylish.StylishActivity;
|
import com.kunzisoft.keepass.stylish.StylishActivity;
|
||||||
@@ -45,6 +46,7 @@ public abstract class LockingActivity extends StylishActivity {
|
|||||||
private LockReceiver lockReceiver;
|
private LockReceiver lockReceiver;
|
||||||
private boolean exitLock;
|
private boolean exitLock;
|
||||||
|
|
||||||
|
protected boolean readOnly;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to start a record time,
|
* Called to start a record time,
|
||||||
@@ -72,6 +74,9 @@ public abstract class LockingActivity extends StylishActivity {
|
|||||||
lockReceiver = null;
|
lockReceiver = null;
|
||||||
|
|
||||||
exitLock = false;
|
exitLock = false;
|
||||||
|
|
||||||
|
readOnly = false;
|
||||||
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkShutdown(Activity activity) {
|
public static void checkShutdown(Activity activity) {
|
||||||
@@ -116,6 +121,12 @@ public abstract class LockingActivity extends StylishActivity {
|
|||||||
TimeoutHelper.recordTime(this);
|
TimeoutHelper.recordTime(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.kunzisoft.magikeyboard;
|
package com.kunzisoft.keepass.magikeyboard;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -9,7 +9,8 @@ import android.support.annotation.Nullable;
|
|||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.kunzisoft.keepass_model.Entry;
|
import com.kunzisoft.keepass.R;
|
||||||
|
import com.kunzisoft.keepass.model.Entry;
|
||||||
|
|
||||||
public class EntryRetrieverActivity extends AppCompatActivity {
|
public class EntryRetrieverActivity extends AppCompatActivity {
|
||||||
|
|
||||||
@@ -43,8 +44,8 @@ public class EntryRetrieverActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
// Show the notification if allowed in Preferences
|
// Show the notification if allowed in Preferences
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
if (sharedPreferences.getBoolean(getString(R.string.notification_entry_key),
|
if (sharedPreferences.getBoolean(getString(R.string.keyboard_notification_entry_key),
|
||||||
getResources().getBoolean(R.bool.notification_entry_default))) {
|
getResources().getBoolean(R.bool.keyboard_notification_entry_default))) {
|
||||||
Intent notificationIntent = new Intent(this, KeyboardEntryNotificationService.class);
|
Intent notificationIntent = new Intent(this, KeyboardEntryNotificationService.class);
|
||||||
startService(notificationIntent);
|
startService(notificationIntent);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.kunzisoft.magikeyboard;
|
package com.kunzisoft.keepass.magikeyboard;
|
||||||
|
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@@ -7,19 +7,24 @@ import android.app.Service;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v7.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.magikeyboard.receiver.NotificationDeleteBroadcastReceiver;
|
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver;
|
||||||
|
import com.kunzisoft.keepass.magikeyboard.receiver.NotificationDeleteBroadcastReceiver;
|
||||||
|
import com.kunzisoft.keepass.timeout.TimeoutHelper;
|
||||||
|
|
||||||
import static android.content.ContentValues.TAG;
|
import static com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
|
||||||
import static com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
|
|
||||||
|
|
||||||
public class KeyboardEntryNotificationService extends Service {
|
public class KeyboardEntryNotificationService extends Service {
|
||||||
|
|
||||||
|
private static final String TAG = KeyboardEntryNotificationService.class.getName();
|
||||||
|
|
||||||
private static final String CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.entry.channel";
|
private static final String CHANNEL_ID_KEYBOARD = "com.kunzisoft.keyboard.notification.entry.channel";
|
||||||
private static final String CHANNEL_NAME_KEYBOARD = "Magikeyboard notification";
|
private static final String CHANNEL_NAME_KEYBOARD = "Magikeyboard notification";
|
||||||
|
|
||||||
@@ -80,7 +85,7 @@ public class KeyboardEntryNotificationService extends Service {
|
|||||||
PendingIntent.getBroadcast(getApplicationContext(), 0, deleteIntent, 0);
|
PendingIntent.getBroadcast(getApplicationContext(), 0, deleteIntent, 0);
|
||||||
|
|
||||||
if (MagikIME.getEntryKey() != null) {
|
if (MagikIME.getEntryKey() != null) {
|
||||||
String entryTitle = getString(R.string.notification_entry_content_title_text);
|
String entryTitle = getString(R.string.keyboard_notification_entry_content_title_text);
|
||||||
String entryUsername = "";
|
String entryUsername = "";
|
||||||
if (!MagikIME.getEntryKey().getTitle().isEmpty())
|
if (!MagikIME.getEntryKey().getTitle().isEmpty())
|
||||||
entryTitle = MagikIME.getEntryKey().getTitle();
|
entryTitle = MagikIME.getEntryKey().getTitle();
|
||||||
@@ -89,40 +94,55 @@ public class KeyboardEntryNotificationService extends Service {
|
|||||||
|
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD)
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_KEYBOARD)
|
||||||
.setSmallIcon(R.drawable.ic_vpn_key_white_24dp)
|
.setSmallIcon(R.drawable.ic_vpn_key_white_24dp)
|
||||||
.setContentTitle(getString(R.string.notification_entry_content_title, entryTitle))
|
.setContentTitle(getString(R.string.keyboard_notification_entry_content_title, entryTitle))
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
.setVisibility(NotificationCompat.VISIBILITY_SECRET)
|
||||||
.setContentText(getString(R.string.notification_entry_content_text, entryUsername))
|
.setContentText(getString(R.string.keyboard_notification_entry_content_text, entryUsername))
|
||||||
.setAutoCancel(false)
|
.setAutoCancel(false)
|
||||||
.setContentIntent(null)
|
.setContentIntent(null)
|
||||||
.setDeleteIntent(pendingDeleteIntent);
|
.setDeleteIntent(pendingDeleteIntent);
|
||||||
|
|
||||||
notificationManager.cancel(notificationId);
|
notificationManager.cancel(notificationId);
|
||||||
notificationManager.notify(notificationId, builder.build());
|
notificationManager.notify(notificationId, builder.build());
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Get timeout
|
|
||||||
/*
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
notificationTimeoutMilliSecs = prefs.getInt(getString(R.string.entry_timeout_key), 100000);
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
stopTask(cleanNotificationTimer);
|
// Timeout only if notification clear is available
|
||||||
cleanNotificationTimer = new Thread(new Runnable() {
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
@Override
|
if (sharedPreferences.getBoolean(getString(R.string.keyboard_notification_entry_clear_close_key),
|
||||||
public void run() {
|
getResources().getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
|
||||||
|
String keyboardTimeout = sharedPreferences.getString(getString(R.string.keyboard_entry_timeout_key),
|
||||||
|
getString(R.string.timeout_default));
|
||||||
try {
|
try {
|
||||||
Thread.sleep(notificationTimeoutMilliSecs);
|
notificationTimeoutMilliSecs = Long.parseLong(keyboardTimeout);
|
||||||
} catch (InterruptedException e) {
|
} catch (NumberFormatException e) {
|
||||||
cleanNotificationTimer = null;
|
notificationTimeoutMilliSecs = TimeoutHelper.DEFAULT_TIMEOUT;
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
if (notificationTimeoutMilliSecs != TimeoutHelper.TIMEOUT_NEVER) {
|
||||||
|
stopTask(cleanNotificationTimer);
|
||||||
|
cleanNotificationTimer = new Thread(() -> {
|
||||||
|
int maxPos = 100;
|
||||||
|
long posDurationMills = notificationTimeoutMilliSecs / maxPos;
|
||||||
|
for (int pos = maxPos; pos > 0; --pos) {
|
||||||
|
builder.setProgress(maxPos, pos, false);
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
try {
|
||||||
|
Thread.sleep(posDurationMills);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pendingDeleteIntent.send();
|
||||||
|
} catch (PendingIntent.CanceledException e) {
|
||||||
|
Log.e(TAG, e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cleanNotificationTimer.start();
|
||||||
}
|
}
|
||||||
notificationManager.cancel(notificationId);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
cleanNotificationTimer.start();
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopTask(Thread task) {
|
private void stopTask(Thread task) {
|
||||||
@@ -130,12 +150,19 @@ public class KeyboardEntryNotificationService extends Service {
|
|||||||
task.interrupt();
|
task.interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void destroyKeyboardNotification() {
|
||||||
public void onDestroy() {
|
stopTask(cleanNotificationTimer);
|
||||||
|
cleanNotificationTimer = null;
|
||||||
unregisterReceiver(lockBroadcastReceiver);
|
unregisterReceiver(lockBroadcastReceiver);
|
||||||
pendingDeleteIntent.cancel();
|
pendingDeleteIntent.cancel();
|
||||||
|
|
||||||
notificationManager.cancel(notificationId);
|
notificationManager.cancel(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
|
||||||
|
destroyKeyboardNotification();
|
||||||
|
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.magikeyboard;
|
package com.kunzisoft.keepass.magikeyboard;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -28,24 +28,35 @@ import android.inputmethodservice.Keyboard;
|
|||||||
import android.inputmethodservice.KeyboardView;
|
import android.inputmethodservice.KeyboardView;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.support.v7.preference.PreferenceManager;
|
import android.support.v7.preference.PreferenceManager;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
import android.view.HapticFeedbackConstants;
|
import android.view.HapticFeedbackConstants;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.PopupWindow;
|
||||||
|
|
||||||
import com.kunzisoft.keepass_model.Entry;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver;
|
import com.kunzisoft.keepass.magikeyboard.adapter.FieldsAdapter;
|
||||||
|
import com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver;
|
||||||
|
import com.kunzisoft.keepass.magikeyboard.view.MagikeyboardView;
|
||||||
|
import com.kunzisoft.keepass.model.Entry;
|
||||||
|
|
||||||
import static com.kunzisoft.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
|
import static com.kunzisoft.keepass.magikeyboard.receiver.LockBroadcastReceiver.LOCK_ACTION;
|
||||||
|
|
||||||
public class MagikIME extends InputMethodService
|
public class MagikIME extends InputMethodService
|
||||||
implements KeyboardView.OnKeyboardActionListener {
|
implements KeyboardView.OnKeyboardActionListener {
|
||||||
private static final String TAG = MagikIME.class.getName();
|
private static final String TAG = MagikIME.class.getName();
|
||||||
|
|
||||||
private static final int KEY_CHANGE_KEYBOARD = 600;
|
public static final int KEY_BACK_KEYBOARD = 600;
|
||||||
|
public static final int KEY_CHANGE_KEYBOARD = 601;
|
||||||
private static final int KEY_UNLOCK = 610;
|
private static final int KEY_UNLOCK = 610;
|
||||||
private static final int KEY_LOCK = 611;
|
private static final int KEY_LOCK = 611;
|
||||||
private static final int KEY_ENTRY = 620;
|
private static final int KEY_ENTRY = 620;
|
||||||
@@ -59,6 +70,9 @@ public class MagikIME extends InputMethodService
|
|||||||
private KeyboardView keyboardView;
|
private KeyboardView keyboardView;
|
||||||
private Keyboard keyboard;
|
private Keyboard keyboard;
|
||||||
private Keyboard keyboard_entry;
|
private Keyboard keyboard_entry;
|
||||||
|
private PopupWindow popupCustomKeys;
|
||||||
|
private FieldsAdapter fieldsAdapter;
|
||||||
|
private boolean playSoundDuringCLick;
|
||||||
|
|
||||||
private LockBroadcastReceiver lockBroadcastReceiver;
|
private LockBroadcastReceiver lockBroadcastReceiver;
|
||||||
|
|
||||||
@@ -77,31 +91,63 @@ public class MagikIME extends InputMethodService
|
|||||||
registerReceiver(lockBroadcastReceiver, new IntentFilter(LOCK_ACTION));
|
registerReceiver(lockBroadcastReceiver, new IntentFilter(LOCK_ACTION));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
unregisterReceiver(lockBroadcastReceiver);
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateInputView() {
|
public View onCreateInputView() {
|
||||||
keyboardView = (KeyboardView) getLayoutInflater().inflate(R.layout.keyboard, null);
|
keyboardView = (MagikeyboardView) getLayoutInflater().inflate(R.layout.keyboard_view, null);
|
||||||
keyboard = new Keyboard(this, R.xml.keyboard_password);
|
keyboard = new Keyboard(this, R.xml.keyboard_password);
|
||||||
keyboard_entry = new Keyboard(this, R.xml.keyboard_password_entry);
|
keyboard_entry = new Keyboard(this, R.xml.keyboard_password_entry);
|
||||||
|
|
||||||
assignKeyboardView();
|
assignKeyboardView();
|
||||||
keyboardView.setOnKeyboardActionListener(this);
|
keyboardView.setOnKeyboardActionListener(this);
|
||||||
keyboardView.setPreviewEnabled(false);
|
keyboardView.setPreviewEnabled(false);
|
||||||
keyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
|
||||||
|
Context context = getBaseContext();
|
||||||
|
View popupFieldsView = LayoutInflater.from(context)
|
||||||
|
.inflate(R.layout.keyboard_popup_fields, new FrameLayout(context));
|
||||||
|
|
||||||
|
if (popupCustomKeys != null)
|
||||||
|
popupCustomKeys.dismiss();
|
||||||
|
|
||||||
|
popupCustomKeys = new PopupWindow(context);
|
||||||
|
popupCustomKeys.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
|
||||||
|
popupCustomKeys.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
|
||||||
|
popupCustomKeys.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
|
||||||
|
popupCustomKeys.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
|
||||||
|
popupCustomKeys.setContentView(popupFieldsView);
|
||||||
|
|
||||||
|
RecyclerView recyclerView = popupFieldsView.findViewById(R.id.keyboard_popup_fields_list);
|
||||||
|
fieldsAdapter = new FieldsAdapter(this);
|
||||||
|
fieldsAdapter.setOnItemClickListener(item -> getCurrentInputConnection().commitText(item.getValue(), 1));
|
||||||
|
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, true));
|
||||||
|
recyclerView.setAdapter(fieldsAdapter);
|
||||||
|
|
||||||
|
View closeView = popupFieldsView.findViewById(R.id.keyboard_popup_close);
|
||||||
|
closeView.setOnClickListener(v -> popupCustomKeys.dismiss());
|
||||||
|
|
||||||
|
// Define preferences
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
keyboardView.setHapticFeedbackEnabled(
|
||||||
|
sharedPreferences.getBoolean(
|
||||||
|
getString(R.string.keyboard_key_vibrate_key),
|
||||||
|
getResources().getBoolean(R.bool.keyboard_key_vibrate_default)));
|
||||||
|
playSoundDuringCLick = sharedPreferences.getBoolean(
|
||||||
|
getString(R.string.keyboard_key_sound_key),
|
||||||
|
getResources().getBoolean(R.bool.keyboard_key_sound_default));
|
||||||
|
|
||||||
return keyboardView;
|
return keyboardView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assignKeyboardView() {
|
private void assignKeyboardView() {
|
||||||
if (entryKey != null) {
|
if (keyboardView != null) {
|
||||||
keyboardView.setKeyboard(keyboard_entry);
|
if (entryKey != null) {
|
||||||
} else {
|
if (keyboard_entry != null)
|
||||||
keyboardView.setKeyboard(keyboard);
|
keyboardView.setKeyboard(keyboard_entry);
|
||||||
|
} else {
|
||||||
|
if (keyboard != null) {
|
||||||
|
dismissCustomKeys();
|
||||||
|
keyboardView.setKeyboard(keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,9 +173,16 @@ public class MagikIME extends InputMethodService
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onKey(int primaryCode, int[] keyCodes) {
|
public void onKey(int primaryCode, int[] keyCodes) {
|
||||||
InputConnection ic = getCurrentInputConnection();
|
InputConnection inputConnection = getCurrentInputConnection();
|
||||||
|
|
||||||
|
// Vibrate
|
||||||
|
keyboardView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||||
|
// Play a sound
|
||||||
|
if (playSoundDuringCLick)
|
||||||
|
playClick(primaryCode);
|
||||||
|
|
||||||
switch(primaryCode){
|
switch(primaryCode){
|
||||||
case KEY_CHANGE_KEYBOARD:
|
case KEY_BACK_KEYBOARD:
|
||||||
try {
|
try {
|
||||||
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||||
assert imeManager != null;
|
assert imeManager != null;
|
||||||
@@ -141,99 +194,74 @@ public class MagikIME extends InputMethodService
|
|||||||
if (imeManager != null)
|
if (imeManager != null)
|
||||||
imeManager.showInputMethodPicker();
|
imeManager.showInputMethodPicker();
|
||||||
}
|
}
|
||||||
// TODO Add a long press to choose the keyboard
|
|
||||||
break;
|
break;
|
||||||
|
case KEY_CHANGE_KEYBOARD:
|
||||||
|
InputMethodManager imeManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||||
|
if (imeManager != null)
|
||||||
|
imeManager.showInputMethodPicker();
|
||||||
|
break;
|
||||||
case KEY_UNLOCK:
|
case KEY_UNLOCK:
|
||||||
// TODO Unlock key
|
// TODO Unlock key
|
||||||
break;
|
break;
|
||||||
case KEY_ENTRY:
|
case KEY_ENTRY:
|
||||||
|
deleteEntryKey(this);
|
||||||
Intent intent = new Intent(this, EntryRetrieverActivity.class);
|
Intent intent = new Intent(this, EntryRetrieverActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
break;
|
break;
|
||||||
case KEY_LOCK:
|
case KEY_LOCK:
|
||||||
deleteEntryKey(this);
|
deleteEntryKey(this);
|
||||||
|
dismissCustomKeys();
|
||||||
break;
|
break;
|
||||||
case KEY_USERNAME:
|
case KEY_USERNAME:
|
||||||
if (entryKey != null) {
|
if (entryKey != null) {
|
||||||
InputConnection inputConnection = getCurrentInputConnection();
|
|
||||||
inputConnection.commitText(entryKey.getUsername(), 1);
|
inputConnection.commitText(entryKey.getUsername(), 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEY_PASSWORD:
|
case KEY_PASSWORD:
|
||||||
if (entryKey != null) {
|
if (entryKey != null) {
|
||||||
InputConnection inputConnection = getCurrentInputConnection();
|
|
||||||
inputConnection.commitText(entryKey.getPassword(), 1);
|
inputConnection.commitText(entryKey.getPassword(), 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEY_URL:
|
case KEY_URL:
|
||||||
if (entryKey != null) {
|
if (entryKey != null) {
|
||||||
InputConnection inputConnection = getCurrentInputConnection();
|
|
||||||
inputConnection.commitText(entryKey.getUrl(), 1);
|
inputConnection.commitText(entryKey.getUrl(), 1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KEY_FIELDS:
|
case KEY_FIELDS:
|
||||||
// TODO Fields key
|
fieldsAdapter.setFields(entryKey.getCustomFields());
|
||||||
|
popupCustomKeys.showAtLocation(keyboardView, Gravity.END | Gravity.TOP, 0, 0);
|
||||||
|
break;
|
||||||
case Keyboard.KEYCODE_DELETE :
|
case Keyboard.KEYCODE_DELETE :
|
||||||
ic.deleteSurroundingText(1, 0);
|
inputConnection.deleteSurroundingText(1, 0);
|
||||||
break;
|
break;
|
||||||
case Keyboard.KEYCODE_DONE:
|
case Keyboard.KEYCODE_DONE:
|
||||||
ic.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
inputConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean(getString(R.string.key_vibrate_key), getResources().getBoolean(R.bool.key_vibrate_default)))
|
|
||||||
vibrate();
|
|
||||||
|
|
||||||
if (sharedPreferences.getBoolean(getString(R.string.key_sound_key), getResources().getBoolean(R.bool.key_sound_default)))
|
|
||||||
playClick(primaryCode);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPress(int primaryCode) {
|
public void onPress(int primaryCode) {}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void onRelease(int primaryCode) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRelease(int primaryCode) {
|
public void onText(CharSequence text) {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onText(CharSequence text) {
|
public void swipeDown() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void swipeDown() {
|
public void swipeLeft() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void swipeLeft() {
|
public void swipeRight() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void swipeRight() {
|
public void swipeUp() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void swipeUp() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private void vibrate() {
|
|
||||||
// TODO better vibration
|
|
||||||
/*
|
|
||||||
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
|
|
||||||
if (vibrator != null) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
vibrator.vibrate(VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE));
|
|
||||||
} else {
|
|
||||||
vibrator.vibrate(50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
private void playClick(int keyCode){
|
private void playClick(int keyCode){
|
||||||
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
|
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
|
||||||
@@ -249,4 +277,18 @@ public class MagikIME extends InputMethodService
|
|||||||
default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
|
default: am.playSoundEffect(AudioManager.FX_KEYPRESS_STANDARD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void dismissCustomKeys() {
|
||||||
|
if (popupCustomKeys != null)
|
||||||
|
popupCustomKeys.dismiss();
|
||||||
|
if (fieldsAdapter != null)
|
||||||
|
fieldsAdapter.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
dismissCustomKeys();
|
||||||
|
unregisterReceiver(lockBroadcastReceiver);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package com.kunzisoft.keepass.magikeyboard.adapter;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.R;
|
||||||
|
import com.kunzisoft.keepass.model.Field;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FieldsAdapter extends RecyclerView.Adapter<FieldsAdapter.FieldViewHolder> {
|
||||||
|
|
||||||
|
private LayoutInflater inflater;
|
||||||
|
private List<Field> fields;
|
||||||
|
private OnItemClickListener listener;
|
||||||
|
|
||||||
|
public FieldsAdapter(Context context) {
|
||||||
|
this.inflater = LayoutInflater.from(context);
|
||||||
|
this.fields = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFields(List<Field> fields) {
|
||||||
|
this.fields = fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnItemClickListener(OnItemClickListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public FieldViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false);
|
||||||
|
return new FieldViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull FieldViewHolder holder, int position) {
|
||||||
|
Field field = fields.get(position);
|
||||||
|
holder.name.setText(field.getName());
|
||||||
|
holder.bind(field, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return fields.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
fields.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnItemClickListener {
|
||||||
|
void onItemClick(Field item);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FieldViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
TextView name;
|
||||||
|
|
||||||
|
FieldViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
name = itemView.findViewById(R.id.keyboard_popup_field_item_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(final Field item, final OnItemClickListener listener) {
|
||||||
|
itemView.setOnClickListener(v -> listener.onItemClick(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.kunzisoft.magikeyboard.receiver;
|
package com.kunzisoft.keepass.magikeyboard.receiver;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.kunzisoft.magikeyboard.receiver;
|
package com.kunzisoft.keepass.magikeyboard.receiver;
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
import com.kunzisoft.magikeyboard.KeyboardEntryNotificationService;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.magikeyboard.MagikIME;
|
import com.kunzisoft.keepass.magikeyboard.KeyboardEntryNotificationService;
|
||||||
import com.kunzisoft.magikeyboard.R;
|
import com.kunzisoft.keepass.magikeyboard.MagikIME;
|
||||||
|
|
||||||
public class NotificationDeleteBroadcastReceiver extends BroadcastReceiver {
|
public class NotificationDeleteBroadcastReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@@ -15,8 +15,8 @@ public class NotificationDeleteBroadcastReceiver extends BroadcastReceiver {
|
|||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
// Clear the entry if define in preferences
|
// Clear the entry if define in preferences
|
||||||
SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
if (sharedPreferences.getBoolean(context.getString(R.string.notification_entry_clear_close_key),
|
if (sharedPreferences.getBoolean(context.getString(R.string.keyboard_notification_entry_clear_close_key),
|
||||||
context.getResources().getBoolean(R.bool.notification_entry_clear_close_default))) {
|
context.getResources().getBoolean(R.bool.keyboard_notification_entry_clear_close_default))) {
|
||||||
MagikIME.deleteEntryKey(context);
|
MagikIME.deleteEntryKey(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.kunzisoft.keepass.magikeyboard.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.inputmethodservice.Keyboard;
|
||||||
|
import android.inputmethodservice.KeyboardView;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import static com.kunzisoft.keepass.magikeyboard.MagikIME.KEY_BACK_KEYBOARD;
|
||||||
|
import static com.kunzisoft.keepass.magikeyboard.MagikIME.KEY_CHANGE_KEYBOARD;
|
||||||
|
|
||||||
|
public class MagikeyboardView extends KeyboardView {
|
||||||
|
|
||||||
|
public MagikeyboardView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
public MagikeyboardView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onLongPress(Keyboard.Key key) {
|
||||||
|
// TODO Action on long press
|
||||||
|
if (key.codes[0] == KEY_BACK_KEYBOARD) {
|
||||||
|
getOnKeyboardActionListener().onKey(KEY_CHANGE_KEYBOARD, null);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
//Log.d("LatinKeyboardView", "KEY: " + key.codes[0]);
|
||||||
|
return super.onLongPress(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package com.kunzisoft.keepass_model;
|
package com.kunzisoft.keepass.model;
|
||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class Entry implements Parcelable {
|
public class Entry implements Parcelable {
|
||||||
|
|
||||||
@@ -13,14 +12,14 @@ public class Entry implements Parcelable {
|
|||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private String url;
|
private String url;
|
||||||
private Map<String, String> customFields;
|
private List<Field> customFields;
|
||||||
|
|
||||||
public Entry() {
|
public Entry() {
|
||||||
this.title = "";
|
this.title = "";
|
||||||
this.username = "";
|
this.username = "";
|
||||||
this.password = "";
|
this.password = "";
|
||||||
this.url = "";
|
this.url = "";
|
||||||
this.customFields = new HashMap<>();
|
this.customFields = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Entry(Parcel in) {
|
protected Entry(Parcel in) {
|
||||||
@@ -29,7 +28,7 @@ public class Entry implements Parcelable {
|
|||||||
password = in.readString();
|
password = in.readString();
|
||||||
url = in.readString();
|
url = in.readString();
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
customFields = in.readHashMap(String.class.getClassLoader());
|
customFields = in.readArrayList(Field.class.getClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<Entry> CREATOR = new Creator<Entry>() {
|
public static final Creator<Entry> CREATOR = new Creator<Entry>() {
|
||||||
@@ -76,16 +75,12 @@ public class Entry implements Parcelable {
|
|||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getCustomFieldsKeys() {
|
public List<Field> getCustomFields() {
|
||||||
return customFields.keySet();
|
return customFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCustomField(String key) {
|
public void addCustomField(Field field) {
|
||||||
return customFields.get(key);
|
this.customFields.add(field);
|
||||||
}
|
|
||||||
|
|
||||||
public void setCustomField(String key, String value) {
|
|
||||||
this.customFields.put(key, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,6 +94,6 @@ public class Entry implements Parcelable {
|
|||||||
parcel.writeString(username);
|
parcel.writeString(username);
|
||||||
parcel.writeString(password);
|
parcel.writeString(password);
|
||||||
parcel.writeString(url);
|
parcel.writeString(url);
|
||||||
parcel.writeMap(customFields);
|
parcel.writeArray(customFields.toArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
59
app/src/main/java/com/kunzisoft/keepass/model/Field.java
Normal file
59
app/src/main/java/com/kunzisoft/keepass/model/Field.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package com.kunzisoft.keepass.model;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public class Field implements Parcelable {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public Field(String name, String value) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Field(Parcel in) {
|
||||||
|
this.name = in.readString();
|
||||||
|
this.value = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<Field> CREATOR = new Creator<Field>() {
|
||||||
|
@Override
|
||||||
|
public Field createFromParcel(Parcel in) {
|
||||||
|
return new Field(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Field[] newArray(int size) {
|
||||||
|
return new Field[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(name);
|
||||||
|
dest.writeString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -239,11 +239,13 @@ public class NotificationCopyingService extends Service {
|
|||||||
}
|
}
|
||||||
countingDownTask = null;
|
countingDownTask = null;
|
||||||
notificationManager.cancel(myNotificationId);
|
notificationManager.cancel(myNotificationId);
|
||||||
try {
|
// Clean password only if no next field
|
||||||
clipboardHelper.cleanClipboard();
|
if (nextFields.size() <= 0)
|
||||||
} catch (SamsungClipboardException e) {
|
try {
|
||||||
Log.e(TAG, "Clipboard can't be cleaned", e);
|
clipboardHelper.cleanClipboard();
|
||||||
}
|
} catch (SamsungClipboardException e) {
|
||||||
|
Log.e(TAG, "Clipboard can't be cleaned", e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
countingDownTask.start();
|
countingDownTask.start();
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ import com.getkeepsafe.taptargetview.TapTarget;
|
|||||||
import com.getkeepsafe.taptargetview.TapTargetView;
|
import com.getkeepsafe.taptargetview.TapTargetView;
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.activities.GroupActivity;
|
import com.kunzisoft.keepass.activities.GroupActivity;
|
||||||
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
import com.kunzisoft.keepass.activities.IntentBuildLauncher;
|
||||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
import com.kunzisoft.keepass.autofill.AutofillHelper;
|
import com.kunzisoft.keepass.autofill.AutofillHelper;
|
||||||
import com.kunzisoft.keepass.compat.ClipDataCompat;
|
import com.kunzisoft.keepass.compat.ClipDataCompat;
|
||||||
@@ -69,6 +69,8 @@ import com.kunzisoft.keepass.fileselect.KeyFileHelper;
|
|||||||
import com.kunzisoft.keepass.fingerprint.FingerPrintAnimatedVector;
|
import com.kunzisoft.keepass.fingerprint.FingerPrintAnimatedVector;
|
||||||
import com.kunzisoft.keepass.fingerprint.FingerPrintExplanationDialog;
|
import com.kunzisoft.keepass.fingerprint.FingerPrintExplanationDialog;
|
||||||
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
|
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
|
||||||
|
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||||
|
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||||
import com.kunzisoft.keepass.stylish.StylishActivity;
|
import com.kunzisoft.keepass.stylish.StylishActivity;
|
||||||
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment;
|
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment;
|
||||||
@@ -124,6 +126,8 @@ public class PasswordActivity extends StylishActivity
|
|||||||
private CompoundButton checkboxDefaultDatabaseView;
|
private CompoundButton checkboxDefaultDatabaseView;
|
||||||
private CompoundButton.OnCheckedChangeListener enableButtonOncheckedChangeListener;
|
private CompoundButton.OnCheckedChangeListener enableButtonOncheckedChangeListener;
|
||||||
|
|
||||||
|
private boolean readOnly;
|
||||||
|
|
||||||
private DefaultCheckChange defaultCheckChange;
|
private DefaultCheckChange defaultCheckChange;
|
||||||
private ValidateButtonViewClickListener validateButtonViewClickListener;
|
private ValidateButtonViewClickListener validateButtonViewClickListener;
|
||||||
|
|
||||||
@@ -139,16 +143,23 @@ public class PasswordActivity extends StylishActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void launch(
|
public static void launch(
|
||||||
Activity act,
|
Activity activity,
|
||||||
String fileName,
|
String fileName,
|
||||||
String keyFile) throws FileNotFoundException {
|
String keyFile) throws FileNotFoundException {
|
||||||
verifyFileNameUriFromLaunch(fileName);
|
verifyFileNameUriFromLaunch(fileName);
|
||||||
|
|
||||||
Intent intent = new Intent(act, PasswordActivity.class);
|
buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
|
||||||
|
// only to avoid visible flickering when redirecting
|
||||||
|
activity.startActivityForResult(intent, RESULT_CANCELED);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void buildAndLaunchIntent(Activity activity, String fileName, String keyFile,
|
||||||
|
IntentBuildLauncher intentBuildLauncher) {
|
||||||
|
Intent intent = new Intent(activity, PasswordActivity.class);
|
||||||
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
|
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
|
||||||
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
|
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
|
||||||
// only to avoid visible flickering when redirecting
|
intentBuildLauncher.startActivityForResult(intent);
|
||||||
act.startActivityForResult(intent, RESULT_CANCELED);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void launchForKeyboardResult(
|
public static void launchForKeyboardResult(
|
||||||
@@ -158,17 +169,16 @@ public class PasswordActivity extends StylishActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void launchForKeyboardResult(
|
public static void launchForKeyboardResult(
|
||||||
Activity act,
|
Activity activity,
|
||||||
String fileName,
|
String fileName,
|
||||||
String keyFile) throws FileNotFoundException {
|
String keyFile) throws FileNotFoundException {
|
||||||
verifyFileNameUriFromLaunch(fileName);
|
verifyFileNameUriFromLaunch(fileName);
|
||||||
|
|
||||||
Intent intent = new Intent(act, PasswordActivity.class);
|
buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
|
||||||
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
|
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
|
||||||
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
|
// only to avoid visible flickering when redirecting
|
||||||
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
|
activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
|
||||||
// only to avoid visible flickering when redirecting
|
});
|
||||||
act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@@ -181,20 +191,19 @@ public class PasswordActivity extends StylishActivity
|
|||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
public static void launchForAutofillResult(
|
public static void launchForAutofillResult(
|
||||||
Activity act,
|
Activity activity,
|
||||||
String fileName,
|
String fileName,
|
||||||
String keyFile,
|
String keyFile,
|
||||||
AssistStructure assistStructure) throws FileNotFoundException {
|
AssistStructure assistStructure) throws FileNotFoundException {
|
||||||
verifyFileNameUriFromLaunch(fileName);
|
verifyFileNameUriFromLaunch(fileName);
|
||||||
|
|
||||||
if ( assistStructure != null ) {
|
if ( assistStructure != null ) {
|
||||||
Intent intent = new Intent(act, PasswordActivity.class);
|
buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
|
||||||
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
|
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
||||||
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
|
activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||||
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
});
|
||||||
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
|
||||||
} else {
|
} else {
|
||||||
launch(act, fileName, keyFile);
|
launch(activity, fileName, keyFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +252,7 @@ public class PasswordActivity extends StylishActivity
|
|||||||
case LockingActivity.RESULT_EXIT_LOCK:
|
case LockingActivity.RESULT_EXIT_LOCK:
|
||||||
case Activity.RESULT_CANCELED:
|
case Activity.RESULT_CANCELED:
|
||||||
setEmptyViews();
|
setEmptyViews();
|
||||||
App.getDB().clear();
|
App.getDB().clear(getApplicationContext());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,6 +285,8 @@ public class PasswordActivity extends StylishActivity
|
|||||||
checkboxKeyfileView = findViewById(R.id.keyfile_checkox);
|
checkboxKeyfileView = findViewById(R.id.keyfile_checkox);
|
||||||
checkboxDefaultDatabaseView = findViewById(R.id.default_database);
|
checkboxDefaultDatabaseView = findViewById(R.id.default_database);
|
||||||
|
|
||||||
|
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState);
|
||||||
|
|
||||||
View browseView = findViewById(R.id.browse_button);
|
View browseView = findViewById(R.id.browse_button);
|
||||||
keyFileHelper = new KeyFileHelper(PasswordActivity.this);
|
keyFileHelper = new KeyFileHelper(PasswordActivity.this);
|
||||||
browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener());
|
browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener());
|
||||||
@@ -326,8 +337,6 @@ public class PasswordActivity extends StylishActivity
|
|||||||
autofillHelper = new AutofillHelper();
|
autofillHelper = new AutofillHelper();
|
||||||
autofillHelper.retrieveAssistStructure(getIntent());
|
autofillHelper.retrieveAssistStructure(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAndPerformedEducation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -380,11 +389,17 @@ public class PasswordActivity extends StylishActivity
|
|||||||
.execute(getIntent());
|
.execute(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
|
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check and display learning views
|
* Check and display learning views
|
||||||
* Displays the explanation for a database opening with fingerprints if available
|
* Displays the explanation for a database opening with fingerprints if available
|
||||||
*/
|
*/
|
||||||
private void checkAndPerformedEducation() {
|
private void checkAndPerformedEducation(Menu menu) {
|
||||||
if (PreferencesUtil.isEducationScreensEnabled(this)) {
|
if (PreferencesUtil.isEducationScreensEnabled(this)) {
|
||||||
|
|
||||||
if (!PreferencesUtil.isEducationUnlockPerformed(this)) {
|
if (!PreferencesUtil.isEducationUnlockPerformed(this)) {
|
||||||
@@ -394,7 +409,7 @@ public class PasswordActivity extends StylishActivity
|
|||||||
getString(R.string.education_unlock_title),
|
getString(R.string.education_unlock_title),
|
||||||
getString(R.string.education_unlock_summary))
|
getString(R.string.education_unlock_summary))
|
||||||
.dimColor(R.color.green)
|
.dimColor(R.color.green)
|
||||||
.icon(ContextCompat.getDrawable(this, R.mipmap.ic_launcher_round))
|
.icon(ContextCompat.getDrawable(getApplicationContext(), R.mipmap.ic_launcher_round))
|
||||||
.textColorInt(Color.WHITE)
|
.textColorInt(Color.WHITE)
|
||||||
.tintTarget(false)
|
.tintTarget(false)
|
||||||
.cancelable(true),
|
.cancelable(true),
|
||||||
@@ -402,6 +417,44 @@ public class PasswordActivity extends StylishActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onTargetClick(TapTargetView view) {
|
public void onTargetClick(TapTargetView view) {
|
||||||
super.onTargetClick(view);
|
super.onTargetClick(view);
|
||||||
|
performedReadOnlyEducation(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOuterCircleClick(TapTargetView view) {
|
||||||
|
super.onOuterCircleClick(view);
|
||||||
|
view.dismiss(false);
|
||||||
|
performedReadOnlyEducation(menu);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// TODO make a period for donation
|
||||||
|
PreferencesUtil.saveEducationPreference(PasswordActivity.this,
|
||||||
|
R.string.education_unlock_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and display learning views
|
||||||
|
* Displays read-only if available
|
||||||
|
*/
|
||||||
|
private void performedReadOnlyEducation(Menu menu) {
|
||||||
|
if (!PreferencesUtil.isEducationReadOnlyPerformed(this)) {
|
||||||
|
try {
|
||||||
|
TapTargetView.showFor(this,
|
||||||
|
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_open_file_read_mode_key,
|
||||||
|
getString(R.string.education_read_only_title),
|
||||||
|
getString(R.string.education_read_only_summary))
|
||||||
|
.textColorInt(Color.WHITE)
|
||||||
|
.tintTarget(true)
|
||||||
|
.cancelable(true),
|
||||||
|
new TapTargetView.Listener() {
|
||||||
|
@Override
|
||||||
|
public void onTargetClick(TapTargetView view) {
|
||||||
|
super.onTargetClick(view);
|
||||||
|
MenuItem editItem = menu.findItem(R.id.menu_open_file_read_mode_key);
|
||||||
|
onOptionsItemSelected(editItem);
|
||||||
checkAndPerformedEducationForFingerprint();
|
checkAndPerformedEducationForFingerprint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,11 +463,13 @@ public class PasswordActivity extends StylishActivity
|
|||||||
super.onOuterCircleClick(view);
|
super.onOuterCircleClick(view);
|
||||||
view.dismiss(false);
|
view.dismiss(false);
|
||||||
checkAndPerformedEducationForFingerprint();
|
checkAndPerformedEducationForFingerprint();
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// TODO make a period for donation
|
PreferencesUtil.saveEducationPreference(this,
|
||||||
PreferencesUtil.saveEducationPreference(PasswordActivity.this, R.string.education_unlock_key);
|
R.string.education_read_only_key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// If icon not visible
|
||||||
|
Log.w(TAG, "Can't performed education for entry's edition");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -883,7 +938,7 @@ public class PasswordActivity extends StylishActivity
|
|||||||
private void loadDatabase(String password, Uri keyfile) {
|
private void loadDatabase(String password, Uri keyfile) {
|
||||||
// Clear before we load
|
// Clear before we load
|
||||||
Database database = App.getDB();
|
Database database = App.getDB();
|
||||||
database.clear();
|
database.clear(getApplicationContext());
|
||||||
// Clear the shutdown flag
|
// Clear the shutdown flag
|
||||||
App.clearShutdown();
|
App.clearShutdown();
|
||||||
|
|
||||||
@@ -953,14 +1008,14 @@ public class PasswordActivity extends StylishActivity
|
|||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||||
assistStructure = autofillHelper.getAssistStructure();
|
assistStructure = autofillHelper.getAssistStructure();
|
||||||
if (assistStructure != null) {
|
if (assistStructure != null) {
|
||||||
GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure);
|
GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure, readOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (assistStructure == null) {
|
if (assistStructure == null) {
|
||||||
if (entrySelectionMode) {
|
if (entrySelectionMode) {
|
||||||
GroupActivity.launchForKeyboardResult(PasswordActivity.this);
|
GroupActivity.launchForKeyboardResult(PasswordActivity.this, readOnly);
|
||||||
} else {
|
} else {
|
||||||
GroupActivity.launch(PasswordActivity.this);
|
GroupActivity.launch(PasswordActivity.this, readOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -968,16 +1023,35 @@ public class PasswordActivity extends StylishActivity
|
|||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
|
// Read menu
|
||||||
|
inflater.inflate(R.menu.open_file, menu);
|
||||||
|
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key));
|
||||||
|
|
||||||
MenuUtil.defaultMenuInflater(inflater, menu);
|
MenuUtil.defaultMenuInflater(inflater, menu);
|
||||||
|
|
||||||
|
// Fingerprint menu
|
||||||
if (!fingerprintMustBeConfigured
|
if (!fingerprintMustBeConfigured
|
||||||
&& prefsNoBackup.contains(getPreferenceKeyValue()) )
|
&& prefsNoBackup.contains(getPreferenceKeyValue()) )
|
||||||
inflater.inflate(R.menu.fingerprint, menu);
|
inflater.inflate(R.menu.fingerprint, menu);
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
|
|
||||||
|
// Show education views
|
||||||
|
new Handler().post(() -> checkAndPerformedEducation(menu));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void changeOpenFileReadIcon(MenuItem togglePassword) {
|
||||||
|
if ( readOnly ) {
|
||||||
|
togglePassword.setTitle(R.string.menu_file_selection_read_only);
|
||||||
|
togglePassword.setIcon(R.drawable.ic_read_only_white_24dp);
|
||||||
|
} else {
|
||||||
|
togglePassword.setTitle(R.string.menu_open_file_read_and_write);
|
||||||
|
togglePassword.setIcon(R.drawable.ic_read_write_white_24dp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
@@ -985,6 +1059,10 @@ public class PasswordActivity extends StylishActivity
|
|||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
finish();
|
finish();
|
||||||
break;
|
break;
|
||||||
|
case R.id.menu_open_file_read_mode_key:
|
||||||
|
readOnly = !readOnly;
|
||||||
|
changeOpenFileReadIcon(item);
|
||||||
|
break;
|
||||||
case R.id.menu_fingerprint_remove_key:
|
case R.id.menu_fingerprint_remove_key:
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
deleteEntryKey();
|
deleteEntryKey();
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
|
||||||
*
|
|
||||||
* This file is part of KeePass DX.
|
|
||||||
*
|
|
||||||
* KeePass DX is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* KeePass DX is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
package com.kunzisoft.keepass.search;
|
|
||||||
|
|
||||||
import android.app.SearchManager;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
|
||||||
import com.kunzisoft.keepass.activities.ListNodesActivity;
|
|
||||||
import com.kunzisoft.keepass.app.App;
|
|
||||||
import com.kunzisoft.keepass.database.Database;
|
|
||||||
import com.kunzisoft.keepass.database.PwGroup;
|
|
||||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public class SearchResultsActivity extends ListNodesActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(getLayoutInflater().inflate(R.layout.search_results, null));
|
|
||||||
|
|
||||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
||||||
toolbar.setTitle(getString(R.string.search_label));
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
assert getSupportActionBar() != null;
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
|
||||||
groupNameView = findViewById(R.id.group_name);
|
|
||||||
|
|
||||||
attachFragmentToContentView();
|
|
||||||
|
|
||||||
View notFoundView = findViewById(R.id.not_found_container);
|
|
||||||
View listContainer = findViewById(R.id.nodes_list_fragment_container);
|
|
||||||
|
|
||||||
if ( mCurrentGroup == null || mCurrentGroup.numbersOfChildEntries() < 1 ) {
|
|
||||||
listContainer.setVisibility(View.GONE);
|
|
||||||
notFoundView.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
listContainer.setVisibility(View.VISIBLE);
|
|
||||||
notFoundView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
|
|
||||||
Database mDb = App.getDB();
|
|
||||||
// Likely the app has been killed exit the activity
|
|
||||||
if ( ! mDb.getLoaded() ) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
return mDb.search(getSearchStr(getIntent()).trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
|
||||||
|
|
||||||
MenuInflater inflater = getMenuInflater();
|
|
||||||
MenuUtil.contributionMenuInflater(inflater, menu);
|
|
||||||
inflater.inflate(R.menu.default_menu, menu);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
switch ( item.getItemId() ) {
|
|
||||||
case android.R.id.home:
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getSearchStr(Intent queryIntent) {
|
|
||||||
// get and process search query here
|
|
||||||
final String queryAction = queryIntent.getAction();
|
|
||||||
if ( Intent.ACTION_SEARCH.equals(queryAction) ) {
|
|
||||||
return queryIntent.getStringExtra(SearchManager.QUERY);
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,8 @@ import android.content.Intent;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.database.PwEntry;
|
import com.kunzisoft.keepass.database.PwEntry;
|
||||||
import com.kunzisoft.keepass_model.Entry;
|
import com.kunzisoft.keepass.model.Entry;
|
||||||
|
import com.kunzisoft.keepass.model.Field;
|
||||||
|
|
||||||
public class EntrySelectionHelper {
|
public class EntrySelectionHelper {
|
||||||
|
|
||||||
@@ -37,9 +38,11 @@ public class EntrySelectionHelper {
|
|||||||
entryModel.setUsername(entry.getUsername());
|
entryModel.setUsername(entry.getUsername());
|
||||||
entryModel.setPassword(entry.getPassword());
|
entryModel.setPassword(entry.getPassword());
|
||||||
entryModel.setUrl(entry.getUrl());
|
entryModel.setUrl(entry.getUrl());
|
||||||
// TODO Fields
|
|
||||||
if (entry.containsCustomFields()) {
|
if (entry.containsCustomFields()) {
|
||||||
//entryModel.setCustomField(entry.getFields());
|
entry.getFields()
|
||||||
|
.doActionToAllCustomProtectedField(
|
||||||
|
(key, value) -> entryModel.addCustomField(
|
||||||
|
new Field(key, value.toString())));
|
||||||
}
|
}
|
||||||
|
|
||||||
mReplyIntent.putExtra(
|
mReplyIntent.putExtra(
|
||||||
|
|||||||
@@ -17,21 +17,16 @@
|
|||||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
package com.kunzisoft.magikeyboard.settings;
|
package com.kunzisoft.keepass.settings;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v7.app.AppCompatActivity;
|
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import com.kunzisoft.magikeyboard.R;
|
import com.kunzisoft.keepass.R;
|
||||||
|
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
public class MagikIMESettings extends LockingActivity {
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
public class MagikIMESettings extends AppCompatActivity {
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -49,26 +44,6 @@ public class MagikIMESettings extends AppCompatActivity {
|
|||||||
.replace(R.id.fragment_container, new MagikIMESettingsFragment())
|
.replace(R.id.fragment_container, new MagikIMESettingsFragment())
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove after all dev
|
|
||||||
try {
|
|
||||||
Class<?> underDevClass = Class.forName("com.kunzisoft.keepass.dialogs.UnderDevelopmentFeatureDialogFragment");
|
|
||||||
Constructor<?> constructor = underDevClass.getConstructor();
|
|
||||||
Object object = constructor.newInstance();
|
|
||||||
Method showMethod = underDevClass.getMethod("show", FragmentManager.class, String.class);
|
|
||||||
showMethod.invoke(object, getSupportFragmentManager(), "magikeyboard_dev_dialog");
|
|
||||||
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (InstantiationException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
package com.kunzisoft.magikeyboard.settings;
|
package com.kunzisoft.keepass.settings;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
import com.kunzisoft.magikeyboard.R;
|
import com.kunzisoft.keepass.R;
|
||||||
|
|
||||||
public class MagikIMESettingsFragment extends PreferenceFragmentCompat {
|
public class MagikIMESettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
// Load the preferences from an XML resource
|
// Load the preferences from an XML resource
|
||||||
setPreferencesFromResource(R.xml.ime_preferences, rootKey);
|
setPreferencesFromResource(R.xml.keyboard_preferences, rootKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,7 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.kunzisoft.keepass.BuildConfig;
|
import com.kunzisoft.keepass.BuildConfig;
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
|
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
|
||||||
import com.kunzisoft.keepass.app.App;
|
import com.kunzisoft.keepass.app.App;
|
||||||
import com.kunzisoft.keepass.database.Database;
|
import com.kunzisoft.keepass.database.Database;
|
||||||
import com.kunzisoft.keepass.dialogs.ProFeatureDialogFragment;
|
import com.kunzisoft.keepass.dialogs.ProFeatureDialogFragment;
|
||||||
@@ -50,7 +51,7 @@ import com.kunzisoft.keepass.dialogs.UnavailableFeatureDialogFragment;
|
|||||||
import com.kunzisoft.keepass.dialogs.UnderDevelopmentFeatureDialogFragment;
|
import com.kunzisoft.keepass.dialogs.UnderDevelopmentFeatureDialogFragment;
|
||||||
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
|
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
|
||||||
import com.kunzisoft.keepass.icons.IconPackChooser;
|
import com.kunzisoft.keepass.icons.IconPackChooser;
|
||||||
import com.kunzisoft.keepass.keyboard.KeyboardExplanationDialog;
|
import com.kunzisoft.keepass.dialogs.KeyboardExplanationDialogFragment;
|
||||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseDescriptionPreferenceDialogFragmentCompat;
|
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseDescriptionPreferenceDialogFragmentCompat;
|
||||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat;
|
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat;
|
||||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseKeyDerivationPreferenceDialogFragmentCompat;
|
import com.kunzisoft.keepass.settings.preferenceDialogFragment.DatabaseKeyDerivationPreferenceDialogFragmentCompat;
|
||||||
@@ -59,7 +60,6 @@ import com.kunzisoft.keepass.settings.preferenceDialogFragment.MemoryUsagePrefer
|
|||||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.ParallelismPreferenceDialogFragmentCompat;
|
import com.kunzisoft.keepass.settings.preferenceDialogFragment.ParallelismPreferenceDialogFragmentCompat;
|
||||||
import com.kunzisoft.keepass.settings.preferenceDialogFragment.RoundsPreferenceDialogFragmentCompat;
|
import com.kunzisoft.keepass.settings.preferenceDialogFragment.RoundsPreferenceDialogFragmentCompat;
|
||||||
import com.kunzisoft.keepass.stylish.Stylish;
|
import com.kunzisoft.keepass.stylish.Stylish;
|
||||||
import com.kunzisoft.magikeyboard.settings.MagikIMESettings;
|
|
||||||
|
|
||||||
public class NestedSettingsFragment extends PreferenceFragmentCompat
|
public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||||
implements Preference.OnPreferenceClickListener {
|
implements Preference.OnPreferenceClickListener {
|
||||||
@@ -72,6 +72,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
private static final int REQUEST_CODE_AUTOFILL = 5201;
|
private static final int REQUEST_CODE_AUTOFILL = 5201;
|
||||||
|
|
||||||
|
private Database database;
|
||||||
|
private boolean databaseReadOnly;
|
||||||
|
|
||||||
private int count = 0;
|
private int count = 0;
|
||||||
|
|
||||||
private Preference roundPref;
|
private Preference roundPref;
|
||||||
@@ -79,14 +82,24 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
private Preference parallelismPref;
|
private Preference parallelismPref;
|
||||||
|
|
||||||
public static NestedSettingsFragment newInstance(Screen key) {
|
public static NestedSettingsFragment newInstance(Screen key) {
|
||||||
|
return newInstance(key, ReadOnlyHelper.READ_ONLY_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NestedSettingsFragment newInstance(Screen key, boolean databaseReadOnly) {
|
||||||
NestedSettingsFragment fragment = new NestedSettingsFragment();
|
NestedSettingsFragment fragment = new NestedSettingsFragment();
|
||||||
// supply arguments to bundle.
|
// supply arguments to bundle.
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putInt(TAG_KEY, key.ordinal());
|
args.putInt(TAG_KEY, key.ordinal());
|
||||||
|
ReadOnlyHelper.putReadOnlyInBundle(args, databaseReadOnly);
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
@@ -110,6 +123,10 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
if (getArguments() != null)
|
if (getArguments() != null)
|
||||||
key = getArguments().getInt(TAG_KEY);
|
key = getArguments().getInt(TAG_KEY);
|
||||||
|
|
||||||
|
database = App.getDB();
|
||||||
|
databaseReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
|
||||||
|
databaseReadOnly = database.isReadOnly() || databaseReadOnly;
|
||||||
|
|
||||||
// Load the preferences from an XML resource
|
// Load the preferences from an XML resource
|
||||||
switch (Screen.values()[key]) {
|
switch (Screen.values()[key]) {
|
||||||
case APPLICATION:
|
case APPLICATION:
|
||||||
@@ -285,8 +302,8 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
Preference keyboardPreference = findPreference(getString(R.string.magic_keyboard_key));
|
Preference keyboardPreference = findPreference(getString(R.string.magic_keyboard_key));
|
||||||
keyboardPreference.setOnPreferenceClickListener(preference -> {
|
keyboardPreference.setOnPreferenceClickListener(preference -> {
|
||||||
if (getFragmentManager() != null) {
|
if (getFragmentManager() != null) {
|
||||||
KeyboardExplanationDialog fingerPrintDialog = new KeyboardExplanationDialog();
|
KeyboardExplanationDialogFragment keyboardDialog = new KeyboardExplanationDialogFragment();
|
||||||
fingerPrintDialog.show(getFragmentManager(), "keyboardExplanationDialog");
|
keyboardDialog.show(getFragmentManager(), "keyboardExplanationDialog");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -306,23 +323,22 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
case DATABASE:
|
case DATABASE:
|
||||||
setPreferencesFromResource(R.xml.database_preferences, rootKey);
|
setPreferencesFromResource(R.xml.database_preferences, rootKey);
|
||||||
|
|
||||||
Database db = App.getDB();
|
if (database.getLoaded()) {
|
||||||
if (db.getLoaded()) {
|
|
||||||
|
|
||||||
PreferenceCategory dbGeneralPrefCategory = (PreferenceCategory) findPreference(getString(R.string.database_general_key));
|
PreferenceCategory dbGeneralPrefCategory = (PreferenceCategory) findPreference(getString(R.string.database_general_key));
|
||||||
|
|
||||||
// Db name
|
// Db name
|
||||||
Preference dbNamePref = findPreference(getString(R.string.database_name_key));
|
Preference dbNamePref = findPreference(getString(R.string.database_name_key));
|
||||||
if ( db.containsName() ) {
|
if ( database.containsName() ) {
|
||||||
dbNamePref.setSummary(db.getName());
|
dbNamePref.setSummary(database.getName());
|
||||||
} else {
|
} else {
|
||||||
dbGeneralPrefCategory.removePreference(dbNamePref);
|
dbGeneralPrefCategory.removePreference(dbNamePref);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Db description
|
// Db description
|
||||||
Preference dbDescriptionPref = findPreference(getString(R.string.database_description_key));
|
Preference dbDescriptionPref = findPreference(getString(R.string.database_description_key));
|
||||||
if ( db.containsDescription() ) {
|
if ( database.containsDescription() ) {
|
||||||
dbDescriptionPref.setSummary(db.getDescription());
|
dbDescriptionPref.setSummary(database.getDescription());
|
||||||
} else {
|
} else {
|
||||||
dbGeneralPrefCategory.removePreference(dbDescriptionPref);
|
dbGeneralPrefCategory.removePreference(dbDescriptionPref);
|
||||||
}
|
}
|
||||||
@@ -331,9 +347,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
SwitchPreference recycleBinPref = (SwitchPreference) findPreference(getString(R.string.recycle_bin_key));
|
SwitchPreference recycleBinPref = (SwitchPreference) findPreference(getString(R.string.recycle_bin_key));
|
||||||
// TODO Recycle
|
// TODO Recycle
|
||||||
dbGeneralPrefCategory.removePreference(recycleBinPref); // To delete
|
dbGeneralPrefCategory.removePreference(recycleBinPref); // To delete
|
||||||
if (db.isRecycleBinAvailable()) {
|
if (database.isRecycleBinAvailable()) {
|
||||||
|
|
||||||
recycleBinPref.setChecked(db.isRecycleBinEnabled());
|
recycleBinPref.setChecked(database.isRecycleBinEnabled());
|
||||||
recycleBinPref.setEnabled(false);
|
recycleBinPref.setEnabled(false);
|
||||||
} else {
|
} else {
|
||||||
dbGeneralPrefCategory.removePreference(recycleBinPref);
|
dbGeneralPrefCategory.removePreference(recycleBinPref);
|
||||||
@@ -341,27 +357,27 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
// Version
|
// Version
|
||||||
Preference dbVersionPref = findPreference(getString(R.string.database_version_key));
|
Preference dbVersionPref = findPreference(getString(R.string.database_version_key));
|
||||||
dbVersionPref.setSummary(db.getVersion());
|
dbVersionPref.setSummary(database.getVersion());
|
||||||
|
|
||||||
// Encryption Algorithm
|
// Encryption Algorithm
|
||||||
Preference algorithmPref = findPreference(getString(R.string.encryption_algorithm_key));
|
Preference algorithmPref = findPreference(getString(R.string.encryption_algorithm_key));
|
||||||
algorithmPref.setSummary(db.getEncryptionAlgorithmName(getResources()));
|
algorithmPref.setSummary(database.getEncryptionAlgorithmName(getResources()));
|
||||||
|
|
||||||
// Key derivation function
|
// Key derivation function
|
||||||
Preference kdfPref = findPreference(getString(R.string.key_derivation_function_key));
|
Preference kdfPref = findPreference(getString(R.string.key_derivation_function_key));
|
||||||
kdfPref.setSummary(db.getKeyDerivationName(getResources()));
|
kdfPref.setSummary(database.getKeyDerivationName(getResources()));
|
||||||
|
|
||||||
// Round encryption
|
// Round encryption
|
||||||
roundPref = findPreference(getString(R.string.transform_rounds_key));
|
roundPref = findPreference(getString(R.string.transform_rounds_key));
|
||||||
roundPref.setSummary(db.getNumberKeyEncryptionRoundsAsString());
|
roundPref.setSummary(database.getNumberKeyEncryptionRoundsAsString());
|
||||||
|
|
||||||
// Memory Usage
|
// Memory Usage
|
||||||
memoryPref = findPreference(getString(R.string.memory_usage_key));
|
memoryPref = findPreference(getString(R.string.memory_usage_key));
|
||||||
memoryPref.setSummary(db.getMemoryUsageAsString());
|
memoryPref.setSummary(database.getMemoryUsageAsString());
|
||||||
|
|
||||||
// Parallelism
|
// Parallelism
|
||||||
parallelismPref = findPreference(getString(R.string.parallelism_key));
|
parallelismPref = findPreference(getString(R.string.parallelism_key));
|
||||||
parallelismPref.setSummary(db.getParallelismAsString());
|
parallelismPref.setSummary(database.getParallelismAsString());
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.e(getClass().getName(), "Database isn't ready");
|
Log.e(getClass().getName(), "Database isn't ready");
|
||||||
@@ -480,18 +496,16 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
|
|
||||||
assert getFragmentManager() != null;
|
assert getFragmentManager() != null;
|
||||||
|
|
||||||
DialogFragment dialogFragment = null;
|
boolean otherDialogFragment = false;
|
||||||
|
|
||||||
|
DialogFragment dialogFragment = null;
|
||||||
if (preference.getKey().equals(getString(R.string.database_name_key))) {
|
if (preference.getKey().equals(getString(R.string.database_name_key))) {
|
||||||
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||||
}
|
} else if (preference.getKey().equals(getString(R.string.database_description_key))) {
|
||||||
else if (preference.getKey().equals(getString(R.string.database_description_key))) {
|
|
||||||
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||||
}
|
} else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
|
||||||
else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
|
|
||||||
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||||
}
|
} else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
|
||||||
else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
|
|
||||||
DatabaseKeyDerivationPreferenceDialogFragmentCompat keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
DatabaseKeyDerivationPreferenceDialogFragmentCompat keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||||
// Add other prefs to manage
|
// Add other prefs to manage
|
||||||
if (roundPref != null)
|
if (roundPref != null)
|
||||||
@@ -501,24 +515,23 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
if (parallelismPref != null)
|
if (parallelismPref != null)
|
||||||
keyDerivationDialogFragment.setParallelismPreference(parallelismPref);
|
keyDerivationDialogFragment.setParallelismPreference(parallelismPref);
|
||||||
dialogFragment = keyDerivationDialogFragment;
|
dialogFragment = keyDerivationDialogFragment;
|
||||||
}
|
} else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
|
||||||
else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
|
|
||||||
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||||
}
|
} else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
|
||||||
else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
|
|
||||||
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||||
}
|
} else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
|
||||||
else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
|
|
||||||
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.getKey());
|
||||||
|
} else {
|
||||||
|
otherDialogFragment = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dialogFragment != null) {
|
if (dialogFragment != null && !databaseReadOnly) {
|
||||||
dialogFragment.setTargetFragment(this, 0);
|
dialogFragment.setTargetFragment(this, 0);
|
||||||
dialogFragment.show(getFragmentManager(), null);
|
dialogFragment.show(getFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could not be handled here. Try with the super method.
|
// Could not be handled here. Try with the super method.
|
||||||
else {
|
else if (otherDialogFragment) {
|
||||||
super.onDisplayPreferenceDialog(preference);
|
super.onDisplayPreferenceDialog(preference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -538,6 +551,12 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
ReadOnlyHelper.onSaveInstanceState(outState, databaseReadOnly);
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
// TODO encapsulate
|
// TODO encapsulate
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.settings;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||||
@@ -54,9 +55,22 @@ public class PreferencesUtil {
|
|||||||
sharedPreferencesEditor.apply();
|
sharedPreferencesEditor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean showUsernamesListEntries(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getBoolean(context.getString(R.string.list_entries_show_username_key),
|
||||||
|
context.getResources().getBoolean(R.bool.list_entries_show_username_default));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the text size in SP, verify the integrity of the size stored in preference
|
||||||
|
*/
|
||||||
public static float getListTextSize(Context ctx) {
|
public static float getListTextSize(Context ctx) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||||
return Float.parseFloat(prefs.getString(ctx.getString(R.string.list_size_key), ctx.getString(R.string.list_size_default)));
|
String defaultSizeString = ctx.getString(R.string.list_size_default);
|
||||||
|
String listSize = prefs.getString(ctx.getString(R.string.list_size_key), defaultSizeString);
|
||||||
|
if (!Arrays.asList(ctx.getResources().getStringArray(R.array.list_size_values)).contains(listSize))
|
||||||
|
listSize = defaultSizeString;
|
||||||
|
return Float.parseFloat(listSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getDefaultPasswordLength(Context ctx) {
|
public static int getDefaultPasswordLength(Context ctx) {
|
||||||
@@ -139,12 +153,26 @@ public class PreferencesUtil {
|
|||||||
ctx.getResources().getBoolean(R.bool.auto_open_file_uri_default));
|
ctx.getResources().getBoolean(R.bool.auto_open_file_uri_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isFirstTimeAskAllowCopyPasswordAndProtectedFields(Context ctx) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||||
|
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_first_time_key),
|
||||||
|
ctx.getResources().getBoolean(R.bool.allow_copy_password_first_time_default));
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean allowCopyPasswordAndProtectedFields(Context ctx) {
|
public static boolean allowCopyPasswordAndProtectedFields(Context ctx) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||||
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_key),
|
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_key),
|
||||||
ctx.getResources().getBoolean(R.bool.allow_copy_password_default));
|
ctx.getResources().getBoolean(R.bool.allow_copy_password_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setAllowCopyPasswordAndProtectedFields(Context ctx, boolean allowCopy) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||||
|
prefs.edit()
|
||||||
|
.putBoolean(ctx.getString(R.string.allow_copy_password_first_time_key), false)
|
||||||
|
.putBoolean(ctx.getString(R.string.allow_copy_password_key), allowCopy)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
public static String getIconPackSelectedId(Context context) {
|
public static String getIconPackSelectedId(Context context) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
return prefs.getString(
|
return prefs.getString(
|
||||||
@@ -158,6 +186,12 @@ public class PreferencesUtil {
|
|||||||
context.getResources().getBoolean(R.bool.allow_no_password_default));
|
context.getResources().getBoolean(R.bool.allow_no_password_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean enableReadOnlyDatabase(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
return prefs.getBoolean(context.getString(R.string.enable_read_only_key),
|
||||||
|
context.getResources().getBoolean(R.bool.enable_read_only_default));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All preference keys associated with education
|
* All preference keys associated with education
|
||||||
*/
|
*/
|
||||||
@@ -166,6 +200,7 @@ public class PreferencesUtil {
|
|||||||
R.string.education_select_db_key,
|
R.string.education_select_db_key,
|
||||||
R.string.education_open_link_db_key,
|
R.string.education_open_link_db_key,
|
||||||
R.string.education_unlock_key,
|
R.string.education_unlock_key,
|
||||||
|
R.string.education_read_only_key,
|
||||||
R.string.education_search_key,
|
R.string.education_search_key,
|
||||||
R.string.education_new_node_key,
|
R.string.education_new_node_key,
|
||||||
R.string.education_sort_key,
|
R.string.education_sort_key,
|
||||||
@@ -245,6 +280,18 @@ public class PreferencesUtil {
|
|||||||
context.getResources().getBoolean(R.bool.education_unlock_default));
|
context.getResources().getBoolean(R.bool.education_unlock_default));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the explanatory view of the database read-only has already been displayed.
|
||||||
|
*
|
||||||
|
* @param context The context to open the SharedPreferences
|
||||||
|
* @return boolean value of education_read_only_key key
|
||||||
|
*/
|
||||||
|
public static boolean isEducationReadOnlyPerformed(Context context) {
|
||||||
|
SharedPreferences prefs = getEducationSharedPreferences(context);
|
||||||
|
return prefs.getBoolean(context.getString(R.string.education_read_only_key),
|
||||||
|
context.getResources().getBoolean(R.bool.education_read_only_default));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the explanatory view of search has already been displayed.
|
* Determines whether the explanatory view of search has already been displayed.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import android.support.v7.widget.Toolbar;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.R;
|
import com.kunzisoft.keepass.R;
|
||||||
|
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
|
||||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||||
|
|
||||||
|
|
||||||
@@ -39,17 +40,18 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
|
|||||||
|
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
|
|
||||||
public static void launch(Activity activity) {
|
public static void launch(Activity activity, boolean readOnly) {
|
||||||
Intent i = new Intent(activity, SettingsActivity.class);
|
Intent intent = new Intent(activity, SettingsActivity.class);
|
||||||
activity.startActivity(i);
|
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
|
||||||
|
activity.startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void launch(Activity activity, boolean checkLock) {
|
public static void launch(Activity activity, boolean readOnly, boolean checkLock) {
|
||||||
// To avoid flickering when launch settings in a LockingActivity
|
// To avoid flickering when launch settings in a LockingActivity
|
||||||
if (!checkLock)
|
if (!checkLock)
|
||||||
launch(activity);
|
launch(activity, readOnly);
|
||||||
else if (LockingActivity.checkTimeIsAllowedOrFinish(activity)) {
|
else if (LockingActivity.checkTimeIsAllowedOrFinish(activity)) {
|
||||||
launch(activity);
|
launch(activity, readOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +116,7 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
|
|||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
R.anim.slide_in_left, R.anim.slide_out_right)
|
||||||
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED)
|
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, readOnly), TAG_NESTED)
|
||||||
.addToBackStack(TAG_NESTED)
|
.addToBackStack(TAG_NESTED)
|
||||||
.commit();
|
.commit();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.kunzisoft.keepass.stream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public interface ActionReadBytes {
|
||||||
|
/**
|
||||||
|
* Called after each buffer fill
|
||||||
|
* @param buffer filled
|
||||||
|
*/
|
||||||
|
void doAction(byte[] buffer) throws IOException;
|
||||||
|
}
|
||||||
@@ -19,11 +19,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.stream;
|
package com.kunzisoft.keepass.stream;
|
||||||
|
|
||||||
import com.kunzisoft.keepass.utils.Types;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.UUID;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
/** Little endian version of the DataInputStream
|
/** Little endian version of the DataInputStream
|
||||||
@@ -32,7 +30,7 @@ import java.util.UUID;
|
|||||||
*/
|
*/
|
||||||
public class LEDataInputStream extends InputStream {
|
public class LEDataInputStream extends InputStream {
|
||||||
|
|
||||||
public static final long INT_TO_LONG_MASK = 0xffffffffL;
|
private static final long INT_TO_LONG_MASK = 0xffffffffL;
|
||||||
|
|
||||||
private InputStream baseStream;
|
private InputStream baseStream;
|
||||||
|
|
||||||
@@ -106,8 +104,9 @@ public class LEDataInputStream extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] readBytes(int length) throws IOException {
|
public byte[] readBytes(int length) throws IOException {
|
||||||
|
// TODO Exception max length < buffer size
|
||||||
byte[] buf = new byte[length];
|
byte[] buf = new byte[length];
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while ( count < length ) {
|
while ( count < length ) {
|
||||||
int read = read(buf, count, length - count);
|
int read = read(buf, count, length - count);
|
||||||
@@ -126,6 +125,33 @@ public class LEDataInputStream extends InputStream {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException {
|
||||||
|
int bufferSize = 256 * 3; // TODO Buffer size
|
||||||
|
byte[] buffer = new byte[bufferSize];
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
int read = 0;
|
||||||
|
while ( offset < length && read != -1) {
|
||||||
|
|
||||||
|
// To reduce the buffer for the last bytes reads
|
||||||
|
if (length - offset < bufferSize) {
|
||||||
|
bufferSize = length - offset;
|
||||||
|
buffer = new byte[bufferSize];
|
||||||
|
}
|
||||||
|
read = read(buffer, 0, bufferSize);
|
||||||
|
|
||||||
|
// To get only the bytes read
|
||||||
|
byte[] optimizedBuffer;
|
||||||
|
if (read >= 0 && buffer.length > read) {
|
||||||
|
optimizedBuffer = Arrays.copyOf(buffer, read);
|
||||||
|
} else {
|
||||||
|
optimizedBuffer = buffer;
|
||||||
|
}
|
||||||
|
actionReadBytes.doAction(optimizedBuffer);
|
||||||
|
offset += read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static int readUShort(InputStream is) throws IOException {
|
public static int readUShort(InputStream is) throws IOException {
|
||||||
byte[] buf = new byte[2];
|
byte[] buf = new byte[2];
|
||||||
|
|
||||||
@@ -146,11 +172,11 @@ public class LEDataInputStream extends InputStream {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static int readUShort( byte[] buf, int offset ) {
|
public static int readUShort( byte[] buf, int offset ) {
|
||||||
return (buf[offset + 0] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8);
|
return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long readLong( byte buf[], int offset ) {
|
public static long readLong( byte buf[], int offset ) {
|
||||||
return ((long)buf[offset + 0] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8)
|
return ((long)buf[offset] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8)
|
||||||
+ (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24)
|
+ (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24)
|
||||||
+ (((long)buf[offset + 4] & 0xFF) << 32) + (((long)buf[offset + 5] & 0xFF) << 40)
|
+ (((long)buf[offset + 4] & 0xFF) << 32) + (((long)buf[offset + 5] & 0xFF) << 40)
|
||||||
+ (((long)buf[offset + 6] & 0xFF) << 48) + (((long)buf[offset + 7] & 0xFF) << 56);
|
+ (((long)buf[offset + 6] & 0xFF) << 48) + (((long)buf[offset + 7] & 0xFF) << 56);
|
||||||
@@ -180,14 +206,8 @@ public class LEDataInputStream extends InputStream {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static int readInt( byte buf[], int offset ) {
|
public static int readInt( byte buf[], int offset ) {
|
||||||
return (buf[offset + 0] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8) + ((buf[offset + 2] & 0xFF) << 16)
|
return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8) + ((buf[offset + 2] & 0xFF) << 16)
|
||||||
+ ((buf[offset + 3] & 0xFF) << 24);
|
+ ((buf[offset + 3] & 0xFF) << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID readUUID() throws IOException {
|
|
||||||
byte[] buf = readBytes(16);
|
|
||||||
|
|
||||||
return Types.bytestoUUID(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ public class ProgressTaskDialogFragment extends DialogFragment implements Progre
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(DialogInterface dialog) {
|
public void onDismiss(DialogInterface dialog) {
|
||||||
super.onDismiss(dialog);
|
|
||||||
Util.unlockScreenOrientation(getActivity());
|
Util.unlockScreenOrientation(getActivity());
|
||||||
|
super.onDismiss(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void stop(AppCompatActivity activity) {
|
public static void stop(AppCompatActivity activity) {
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ public class TimeoutHelper {
|
|||||||
|
|
||||||
private static final String TAG = "TimeoutHelper";
|
private static final String TAG = "TimeoutHelper";
|
||||||
|
|
||||||
private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
public static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
|
||||||
|
public static final long TIMEOUT_NEVER = -1; // Infinite
|
||||||
|
|
||||||
public static void recordTime(Activity act) {
|
public static void recordTime(Activity act) {
|
||||||
// Record timeout time in case timeout service is killed
|
// Record timeout time in case timeout service is killed
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
@@ -56,9 +57,9 @@ public class TimeoutHelper {
|
|||||||
long cur_time = System.currentTimeMillis();
|
long cur_time = System.currentTimeMillis();
|
||||||
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(act);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(act);
|
||||||
long timeout_start = prefs.getLong(act.getString(R.string.timeout_key), -1);
|
long timeout_start = prefs.getLong(act.getString(R.string.timeout_key), TIMEOUT_NEVER);
|
||||||
// The timeout never started
|
// The timeout never started
|
||||||
if (timeout_start == -1) {
|
if (timeout_start == TIMEOUT_NEVER) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ public class TimeoutHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We are set to never timeout
|
// We are set to never timeout
|
||||||
if (timeout == -1) {
|
if (timeout == TIMEOUT_NEVER) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,32 +19,195 @@
|
|||||||
*/
|
*/
|
||||||
package com.kunzisoft.keepass.utils;
|
package com.kunzisoft.keepass.utils;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.kunzisoft.keepass.stream.ActionReadBytes;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PipedInputStream;
|
||||||
|
import java.io.PipedOutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
import java.util.zip.GZIPOutputStream;
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
public class MemUtil {
|
public class MemUtil {
|
||||||
|
|
||||||
|
private static final String TAG = MemUtil.class.getName();
|
||||||
|
public static final int BUFFER_SIZE_BYTES = 3 * 128;
|
||||||
|
|
||||||
|
public static void copyStream(InputStream in, OutputStream out) throws IOException {
|
||||||
|
byte[] buf = new byte[BUFFER_SIZE_BYTES];
|
||||||
|
int read;
|
||||||
|
try {
|
||||||
|
while ((read = in.read(buf)) != -1) {
|
||||||
|
out.write(buf, 0, read);
|
||||||
|
}
|
||||||
|
} catch (OutOfMemoryError error) {
|
||||||
|
throw new IOException(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void readBytes(@NonNull InputStream inputStream, ActionReadBytes actionReadBytes)
|
||||||
|
throws IOException {
|
||||||
|
byte[] buffer = new byte[MemUtil.BUFFER_SIZE_BYTES];
|
||||||
|
int read = 0;
|
||||||
|
while (read != -1) {
|
||||||
|
read = inputStream.read(buffer, 0, buffer.length);
|
||||||
|
if (read != -1) {
|
||||||
|
byte[] optimizedBuffer;
|
||||||
|
if (buffer.length == read) {
|
||||||
|
optimizedBuffer = buffer;
|
||||||
|
} else {
|
||||||
|
optimizedBuffer = Arrays.copyOf(buffer, read);
|
||||||
|
}
|
||||||
|
actionReadBytes.doAction(optimizedBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
byte[] buffer = new byte[BUFFER_SIZE_BYTES];
|
||||||
|
// To create the last buffer who is smaller
|
||||||
|
long numberOfFullBuffer = length / buffer.length;
|
||||||
|
long sizeOfFullBuffers = numberOfFullBuffer * buffer.length;
|
||||||
|
int read = 0;
|
||||||
|
//if (protectedBinary.length() > 0) {
|
||||||
|
while (read < length) {
|
||||||
|
// Create the last smaller buffer
|
||||||
|
if (read >= sizeOfFullBuffers)
|
||||||
|
buffer = new byte[(int) (length % buffer.length)];
|
||||||
|
read += inputStream.read(buffer, 0, buffer.length);
|
||||||
|
actionReadBytes.doAction(buffer);
|
||||||
|
}
|
||||||
|
//*/
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] decompress(byte[] input) throws IOException {
|
public static byte[] decompress(byte[] input) throws IOException {
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(input);
|
ByteArrayInputStream bais = new ByteArrayInputStream(input);
|
||||||
GZIPInputStream gzis = new GZIPInputStream(bais);
|
GZIPInputStream gzis = new GZIPInputStream(bais);
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
Util.copyStream(gzis, baos);
|
copyStream(gzis, baos);
|
||||||
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] compress(byte[] input) throws IOException {
|
|
||||||
ByteArrayInputStream bais = new ByteArrayInputStream(input);
|
|
||||||
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
GZIPOutputStream gzos = new GZIPOutputStream(baos);
|
|
||||||
Util.copyStream(bais, gzos);
|
|
||||||
gzos.close();
|
|
||||||
|
|
||||||
return baos.toByteArray();
|
return baos.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] compress(byte[] input) throws IOException {
|
||||||
|
ByteArrayInputStream bais = new ByteArrayInputStream(input);
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
GZIPOutputStream gzos = new GZIPOutputStream(baos);
|
||||||
|
copyStream(bais, gzos);
|
||||||
|
gzos.close();
|
||||||
|
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compresses the input data using GZip and outputs the compressed data.
|
||||||
|
*
|
||||||
|
* @param input
|
||||||
|
* An {@link InputStream} containing the input raw data.
|
||||||
|
*
|
||||||
|
* @return An {@link InputStream} to the compressed data.
|
||||||
|
*/
|
||||||
|
public static InputStream compress(final InputStream input) {
|
||||||
|
final PipedInputStream compressedDataStream = new PipedInputStream(3 * 128);
|
||||||
|
Log.d(TAG, "About to compress input data using gzip asynchronously...");
|
||||||
|
PipedOutputStream compressionOutput;
|
||||||
|
GZIPOutputStream gzipCompressedDataStream = null;
|
||||||
|
try {
|
||||||
|
compressionOutput = new PipedOutputStream(compressedDataStream);
|
||||||
|
gzipCompressedDataStream = new GZIPOutputStream(compressionOutput);
|
||||||
|
IOUtils.copy(input, gzipCompressedDataStream);
|
||||||
|
Log.e(TAG, "Successfully compressed input data using gzip.");
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to compress input data.", e);
|
||||||
|
} finally {
|
||||||
|
if (gzipCompressedDataStream != null) {
|
||||||
|
try {
|
||||||
|
gzipCompressedDataStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to close gzip output stream.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compressedDataStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// For writing to a Parcel
|
||||||
|
public static <K extends Parcelable,V extends Parcelable> void writeParcelableMap(
|
||||||
|
Parcel parcel, int flags, Map<K, V > map) {
|
||||||
|
parcel.writeInt(map.size());
|
||||||
|
for(Map.Entry<K, V> e : map.entrySet()){
|
||||||
|
parcel.writeParcelable(e.getKey(), flags);
|
||||||
|
parcel.writeParcelable(e.getValue(), flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For reading from a Parcel
|
||||||
|
public static <K extends Parcelable,V extends Parcelable> Map<K,V> readParcelableMap(
|
||||||
|
Parcel parcel, Class<K> kClass, Class<V> vClass) {
|
||||||
|
int size = parcel.readInt();
|
||||||
|
Map<K, V> map = new HashMap<K, V>(size);
|
||||||
|
for(int i = 0; i < size; i++){
|
||||||
|
map.put(kClass.cast(parcel.readParcelable(kClass.getClassLoader())),
|
||||||
|
vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For writing map with string key to a Parcel
|
||||||
|
public static <V extends Parcelable> void writeStringParcelableMap(
|
||||||
|
Parcel parcel, int flags, Map<String, V> map) {
|
||||||
|
parcel.writeInt(map.size());
|
||||||
|
for(Map.Entry<String, V> e : map.entrySet()){
|
||||||
|
parcel.writeString(e.getKey());
|
||||||
|
parcel.writeParcelable(e.getValue(), flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For reading map with string key from a Parcel
|
||||||
|
public static <V extends Parcelable> HashMap<String,V> readStringParcelableMap(
|
||||||
|
Parcel parcel, Class<V> vClass) {
|
||||||
|
int size = parcel.readInt();
|
||||||
|
HashMap<String, V> map = new HashMap<>(size);
|
||||||
|
for(int i = 0; i < size; i++){
|
||||||
|
map.put(parcel.readString(),
|
||||||
|
vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// For writing map with string key and string value to a Parcel
|
||||||
|
public static void writeStringParcelableMap(Parcel dest, Map<String, String> map) {
|
||||||
|
dest.writeInt(map.size());
|
||||||
|
for(Map.Entry<String, String> e : map.entrySet()){
|
||||||
|
dest.writeString(e.getKey());
|
||||||
|
dest.writeString(e.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For reading map with string key and string value from a Parcel
|
||||||
|
public static HashMap<String, String> readStringParcelableMap(Parcel in) {
|
||||||
|
int size = in.readInt();
|
||||||
|
HashMap<String, String> map = new HashMap<>(size);
|
||||||
|
for(int i = 0; i < size; i++){
|
||||||
|
map.put(in.readString(),
|
||||||
|
in.readString());
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import com.kunzisoft.keepass.activities.AboutActivity;
|
|||||||
import com.kunzisoft.keepass.settings.SettingsActivity;
|
import com.kunzisoft.keepass.settings.SettingsActivity;
|
||||||
import com.kunzisoft.keepass.stylish.StylishActivity;
|
import com.kunzisoft.keepass.stylish.StylishActivity;
|
||||||
|
|
||||||
|
import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
|
||||||
|
|
||||||
|
|
||||||
public class MenuUtil {
|
public class MenuUtil {
|
||||||
|
|
||||||
@@ -56,20 +58,20 @@ public class MenuUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) {
|
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) {
|
||||||
return onDefaultMenuOptionsItemSelected(activity, item, false);
|
return onDefaultMenuOptionsItemSelected(activity, item, READ_ONLY_DEFAULT, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @param checkLock Check the time lock before launch settings in LockingActivity
|
* @param checkLock Check the time lock before launch settings in LockingActivity
|
||||||
*/
|
*/
|
||||||
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean checkLock) {
|
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean readOnly, boolean checkLock) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_contribute:
|
case R.id.menu_contribute:
|
||||||
return onContributionItemSelected(activity);
|
return onContributionItemSelected(activity);
|
||||||
|
|
||||||
case R.id.menu_app_settings:
|
case R.id.menu_app_settings:
|
||||||
// To avoid flickering when launch settings in a LockingActivity
|
// To avoid flickering when launch settings in a LockingActivity
|
||||||
SettingsActivity.launch(activity, checkLock);
|
SettingsActivity.launch(activity, readOnly, checkLock);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case R.id.menu_about:
|
case R.id.menu_about:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user