mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
154 Commits
2.5.0.0bet
...
2.5.0.0bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cc530cbec | ||
|
|
b540cde623 | ||
|
|
9ffffdceca | ||
|
|
257d0ff315 | ||
|
|
a935b0a089 | ||
|
|
0236bddffd | ||
|
|
384966641a | ||
|
|
3be9c9161c | ||
|
|
a445533ffc | ||
|
|
e073b21544 | ||
|
|
7e746bb01c | ||
|
|
2d8e902a2a | ||
|
|
bb10892b76 | ||
|
|
bdfa5ae707 | ||
|
|
2091b04a11 | ||
|
|
f367c098ec | ||
|
|
98631d37ae | ||
|
|
d504ab7924 | ||
|
|
4fc8c74530 | ||
|
|
4876623f86 | ||
|
|
d5b752e4c0 | ||
|
|
a1ecc5b399 | ||
|
|
306e8b17b2 | ||
|
|
f66500f535 | ||
|
|
b92ef7cb1a | ||
|
|
b30c2656e8 | ||
|
|
0899c7fc5a | ||
|
|
800d0eb04d | ||
|
|
b09bc52b51 | ||
|
|
657c810c1f | ||
|
|
c501d6bdc6 | ||
|
|
f13c595bf9 | ||
|
|
60e2209281 | ||
|
|
24eeff1d61 | ||
|
|
c94291f6e1 | ||
|
|
6faee3cef9 | ||
|
|
bb4e067394 | ||
|
|
81503c6934 | ||
|
|
8858a5cdca | ||
|
|
d994fbafcf | ||
|
|
c3954faa3e | ||
|
|
0650a6f7db | ||
|
|
0ddd08bf6d | ||
|
|
a1720c1c79 | ||
|
|
cd9f82696a | ||
|
|
242427d348 | ||
|
|
3f1ab3623d | ||
|
|
7068b4b4b3 | ||
|
|
7c81685aa6 | ||
|
|
f7fc7984e2 | ||
|
|
b2ef29d131 | ||
|
|
d5dcf697f6 | ||
|
|
9731247f2e | ||
|
|
c2654cd65c | ||
|
|
e032540c0b | ||
|
|
6dda7d1e64 | ||
|
|
f67df77dff | ||
|
|
ecdadaf0bc | ||
|
|
25fb85a5ef | ||
|
|
31426268ea | ||
|
|
f0b5b1bcb5 | ||
|
|
1f2ebf0825 | ||
|
|
fa0afbe947 | ||
|
|
70967a4234 | ||
|
|
87c9aeeb12 | ||
|
|
0a386c3985 | ||
|
|
bfcd4b9f00 | ||
|
|
3d6082a5d9 | ||
|
|
aceeb581d4 | ||
|
|
d46a6a2ea8 | ||
|
|
c1bf96ac5f | ||
|
|
a1bf5f1e70 | ||
|
|
7155e25c1e | ||
|
|
5c4d2e607a | ||
|
|
79750c5320 | ||
|
|
bb353bc9d6 | ||
|
|
5818762aaf | ||
|
|
e5467bc54b | ||
|
|
e574fba0a5 | ||
|
|
82b59662f3 | ||
|
|
260ce95509 | ||
|
|
23df28cb25 | ||
|
|
a68960a30f | ||
|
|
f295aac206 | ||
|
|
d568604117 | ||
|
|
795d6fa334 | ||
|
|
d788d16020 | ||
|
|
c8db1f7c5c | ||
|
|
ac67ff9f21 | ||
|
|
3a71f635f0 | ||
|
|
a9e12cb518 | ||
|
|
b0aac43d6c | ||
|
|
f0d7249679 | ||
|
|
32fb536a92 | ||
|
|
8f8361a176 | ||
|
|
06056128e5 | ||
|
|
259c31b94c | ||
|
|
c24c18d89e | ||
|
|
61043b3acb | ||
|
|
52f8862e71 | ||
|
|
8bd32e6605 | ||
|
|
d430305eb1 | ||
|
|
2cb3972865 | ||
|
|
724bb1fc86 | ||
|
|
ffffeb9e85 | ||
|
|
ebe8c90238 | ||
|
|
25f657e665 | ||
|
|
38def26865 | ||
|
|
a08e65733d | ||
|
|
e5bc9bfd1d | ||
|
|
767f7b06d6 | ||
|
|
a06977cd25 | ||
|
|
60be6f1223 | ||
|
|
e9929ed848 | ||
|
|
21c657c107 | ||
|
|
a93271401d | ||
|
|
a66cd68ae2 | ||
|
|
9ac060ea05 | ||
|
|
c20f453b90 | ||
|
|
27d633a1e9 | ||
|
|
221a851f44 | ||
|
|
c091ffb5e1 | ||
|
|
d8f81b669d | ||
|
|
fb72f37ebb | ||
|
|
6c5936d15d | ||
|
|
e68c682cac | ||
|
|
04da145513 | ||
|
|
a15a039f2a | ||
|
|
818c0a769b | ||
|
|
cc7b8a3fd8 | ||
|
|
43b4d00902 | ||
|
|
06b126469a | ||
|
|
2ca4e817e9 | ||
|
|
42a52b26bf | ||
|
|
5d8a73080b | ||
|
|
0b736ce0b3 | ||
|
|
dea6515e90 | ||
|
|
1bd1b2a224 | ||
|
|
b7a76ed2e7 | ||
|
|
a1237215cc | ||
|
|
4bb869e288 | ||
|
|
1d528488d3 | ||
|
|
90282d9722 | ||
|
|
2c7b19d67d | ||
|
|
981fef8fb1 | ||
|
|
77c8207c73 | ||
|
|
825a5c7e73 | ||
|
|
d921a0ae1a | ||
|
|
ffd40a5419 | ||
|
|
1946844858 | ||
|
|
052641c556 | ||
|
|
7950933d1f | ||
|
|
8f8b265d97 | ||
|
|
d20eef0922 |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -24,7 +24,7 @@
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
34
CHANGELOG
34
CHANGELOG
@@ -1,3 +1,33 @@
|
||||
KeepassDX (2.5.0.0beta5)
|
||||
* Autofill (Android O)
|
||||
* Deletion for group
|
||||
* New sorts with (Asc/Dsc, Groups before or after)
|
||||
* Better permission management with dialog at runtime
|
||||
* Setting to change font of field (monospace for a better visibility)
|
||||
* Open kdbx and kdb files from file browser
|
||||
* Change sort of fields
|
||||
* Hide empty fields
|
||||
* Add copy button for Username / Password and extra field
|
||||
* Add 5, 10, 20 seconds and 15 minutes of clipboard timeout
|
||||
* Hide "show password" icon when password not present
|
||||
* New animations for add button
|
||||
* New list to add and delete node with animation
|
||||
* Change view for better cohesion
|
||||
* Upgrade translations
|
||||
* Fix crash for API < Kitkat
|
||||
* Fix fingerprint bugs
|
||||
* Fix many small bugs
|
||||
* Add recycle bin setting (not yet accessible)
|
||||
|
||||
KeepassDX (2.5.0.0beta4)
|
||||
* Show only file name
|
||||
* Setting for full path
|
||||
* Add information for each database file
|
||||
* Setting to delete fingerprints
|
||||
* Solve bugs when change fingerprint
|
||||
* Delete view assignment for fingerprint opening
|
||||
* Merge KeePassDroid 2.2.1
|
||||
|
||||
KeepassDX (2.5.0.0beta3)
|
||||
* New database workflow with new screens and folder selection
|
||||
* Settings for default password generation
|
||||
@@ -21,6 +51,10 @@ KeepassDX (2.5.0.0beta1)
|
||||
* Update French translation
|
||||
* Change donation (see KeepassDroid to contribute on both projects)
|
||||
|
||||
KeePassDroid (2.2.1)
|
||||
* Fix kdbx4 date corruption
|
||||
* Updated German translations
|
||||
|
||||
KeePassDroid (2.2.0.9)
|
||||
* Update build tools version to workaround CM/Lineage bug (closes: #249)
|
||||
* Update Russian translations
|
||||
|
||||
@@ -21,7 +21,7 @@ Diego Pierotto - Italian
|
||||
Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French
|
||||
Maciej Bieniek, cod3r - Polish
|
||||
Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian
|
||||
MaWi, rvs2008, meviox, MaDill - German
|
||||
MaWi, rvs2008, meviox, MaDill, EdlerProgrammierer, Jan Thomas - German
|
||||
yslandro - Norwegian Nynorsk
|
||||
王科峰 - Chinese
|
||||
Typhoon - Slovak
|
||||
|
||||
35
ReadMe.md
35
ReadMe.md
@@ -1,18 +1,19 @@
|
||||
# Android Keepass DX
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/logo.png"> Keepass DX is a material design Keepass Client for manage keys and passwords in crypt database for your android device.
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/icon.png"> Keepass DX is a material design Keepass Client for manage keys and passwords in crypt database for your android device.
|
||||
|
||||
### Features
|
||||
|
||||
- Simplified creation of the database file
|
||||
- Create entries and groups
|
||||
- Open database, copy username / password, open URI / URL
|
||||
- Fingerprint for fast unlocking
|
||||
- Material design with themes
|
||||
- Device integration and AutoFill (In progress)
|
||||
* Create database files / entries and groups
|
||||
* Support for .kdb and .kdbx files (version 1 to 4)
|
||||
* Open database, copy username / password, open URI / URL
|
||||
* Fingerprint for fast unlocking
|
||||
* Material design with themes
|
||||
* AutoFill and Integration (Development in progress)
|
||||
* Precise management of settings
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen1.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen2.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen1.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen2.jpg" width="220">
|
||||
|
||||
## What is KeePass?
|
||||
|
||||
@@ -32,8 +33,8 @@ Even if the application is free, to maintain the application, you can make donat
|
||||
|
||||
[](https://liberapay.com/Kunzisoft/donate "Kunzisoft Liberapay Donation")
|
||||
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen4.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/art/screen5.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen4.jpg" width="220">
|
||||
<img src="https://raw.githubusercontent.com/Kunzisoft/KeePassDX/master/fastlane/metadata/android/en-US/images/phoneScreenshots/screen5.jpg" width="220">
|
||||
|
||||
## Download
|
||||
|
||||
@@ -45,16 +46,6 @@ Even if the application is free, to maintain the application, you can make donat
|
||||
alt="Get it on Google Play"
|
||||
height="80">](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free)
|
||||
|
||||
### JNI
|
||||
|
||||
Native library build instructions:
|
||||
1. Make sure you have the latest MIPS Android NDK installed:
|
||||
https://developer.android.com/tools/sdk/ndk/index.html
|
||||
2. From KeePassDroid/app/src/main/jni, call prep_build.sh to download and unpack the crypto sources.
|
||||
3. The standard gradle files build everything now.
|
||||
|
||||
This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2017 Jeremy Jamet / Kunzisoft.
|
||||
@@ -73,3 +64,5 @@ This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
This project is a fork of [KeepassDroid](https://github.com/bpellin/keepassdroid) by bpellin.
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion = 25
|
||||
buildToolsVersion = "27.0.1"
|
||||
compileSdkVersion = 27
|
||||
buildToolsVersion = "27.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 25
|
||||
versionCode = 3
|
||||
versionName = "2.5.0.0beta3"
|
||||
targetSdkVersion 27
|
||||
versionCode = 5
|
||||
versionName = "2.5.0.0beta5"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.keepassdroid.tests"
|
||||
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
||||
@@ -58,21 +59,34 @@ android {
|
||||
buildConfigField "boolean", "GOOGLE_PLAY_VERSION", "true"
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
targetCompatibility 1.8
|
||||
sourceCompatibility 1.8
|
||||
}
|
||||
}
|
||||
|
||||
def supportVersion = "25.4.0"
|
||||
def supportVersion = "27.1.0"
|
||||
def spongycastleVersion = "1.58.0.0"
|
||||
def permissionDispatcherVersion = "3.1.0"
|
||||
|
||||
dependencies {
|
||||
androidTestCompile "junit:junit:4.12"
|
||||
compile "com.android.support:appcompat-v7:$supportVersion"
|
||||
compile "com.android.support:design:$supportVersion"
|
||||
compile "com.android.support:preference-v7:$supportVersion"
|
||||
compile "com.android.support:preference-v14:$supportVersion"
|
||||
compile "com.android.support:cardview-v7:$supportVersion"
|
||||
compile "com.madgag.spongycastle:core:$spongycastleVersion"
|
||||
compile "com.madgag.spongycastle:prov:$spongycastleVersion"
|
||||
compile "joda-time:joda-time:2.9.9"
|
||||
compile "org.sufficientlysecure:html-textview:3.5"
|
||||
compile "com.nononsenseapps:filepicker:4.1.0"
|
||||
implementation "com.android.support:appcompat-v7:$supportVersion"
|
||||
implementation "com.android.support:design:$supportVersion"
|
||||
implementation "com.android.support:preference-v7:$supportVersion"
|
||||
implementation "com.android.support:preference-v14:$supportVersion"
|
||||
implementation "com.android.support:cardview-v7:$supportVersion"
|
||||
implementation "com.madgag.spongycastle:core:$spongycastleVersion"
|
||||
implementation "com.madgag.spongycastle:prov:$spongycastleVersion"
|
||||
// Time
|
||||
implementation "joda-time:joda-time:2.9.9"
|
||||
implementation "org.sufficientlysecure:html-textview:3.5"
|
||||
implementation "com.nononsenseapps:filepicker:4.1.0"
|
||||
// Permissions
|
||||
implementation ("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
|
||||
// if you don't use android.app.Fragment you can exclude support for them
|
||||
exclude module: "support-v13"
|
||||
}
|
||||
annotationProcessor "com.github.hotchemi:permissionsdispatcher-processor:$permissionDispatcherVersion"
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.1'
|
||||
implementation group: 'com.google.guava', name: 'guava', version: '23.0-android'
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ public class PwEntryTestV4 extends TestCase {
|
||||
entry.icon = new PwIconStandard(5);
|
||||
entry.overrideURL = "override";
|
||||
entry.parent = new PwGroupV4();
|
||||
entry.strings.put("key2", new ProtectedString(false, "value2"));
|
||||
entry.addField("key2", new ProtectedString(false, "value2"));
|
||||
entry.url = "http://localhost";
|
||||
entry.uuid = UUID.randomUUID();
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.List;
|
||||
import android.content.Context;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
@@ -74,8 +74,8 @@ public class DeleteEntry extends AndroidTestCase {
|
||||
PwGroup results1 = dbHelp.search(db, ENTRY1_NAME);
|
||||
PwGroup results2 = dbHelp.search(db, ENTRY2_NAME);
|
||||
|
||||
assertEquals("Entry1 was not removed from the search results", 0, results1.childEntries.size());
|
||||
assertEquals("Entry2 was not removed from the search results", 0, results2.childEntries.size());
|
||||
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
|
||||
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());
|
||||
|
||||
// Verify the group was deleted
|
||||
group1 = getGroup(pm, GROUP1_NAME);
|
||||
|
||||
@@ -25,12 +25,10 @@ import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabaseV3Debug;
|
||||
import com.keepassdroid.database.load.Importer;
|
||||
import com.keepassdroid.tests.TestUtil;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
|
||||
public class TestData {
|
||||
private static final String TEST1_KEYFILE = "";
|
||||
|
||||
@@ -26,7 +26,7 @@ import android.preference.PreferenceManager;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.tests.database.TestData;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class SearchTest extends AndroidTestCase {
|
||||
|
||||
public void testSearch() {
|
||||
PwGroup results = mDb.Search("Amazon");
|
||||
assertTrue("Search result not found.", results.childEntries.size() > 0);
|
||||
assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
||||
|
||||
}
|
||||
|
||||
@@ -51,14 +51,14 @@ public class SearchTest extends AndroidTestCase {
|
||||
updateOmitSetting(false);
|
||||
PwGroup results = mDb.Search("BackupOnly");
|
||||
|
||||
assertTrue("Search result not found.", results.childEntries.size() > 0);
|
||||
assertTrue("Search result not found.", results.numbersOfChildEntries() > 0);
|
||||
}
|
||||
|
||||
public void testBackupExcluded() {
|
||||
updateOmitSetting(true);
|
||||
PwGroup results = mDb.Search("BackupOnly");
|
||||
|
||||
assertFalse("Search result found, but should not have been.", results.childEntries.size() > 0);
|
||||
assertFalse("Search result found, but should not have been.", results.numbersOfChildEntries() > 0);
|
||||
}
|
||||
|
||||
private void updateOmitSetting(boolean setting) {
|
||||
@@ -66,7 +66,7 @@ public class SearchTest extends AndroidTestCase {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
|
||||
editor.putBoolean(ctx.getString(R.string.omitbackup_key), setting);
|
||||
editor.putBoolean(ctx.getString(R.string.settings_omitbackup_key), setting);
|
||||
editor.commit();
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
android:theme="@style/KeepassDXStyle.Light"
|
||||
tools:replace="android:theme">
|
||||
<!-- TODO backup API Key -->
|
||||
<meta-data android:name="com.google.android.backup.api_key"
|
||||
<meta-data
|
||||
android:name="com.google.android.backup.api_key"
|
||||
android:value="" />
|
||||
|
||||
<!-- Folder picker -->
|
||||
@@ -37,7 +38,7 @@
|
||||
android:resource="@xml/nnf_provider_paths" />
|
||||
</provider>
|
||||
<activity
|
||||
android:name="com.keepassdroid.FilePickerStylishActivity"
|
||||
android:name="com.keepassdroid.fileselect.FilePickerStylishActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.GET_CONTENT" />
|
||||
@@ -52,12 +53,19 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.keepassdroid.fileselect.FileSelectActivity"
|
||||
<activity
|
||||
android:name="com.keepassdroid.fileselect.FileSelectActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity android:name="com.keepassdroid.AboutActivity"
|
||||
<activity
|
||||
android:name="com.keepassdroid.activities.AboutActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:label="@string/menu_about" />
|
||||
<activity android:name="com.keepassdroid.PasswordActivity" android:configChanges="orientation|keyboardHidden">
|
||||
<activity
|
||||
android:name="com.keepassdroid.password.PasswordActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -87,42 +95,58 @@
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.kdbx" />
|
||||
</intent-filter>
|
||||
<intent-filter tools:ignore="AppLinkUrlError">
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:mimeType="application/octet-stream"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.keepassdroid.GroupActivityV3" android:configChanges="orientation|keyboardHidden">
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
android:value="com.keepassdroid.search.SearchResults" />
|
||||
</activity>
|
||||
<activity android:name="com.keepassdroid.GroupActivityV4" android:configChanges="orientation|keyboardHidden">
|
||||
<meta-data android:name="android.app.default_searchable"
|
||||
<activity
|
||||
android:name="com.keepassdroid.activities.GroupActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
<meta-data
|
||||
android:name="android.app.default_searchable"
|
||||
android:value="com.keepassdroid.search.SearchResults"
|
||||
android:exported="false"/>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.keepassdroid.EntryActivity"
|
||||
android:name="com.keepassdroid.activities.EntryActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="com.keepassdroid.EntryActivityV4"
|
||||
android:name="com.keepassdroid.activities.EntryEditActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="com.keepassdroid.EntryEditActivityV3"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name="com.keepassdroid.EntryEditActivityV4"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity android:name="com.keepassdroid.search.SearchResultsActivity" android:launchMode="standard">
|
||||
android:name="com.keepassdroid.search.SearchResultsActivity"
|
||||
android:launchMode="standard">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />
|
||||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable" />
|
||||
</activity>
|
||||
<activity android:name="com.keepassdroid.settings.SettingsActivity" />
|
||||
<activity android:name="com.keepassdroid.autofill.AutoFillAuthActivity"
|
||||
android:configChanges="orientation|keyboardHidden" />
|
||||
|
||||
<service android:name="com.keepassdroid.services.TimeoutService" />
|
||||
<service
|
||||
android:name="com.keepassdroid.autofill.KeeAutofillService"
|
||||
android:label="@string/autofill_service_name"
|
||||
android:permission="android.permission.BIND_AUTOFILL_SERVICE">
|
||||
<meta-data
|
||||
android:name="android.autofill"
|
||||
android:resource="@xml/dataset_service" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.service.autofill.AutofillService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.utils.SprEngine;
|
||||
import com.keepassdroid.utils.SprEngineV4;
|
||||
import com.keepassdroid.view.EntrySection;
|
||||
|
||||
|
||||
public class EntryActivityV4 extends EntryActivity {
|
||||
|
||||
@Override
|
||||
protected void setEntryView() {
|
||||
setContentView(R.layout.entry_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillData(boolean trimList) {
|
||||
super.fillData(trimList);
|
||||
|
||||
ViewGroup group = (ViewGroup) findViewById(R.id.extra_strings);
|
||||
|
||||
if (trimList) {
|
||||
group.removeAllViews();
|
||||
}
|
||||
|
||||
PwEntryV4 entry = (PwEntryV4) mEntry;
|
||||
|
||||
PwDatabase pm = App.getDB().pm;
|
||||
SprEngine spr = SprEngineV4.getInstance(pm);
|
||||
|
||||
// Display custom strings
|
||||
if (entry.strings.size() > 0) {
|
||||
for (Map.Entry<String, ProtectedString> pair : entry.strings.entrySet()) {
|
||||
String key = pair.getKey();
|
||||
|
||||
if (!PwEntryV4.IsStandardString(key)) {
|
||||
String text = pair.getValue().toString();
|
||||
View view = new EntrySection(this, null, key, spr.compile(text, entry, pm));
|
||||
group.addView(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwEntryV3;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwGroupV3;
|
||||
import com.keepassdroid.database.PwGroupV4;
|
||||
import com.keepassdroid.database.PwIconStandard;
|
||||
import com.keepassdroid.database.edit.AddEntry;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.database.edit.RunnableOnFinish;
|
||||
import com.keepassdroid.database.edit.UpdateEntry;
|
||||
import com.keepassdroid.icons.Icons;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.Types;
|
||||
import com.keepassdroid.utils.Util;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class EntryEditActivity extends LockCloseHideActivity
|
||||
implements IconPickerFragment.IconPickerListener,
|
||||
GeneratePasswordFragment.GeneratePasswordListener {
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
public static final String KEY_PARENT = "parent";
|
||||
|
||||
protected PwEntry mEntry;
|
||||
protected boolean mIsNew;
|
||||
protected int mSelectedIconID = -1;
|
||||
|
||||
public static void Launch(Activity act, PwEntry pw) {
|
||||
Intent i;
|
||||
if (pw instanceof PwEntryV3) {
|
||||
i = new Intent(act, EntryEditActivityV3.class);
|
||||
}
|
||||
else if (pw instanceof PwEntryV4) {
|
||||
i = new Intent(act, EntryEditActivityV4.class);
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Not yet implemented.");
|
||||
}
|
||||
|
||||
i.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
|
||||
act.startActivityForResult(i, 0);
|
||||
}
|
||||
|
||||
public static void Launch(Activity act, PwGroup pw) {
|
||||
Intent i;
|
||||
if (pw instanceof PwGroupV3) {
|
||||
i = new Intent(act, EntryEditActivityV3.class);
|
||||
EntryEditActivityV3.putParentId(i, KEY_PARENT, (PwGroupV3)pw);
|
||||
}
|
||||
else if (pw instanceof PwGroupV4) {
|
||||
i = new Intent(act, EntryEditActivityV4.class);
|
||||
EntryEditActivityV4.putParentId(i, KEY_PARENT, (PwGroupV4)pw);
|
||||
}
|
||||
else {
|
||||
throw new RuntimeException("Not yet implemented.");
|
||||
}
|
||||
|
||||
act.startActivityForResult(i, 0);
|
||||
}
|
||||
|
||||
protected abstract PwGroupId getParentGroupId(Intent i, String key);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.entry_edit);
|
||||
setResult(KeePass.EXIT_NORMAL);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
Database db = App.getDB();
|
||||
if ( ! db.Loaded() ) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] uuidBytes = i.getByteArrayExtra(KEY_ENTRY);
|
||||
|
||||
PwDatabase pm = db.pm;
|
||||
if ( uuidBytes == null ) {
|
||||
|
||||
PwGroupId parentId = getParentGroupId(i, KEY_PARENT);
|
||||
PwGroup parent = pm.groups.get(parentId);
|
||||
mEntry = PwEntry.getInstance(parent);
|
||||
mIsNew = true;
|
||||
|
||||
} else {
|
||||
UUID uuid = Types.bytestoUUID(uuidBytes);
|
||||
mEntry = pm.entries.get(uuid);
|
||||
mIsNew = false;
|
||||
fillData();
|
||||
}
|
||||
|
||||
View scrollView = findViewById(R.id.entry_scroll);
|
||||
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
|
||||
|
||||
View iconButton = findViewById(R.id.icon_button);
|
||||
iconButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
IconPickerFragment.Launch(EntryEditActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
// Generate password button
|
||||
View generatePassword = findViewById(R.id.generate_button);
|
||||
generatePassword.setOnClickListener(new OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
GeneratePasswordFragment generatePasswordFragment = new GeneratePasswordFragment();
|
||||
generatePasswordFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
|
||||
}
|
||||
});
|
||||
|
||||
// Save button
|
||||
View save = findViewById(R.id.entry_save);
|
||||
save.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
EntryEditActivity act = EntryEditActivity.this;
|
||||
|
||||
if (!validateBeforeSaving()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PwEntry newEntry = populateNewEntry();
|
||||
|
||||
if ( newEntry.getTitle().equals(mEntry.getTitle()) ) {
|
||||
setResult(KeePass.EXIT_REFRESH);
|
||||
} else {
|
||||
setResult(KeePass.EXIT_REFRESH_TITLE);
|
||||
}
|
||||
|
||||
RunnableOnFinish task;
|
||||
OnFinish onFinish = act.new AfterSave(new Handler());
|
||||
|
||||
if ( mIsNew ) {
|
||||
task = AddEntry.getInstance(EntryEditActivity.this, App.getDB(), newEntry, onFinish);
|
||||
} else {
|
||||
task = new UpdateEntry(EntryEditActivity.this, App.getDB(), mEntry, newEntry, onFinish);
|
||||
}
|
||||
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
protected boolean validateBeforeSaving() {
|
||||
// Require title
|
||||
String title = Util.getEditText(this, R.id.entry_title);
|
||||
if ( title.length() == 0 ) {
|
||||
Toast.makeText(this, R.string.error_title_required, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate password
|
||||
String pass = Util.getEditText(this, R.id.entry_password);
|
||||
String conf = Util.getEditText(this, R.id.entry_confpassword);
|
||||
if ( ! pass.equals(conf) ) {
|
||||
Toast.makeText(this, R.string.error_pass_match, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected PwEntry populateNewEntry() {
|
||||
return populateNewEntry(null);
|
||||
}
|
||||
|
||||
protected PwEntry populateNewEntry(PwEntry entry) {
|
||||
PwEntry newEntry;
|
||||
if (entry == null) {
|
||||
newEntry = mEntry.clone(true);
|
||||
}
|
||||
else {
|
||||
newEntry = entry;
|
||||
}
|
||||
|
||||
Date now = Calendar.getInstance().getTime();
|
||||
newEntry.setLastAccessTime(now);
|
||||
newEntry.setLastModificationTime(now);
|
||||
|
||||
PwDatabase db = App.getDB().pm;
|
||||
newEntry.setTitle(Util.getEditText(this, R.id.entry_title), db);
|
||||
if(mSelectedIconID != -1)
|
||||
newEntry.setIcon(new PwIconStandard(mSelectedIconID));
|
||||
newEntry.setUrl(Util.getEditText(this, R.id.entry_url), db);
|
||||
newEntry.setUsername(Util.getEditText(this, R.id.entry_user_name), db);
|
||||
newEntry.setNotes(Util.getEditText(this, R.id.entry_comment), db);
|
||||
newEntry.setPassword(Util.getEditText(this, R.id.entry_password), db);
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.donationMenuInflater(inflater, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
case R.id.menu_donate:
|
||||
return MenuUtil.onDonationItemSelected(this);
|
||||
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
protected void fillData() {
|
||||
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
|
||||
App.getDB().drawFactory.assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
|
||||
|
||||
populateText(R.id.entry_title, mEntry.getTitle());
|
||||
populateText(R.id.entry_user_name, mEntry.getUsername());
|
||||
populateText(R.id.entry_url, mEntry.getUrl());
|
||||
|
||||
String password = mEntry.getPassword();
|
||||
populateText(R.id.entry_password, password);
|
||||
populateText(R.id.entry_confpassword, password);
|
||||
|
||||
populateText(R.id.entry_comment, mEntry.getNotes());
|
||||
}
|
||||
|
||||
private void populateText(int viewId, String text) {
|
||||
TextView tv = (TextView) findViewById(viewId);
|
||||
tv.setText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
mSelectedIconID = bundle.getInt(IconPickerFragment.KEY_ICON_ID);
|
||||
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
|
||||
currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptPassword(Bundle bundle) {
|
||||
String generatedPassword = bundle.getString(GeneratePasswordFragment.KEY_PASSWORD_ID);
|
||||
EditText password = (EditText) findViewById(R.id.entry_password);
|
||||
EditText confPassword = (EditText) findViewById(R.id.entry_confpassword);
|
||||
|
||||
password.setText(generatedPassword);
|
||||
confPassword.setText(generatedPassword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelPassword(Bundle bundle) {
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
private final class AfterSave extends OnFinish {
|
||||
|
||||
AfterSave(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ( mSuccess ) {
|
||||
finish();
|
||||
} else {
|
||||
displayMessage(EntryEditActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwGroupIdV3;
|
||||
import com.keepassdroid.database.PwGroupV3;
|
||||
|
||||
public class EntryEditActivityV3 extends EntryEditActivity {
|
||||
|
||||
@Override
|
||||
protected PwEntry populateNewEntry(PwEntry entry) {
|
||||
PwEntry newEntry = super.populateNewEntry(entry);
|
||||
|
||||
if (mSelectedIconID == -1) {
|
||||
if (mIsNew) {
|
||||
newEntry.icon = App.getDB().pm.iconFactory.getIcon(0);
|
||||
}
|
||||
else {
|
||||
// Keep previous icon, if no new one was selected
|
||||
newEntry.icon = mEntry.icon;
|
||||
}
|
||||
}
|
||||
else {
|
||||
newEntry.icon = App.getDB().pm.iconFactory.getIcon(mSelectedIconID);
|
||||
}
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
protected static void putParentId(Intent i, String parentKey, PwGroupV3 parent) {
|
||||
i.putExtra(parentKey, parent.groupId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PwGroupId getParentGroupId(Intent i, String key) {
|
||||
int groupId = i.getIntExtra(key, -1);
|
||||
|
||||
return new PwGroupIdV3(groupId);
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwGroupIdV4;
|
||||
import com.keepassdroid.database.PwGroupV4;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.utils.Types;
|
||||
import com.keepassdroid.view.EntryEditSection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntryEditActivityV4 extends EntryEditActivity {
|
||||
|
||||
private ScrollView scroll;
|
||||
private LayoutInflater inflater;
|
||||
|
||||
protected static void putParentId(Intent i, String parentKey, PwGroupV4 parent) {
|
||||
PwGroupId id = parent.getId();
|
||||
PwGroupIdV4 id4 = (PwGroupIdV4) id;
|
||||
|
||||
i.putExtra(parentKey, Types.UUIDtoBytes(id4.getId()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PwGroupId getParentGroupId(Intent i, String key) {
|
||||
byte[] buf = i.getByteArrayExtra(key);
|
||||
UUID id = Types.bytestoUUID(buf);
|
||||
|
||||
return new PwGroupIdV4(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
scroll = (ScrollView) findViewById(R.id.entry_scroll);
|
||||
|
||||
View add = findViewById(R.id.add_advanced);
|
||||
add.setVisibility(View.VISIBLE);
|
||||
add.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
|
||||
|
||||
EntryEditSection ees = (EntryEditSection) inflater.inflate(R.layout.entry_edit_section, container, false);
|
||||
ees.setData("", new ProtectedString(false, ""));
|
||||
container.addView(ees);
|
||||
|
||||
// Scroll bottom
|
||||
scroll.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scroll.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillData() {
|
||||
super.fillData();
|
||||
|
||||
PwEntryV4 entry = (PwEntryV4) mEntry;
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
|
||||
|
||||
if (entry.strings.size() > 0) {
|
||||
for (Entry<String, ProtectedString> pair : entry.strings.entrySet()) {
|
||||
String key = pair.getKey();
|
||||
|
||||
if (!PwEntryV4.IsStandardString(key)) {
|
||||
EntryEditSection ees = (EntryEditSection) inflater.inflate(R.layout.entry_edit_section, container, false);
|
||||
ees.setData(key, pair.getValue());
|
||||
container.addView(ees);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected PwEntry populateNewEntry() {
|
||||
PwEntryV4 newEntry = (PwEntryV4) mEntry.clone(true);
|
||||
newEntry.history = (ArrayList<PwEntryV4>) newEntry.history.clone();
|
||||
newEntry.createBackup((PwDatabaseV4)App.getDB().pm);
|
||||
|
||||
newEntry = (PwEntryV4) super.populateNewEntry(newEntry);
|
||||
|
||||
Map<String, ProtectedString> strings = newEntry.strings;
|
||||
|
||||
// Delete all new standard strings
|
||||
Iterator<Entry<String, ProtectedString>> iter = strings.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Entry<String, ProtectedString> pair = iter.next();
|
||||
if (!PwEntryV4.IsStandardString(pair.getKey())) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
|
||||
for (int i = 0; i < container.getChildCount(); i++) {
|
||||
View view = container.getChildAt(i);
|
||||
|
||||
TextView keyView = (TextView)view.findViewById(R.id.title);
|
||||
String key = keyView.getText().toString();
|
||||
|
||||
TextView valueView = (TextView)view.findViewById(R.id.value);
|
||||
String value = valueView.getText().toString();
|
||||
|
||||
CheckBox cb = (CheckBox)view.findViewById(R.id.protection);
|
||||
boolean protect = cb.isChecked();
|
||||
|
||||
strings.put(key, new ProtectedString(protect, value));
|
||||
}
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
public void deleteAdvancedString(View view) {
|
||||
ViewGroup section = (ViewGroup) view.getParent();
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
|
||||
|
||||
for (int i = 0; i < container.getChildCount(); i++) {
|
||||
ViewGroup ees = (ViewGroup) container.getChildAt(i);
|
||||
if (ees == section) {
|
||||
container.removeViewAt(i);
|
||||
container.invalidate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean validateBeforeSaving() {
|
||||
if(!super.validateBeforeSaving()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
|
||||
for (int i = 0; i < container.getChildCount(); i++) {
|
||||
EntryEditSection ees = (EntryEditSection) container.getChildAt(i);
|
||||
|
||||
TextView keyView = (TextView) ees.findViewById(R.id.title);
|
||||
CharSequence key = keyView.getText();
|
||||
|
||||
if (key == null || key.length() == 0) {
|
||||
Toast.makeText(this, R.string.error_string_key, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwGroupV3;
|
||||
import com.keepassdroid.database.PwGroupV4;
|
||||
import com.keepassdroid.database.edit.AddGroup;
|
||||
import com.keepassdroid.dialog.ReadOnlyDialog;
|
||||
import com.keepassdroid.view.ClickView;
|
||||
import com.keepassdroid.view.GroupAddEntryView;
|
||||
import com.keepassdroid.view.GroupRootView;
|
||||
import com.keepassdroid.view.GroupViewOnlyView;
|
||||
|
||||
public abstract class GroupActivity extends GroupBaseActivity
|
||||
implements GroupEditFragment.CreateGroupListener, IconPickerFragment.IconPickerListener {
|
||||
|
||||
private static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
|
||||
|
||||
protected boolean addGroupEnabled = false;
|
||||
protected boolean addEntryEnabled = false;
|
||||
protected boolean isRoot = false;
|
||||
protected boolean readOnly = false;
|
||||
|
||||
private static final String TAG = "Group Activity:";
|
||||
|
||||
public static void Launch(Activity act) {
|
||||
Launch(act, null);
|
||||
}
|
||||
|
||||
public static void Launch(Activity act, PwGroup group) {
|
||||
Intent i;
|
||||
|
||||
// Need to use PwDatabase since tree may be null
|
||||
PwDatabase db = App.getDB().pm;
|
||||
if ( db instanceof PwDatabaseV3 ) {
|
||||
i = new Intent(act, GroupActivityV3.class);
|
||||
|
||||
if ( group != null ) {
|
||||
PwGroupV3 g = (PwGroupV3) group;
|
||||
i.putExtra(KEY_ENTRY, g.groupId);
|
||||
}
|
||||
} else if ( db instanceof PwDatabaseV4 ) {
|
||||
i = new Intent(act, GroupActivityV4.class);
|
||||
|
||||
if ( group != null ) {
|
||||
PwGroupV4 g = (PwGroupV4) group;
|
||||
i.putExtra(KEY_ENTRY, g.uuid.toString());
|
||||
}
|
||||
} else {
|
||||
// Reached if db is null
|
||||
Log.d(TAG, "Tried to launch with null db");
|
||||
return;
|
||||
}
|
||||
|
||||
act.startActivityForResult(i,0);
|
||||
}
|
||||
|
||||
protected abstract PwGroupId retrieveGroupId(Intent i);
|
||||
|
||||
protected void setupButtons() {
|
||||
addGroupEnabled = !readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if ( isFinishing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setResult(KeePass.EXIT_NORMAL);
|
||||
|
||||
Log.w(TAG, "Creating tree view");
|
||||
Intent intent = getIntent();
|
||||
|
||||
PwGroupId id = retrieveGroupId(intent);
|
||||
|
||||
Database db = App.getDB();
|
||||
readOnly = db.readOnly;
|
||||
PwGroup root = db.pm.rootGroup;
|
||||
if ( id == null ) {
|
||||
mGroup = root;
|
||||
} else {
|
||||
mGroup = db.pm.groups.get(id);
|
||||
}
|
||||
|
||||
Log.w(TAG, "Retrieved tree");
|
||||
if ( mGroup == null ) {
|
||||
Log.w(TAG, "Group was null");
|
||||
return;
|
||||
}
|
||||
|
||||
isRoot = mGroup == root;
|
||||
|
||||
setupButtons();
|
||||
|
||||
if ( addGroupEnabled && addEntryEnabled ) {
|
||||
setContentView(new GroupAddEntryView(this));
|
||||
} else if ( addGroupEnabled ) {
|
||||
setContentView(new GroupRootView(this));
|
||||
} else if ( addEntryEnabled ) {
|
||||
setContentView(new GroupAddEntryView(this));
|
||||
View addGroup = findViewById(R.id.add_group);
|
||||
addGroup.setVisibility(View.GONE);
|
||||
} else {
|
||||
setContentView(new GroupViewOnlyView(this));
|
||||
}
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar.setTitle("");
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if ( mGroup.getParent() != null )
|
||||
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
|
||||
|
||||
Log.w(TAG, "Set view");
|
||||
|
||||
if ( addGroupEnabled ) {
|
||||
// Add Group button
|
||||
View addGroup = findViewById(R.id.add_group);
|
||||
addGroup.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
GroupEditFragment groupEditFragment = new GroupEditFragment();
|
||||
groupEditFragment.show(getSupportFragmentManager(), TAG_CREATE_GROUP);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if ( addEntryEnabled ) {
|
||||
// Add Entry button
|
||||
View addEntry = findViewById(R.id.add_entry);
|
||||
addEntry.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
EntryEditActivity.Launch(GroupActivity.this, mGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setGroupTitle();
|
||||
setGroupIcon();
|
||||
|
||||
setListAdapter(new PwGroupListAdapter(this, mGroup));
|
||||
registerForContextMenu(getListView());
|
||||
Log.w(TAG, "Finished creating tree");
|
||||
|
||||
if (isRoot) {
|
||||
showWarnings();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
|
||||
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
|
||||
ClickView cv = (ClickView) acmi.targetView;
|
||||
cv.onCreateMenu(menu, menuInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||
ClickView cv = (ClickView) acmi.targetView;
|
||||
|
||||
return cv.onContextItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approveCreateGroup(Bundle bundle) {
|
||||
String GroupName = bundle.getString(GroupEditFragment.KEY_NAME);
|
||||
int GroupIconID = bundle.getInt(GroupEditFragment.KEY_ICON_ID);
|
||||
GroupActivity act = GroupActivity.this;
|
||||
Handler handler = new Handler();
|
||||
AddGroup task = AddGroup.getInstance(this, App.getDB(), GroupName, GroupIconID, mGroup, act.new RefreshTask(handler), false);
|
||||
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelCreateGroup(Bundle bundle) {
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
@Override
|
||||
// For icon in create tree dialog
|
||||
public void iconPicked(Bundle bundle) {
|
||||
GroupEditFragment groupEditFragment = (GroupEditFragment) getSupportFragmentManager().findFragmentByTag(TAG_CREATE_GROUP);
|
||||
if (groupEditFragment != null) {
|
||||
groupEditFragment.iconPicked(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
protected void showWarnings() {
|
||||
if (App.getDB().readOnly) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
if (prefs.getBoolean(getString(R.string.show_read_only_warning), true)) {
|
||||
Dialog dialog = new ReadOnlyDialog(this);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.keepassdroid.database.PwGroupIdV3;
|
||||
|
||||
public class GroupActivityV3 extends GroupActivity {
|
||||
|
||||
@Override
|
||||
protected PwGroupIdV3 retrieveGroupId(Intent i) {
|
||||
int id = i.getIntExtra(KEY_ENTRY, -1);
|
||||
|
||||
if ( id == -1 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PwGroupIdV3(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupButtons() {
|
||||
super.setupButtons();
|
||||
addEntryEnabled = !isRoot && !readOnly;
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwGroupIdV4;
|
||||
|
||||
public class GroupActivityV4 extends GroupActivity {
|
||||
|
||||
@Override
|
||||
protected PwGroupId retrieveGroupId(Intent i) {
|
||||
String uuid = i.getStringExtra(KEY_ENTRY);
|
||||
|
||||
if ( uuid == null || uuid.length() == 0 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PwGroupIdV4(UUID.fromString(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupButtons() {
|
||||
super.setupButtons();
|
||||
addEntryEnabled = !readOnly;
|
||||
}
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.ActivityCompat;
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.search.SearchResultsActivity;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.view.AssignPasswordHelper;
|
||||
import com.keepassdroid.view.ClickView;
|
||||
import com.keepassdroid.view.GroupViewOnlyView;
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public abstract class GroupBaseActivity extends LockCloseListActivity
|
||||
implements AssignMasterKeyDialog.AssignPasswordDialogListener {
|
||||
protected ListView mList;
|
||||
protected ListAdapter mAdapter;
|
||||
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
|
||||
private SharedPreferences prefs;
|
||||
|
||||
protected PwGroup mGroup;
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
refreshIfDirty();
|
||||
}
|
||||
|
||||
public void refreshIfDirty() {
|
||||
Database db = App.getDB();
|
||||
if ( db.dirty.contains(mGroup) ) {
|
||||
db.dirty.remove(mGroup);
|
||||
((BaseAdapter) mAdapter).notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
ClickView cv = (ClickView) mAdapter.getView(position, null, null);
|
||||
cv.onClick();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! App.getDB().Loaded() ) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
|
||||
setContentView(new GroupViewOnlyView(this));
|
||||
setResult(KeePass.EXIT_NORMAL);
|
||||
|
||||
styleScrollBars();
|
||||
|
||||
}
|
||||
|
||||
protected void styleScrollBars() {
|
||||
ensureCorrectListView();
|
||||
mList.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
|
||||
mList.setTextFilterEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
protected void setGroupTitle() {
|
||||
if ( mGroup != null ) {
|
||||
String name = mGroup.getName();
|
||||
TextView tv = (TextView) findViewById(R.id.group_name);
|
||||
if ( name != null && name.length() > 0 ) {
|
||||
if ( tv != null ) {
|
||||
tv.setText(name);
|
||||
}
|
||||
} else {
|
||||
if ( tv != null ) {
|
||||
tv.setText(getText(R.string.root));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setGroupIcon() {
|
||||
if (mGroup != null) {
|
||||
ImageView iv = (ImageView) findViewById(R.id.icon);
|
||||
App.getDB().drawFactory.assignDrawableTo(iv, getResources(), mGroup.getIcon());
|
||||
}
|
||||
}
|
||||
|
||||
protected void setListAdapter(ListAdapter adapter) {
|
||||
ensureCorrectListView();
|
||||
mAdapter = adapter;
|
||||
mList.setAdapter(adapter);
|
||||
}
|
||||
|
||||
protected ListView getListView() {
|
||||
ensureCorrectListView();
|
||||
return mList;
|
||||
}
|
||||
|
||||
private void ensureCorrectListView(){
|
||||
mList = (ListView)findViewById(R.id.group_list);
|
||||
if (mList != null) {
|
||||
mList.setOnItemClickListener(
|
||||
new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
onListItemClick((ListView) parent, v, position, id);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.search, menu);
|
||||
MenuUtil.donationMenuInflater(inflater, menu);
|
||||
inflater.inflate(R.menu.tree, menu);
|
||||
inflater.inflate(R.menu.database, menu);
|
||||
inflater.inflate(R.menu.default_menu, menu);
|
||||
|
||||
// Get the SearchView and set the searchable configuration
|
||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||
assert searchManager != null;
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_search);
|
||||
SearchView searchView = null;
|
||||
if (searchItem != null) {
|
||||
searchView = (SearchView) searchItem.getActionView();
|
||||
}
|
||||
if (searchView != null) {
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
|
||||
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setSortMenuText(Menu menu) {
|
||||
boolean sortByName = false;
|
||||
|
||||
// Will be null if onPrepareOptionsMenu is called before onCreate
|
||||
if (prefs != null) {
|
||||
sortByName = prefs.getBoolean(getString(R.string.sort_key), getResources().getBoolean(R.bool.sort_default));
|
||||
}
|
||||
|
||||
int resId;
|
||||
if ( sortByName ) {
|
||||
resId = R.string.sort_db;
|
||||
} else {
|
||||
resId = R.string.sort_name;
|
||||
}
|
||||
|
||||
menu.findItem(R.id.menu_sort).setTitle(resId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
if ( ! super.onPrepareOptionsMenu(menu) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setSortMenuText(menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
|
||||
case R.id.menu_search:
|
||||
onSearchRequested();
|
||||
return true;
|
||||
|
||||
case R.id.menu_sort:
|
||||
toggleSort();
|
||||
return true;
|
||||
|
||||
case R.id.menu_lock:
|
||||
App.setShutdown();
|
||||
setResult(KeePass.EXIT_LOCK);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
case R.id.menu_change_master_key:
|
||||
setPassword();
|
||||
return true;
|
||||
|
||||
default:
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleSort() {
|
||||
// Toggle setting
|
||||
String sortKey = getString(R.string.sort_key);
|
||||
boolean sortByName = prefs.getBoolean(sortKey, getResources().getBoolean(R.bool.sort_default));
|
||||
Editor editor = prefs.edit();
|
||||
editor.putBoolean(sortKey, ! sortByName);
|
||||
EditorCompat.apply(editor);
|
||||
|
||||
// Refresh menu titles
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
|
||||
// Mark all groups as dirty now to refresh them on load
|
||||
Database db = App.getDB();
|
||||
db.markAllGroupsAsDirty();
|
||||
// We'll manually refresh this tree so we can remove it
|
||||
db.dirty.remove(mGroup);
|
||||
|
||||
// Tell the adapter to refresh it's list
|
||||
((BaseAdapter) mAdapter).notifyDataSetChanged();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogPositiveClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
AssignPasswordHelper assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPassword, keyFile);
|
||||
assignPasswordHelper.assignPasswordInDatabase(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogNegativeClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
}
|
||||
|
||||
private void setPassword() {
|
||||
AssignMasterKeyDialog dialog = new AssignMasterKeyDialog();
|
||||
dialog.show(getSupportFragmentManager(), "passwordDialog");
|
||||
}
|
||||
|
||||
public class RefreshTask extends OnFinish {
|
||||
public RefreshTask(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ( mSuccess) {
|
||||
refreshIfDirty();
|
||||
} else {
|
||||
displayMessage(GroupBaseActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AfterDeleteGroup extends OnFinish {
|
||||
public AfterDeleteGroup(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ( mSuccess) {
|
||||
refreshIfDirty();
|
||||
} else {
|
||||
mHandler.post(new UIToastTask(GroupBaseActivity.this, "Unrecoverable error: " + mMessage));
|
||||
App.setShutdown();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import com.keepassdroid.compat.BuildCompat;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
|
||||
/**
|
||||
* Locking Close Activity that sets FLAG_SECURE to prevent screenshots, and from
|
||||
* appearing in the recent app preview
|
||||
* @author Brian Pellin
|
||||
*
|
||||
*/
|
||||
public abstract class LockCloseHideActivity extends LockCloseActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Several gingerbread devices have problems with FLAG_SECURE
|
||||
int ver = BuildCompat.getSdkVersion();
|
||||
if (ver >= BuildCompat.VERSION_CODE_ICE_CREAM_SANDWICH || ver < BuildCompat.VERSION_CODE_GINGERBREAD) {
|
||||
getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import com.keepassdroid.timeout.TimeoutHelper;
|
||||
|
||||
public abstract class LockCloseListActivity extends LockingActivity {
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
TimeoutHelper.checkShutdown(this);
|
||||
}
|
||||
}
|
||||
@@ -1,711 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.BackupManagerCompat;
|
||||
import com.keepassdroid.compat.ClipDataCompat;
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.database.edit.LoadDB;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
||||
import com.keepassdroid.fingerprint.FingerPrintAnimatedVector;
|
||||
import com.keepassdroid.fingerprint.FingerPrintHelper;
|
||||
import com.keepassdroid.settings.PrefsUtil;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.keepassdroid.utils.Util;
|
||||
import com.keepassdroid.view.FingerPrintDialog;
|
||||
import com.keepassdroid.view.KeyFileHelper;
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class PasswordActivity extends LockingActivity implements FingerPrintHelper.FingerPrintCallback {
|
||||
|
||||
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
||||
private static final String KEY_FILENAME = "fileName";
|
||||
private static final String KEY_KEYFILE = "keyFile";
|
||||
private static final String KEY_PASSWORD = "password";
|
||||
private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately";
|
||||
private static final String VIEW_INTENT = "android.intent.action.VIEW";
|
||||
|
||||
private Uri mDbUri = null;
|
||||
private Uri mKeyUri = null;
|
||||
private boolean mRememberKeyfile;
|
||||
SharedPreferences prefs;
|
||||
SharedPreferences prefsNoBackup;
|
||||
|
||||
private FingerPrintHelper fingerPrintHelper;
|
||||
private boolean fingerprintMustBeConfigured = true;
|
||||
|
||||
private int mode;
|
||||
private static final String PREF_KEY_VALUE_PREFIX = "valueFor_"; // key is a combination of db file name and this prefix
|
||||
private static final String PREF_KEY_IV_PREFIX = "ivFor_"; // key is a combination of db file name and this prefix
|
||||
|
||||
private View fingerprintContainerView;
|
||||
private View fingerprintImageView;
|
||||
private FingerPrintAnimatedVector fingerPrintAnimatedVector;
|
||||
private TextView fingerprintTextView;
|
||||
private TextView filenameView;
|
||||
private EditText passwordView;
|
||||
private EditText keyFileView;
|
||||
private Button confirmButtonView;
|
||||
private CheckBox checkboxPasswordView;
|
||||
private CheckBox checkboxKeyfileView;
|
||||
|
||||
private KeyFileHelper keyFileHelper;
|
||||
|
||||
public static void Launch(
|
||||
Activity act,
|
||||
String fileName) throws FileNotFoundException {
|
||||
Launch(act, fileName, "");
|
||||
}
|
||||
|
||||
public static void Launch(
|
||||
Activity act,
|
||||
String fileName,
|
||||
String keyFile) throws FileNotFoundException {
|
||||
if (EmptyUtils.isNullOrEmpty(fileName)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
Uri uri = UriUtil.parseDefaultFile(fileName);
|
||||
assert uri != null;
|
||||
String scheme = uri.getScheme();
|
||||
|
||||
if (!EmptyUtils.isNullOrEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
|
||||
File dbFile = new File(uri.getPath());
|
||||
if (!dbFile.exists()) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
Intent i = new Intent(act, PasswordActivity.class);
|
||||
i.putExtra(KEY_FILENAME, fileName);
|
||||
i.putExtra(KEY_KEYFILE, keyFile);
|
||||
|
||||
act.startActivityForResult(i, 0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
|
||||
new KeyFileHelper.KeyFileCallback() {
|
||||
@Override
|
||||
public void onKeyFileResultCallback(Uri uri) {
|
||||
if(uri != null) {
|
||||
keyFileView.setText(uri.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
switch (requestCode) {
|
||||
case KeePass.EXIT_NORMAL:
|
||||
setEmptyViews();
|
||||
App.getDB().clear();
|
||||
break;
|
||||
|
||||
case KeePass.EXIT_LOCK:
|
||||
setResult(KeePass.EXIT_LOCK);
|
||||
setEmptyViews();
|
||||
finish();
|
||||
App.getDB().clear();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent i = getIntent();
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefsNoBackup = getSharedPreferences("nobackup", Context.MODE_PRIVATE);
|
||||
|
||||
mRememberKeyfile = prefs.getBoolean(getString(R.string.keyfile_key), getResources().getBoolean(R.bool.keyfile_default));
|
||||
setContentView(R.layout.password);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
confirmButtonView = (Button) findViewById(R.id.pass_ok);
|
||||
fingerprintContainerView = findViewById(R.id.fingerprint_container);
|
||||
fingerprintImageView = findViewById(R.id.fingerprint_image);
|
||||
fingerprintTextView = (TextView) findViewById(R.id.fingerprint_label);
|
||||
filenameView = (TextView) findViewById(R.id.filename);
|
||||
passwordView = (EditText) findViewById(R.id.password);
|
||||
keyFileView = (EditText) findViewById(R.id.pass_keyfile);
|
||||
checkboxPasswordView = (CheckBox) findViewById(R.id.password_checkbox);
|
||||
checkboxKeyfileView = (CheckBox) findViewById(R.id.keyfile_checkox);
|
||||
|
||||
passwordView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
checkboxPasswordView.setChecked(true);
|
||||
}
|
||||
});
|
||||
keyFileView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
checkboxKeyfileView.setChecked(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerPrintAnimatedVector = new FingerPrintAnimatedVector(this,
|
||||
(ImageView) fingerprintImageView);
|
||||
}
|
||||
|
||||
new InitTask().execute(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// If the application was shutdown make sure to clear the password field, if it
|
||||
// was saved in the instance state
|
||||
if (App.isShutdown()) {
|
||||
setEmptyViews();
|
||||
}
|
||||
|
||||
// Clear the shutdown flag
|
||||
App.clearShutdown();
|
||||
|
||||
// checks if fingerprint is available, will also start listening for fingerprints when available
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
initForFingerprint();
|
||||
checkAvailability();
|
||||
if (fingerPrintAnimatedVector != null) {
|
||||
fingerPrintAnimatedVector.startScan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setEmptyViews() {
|
||||
passwordView.setText("");
|
||||
keyFileView.setText("");
|
||||
checkboxPasswordView.setChecked(false);
|
||||
checkboxKeyfileView.setChecked(false);
|
||||
}
|
||||
|
||||
private void retrieveSettings() {
|
||||
String defaultFilename = prefs.getString(KEY_DEFAULT_FILENAME, "");
|
||||
if (!EmptyUtils.isNullOrEmpty(mDbUri.getPath()) && UriUtil.equalsDefaultfile(mDbUri, defaultFilename)) {
|
||||
CompoundButton checkbox = (CompoundButton) findViewById(R.id.default_database);
|
||||
checkbox.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
private Uri getKeyFile(Uri dbUri) {
|
||||
if (mRememberKeyfile) {
|
||||
return App.getFileHistory().getFileByName(dbUri);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void populateView() {
|
||||
String db = (mDbUri == null) ? "" : mDbUri.toString();
|
||||
if (!db.isEmpty())
|
||||
filenameView.setText(db);
|
||||
|
||||
String key = (mKeyUri == null) ? "" : mKeyUri.toString();
|
||||
if (!key.isEmpty())
|
||||
keyFileView.setText(key);
|
||||
}
|
||||
|
||||
// fingerprint related code here
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void initForFingerprint() {
|
||||
fingerPrintHelper = new FingerPrintHelper(this, this);
|
||||
|
||||
// when text entered we can enable the logon/purchase button and if required update encryption/decryption mode
|
||||
passwordView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(
|
||||
final CharSequence s,
|
||||
final int start,
|
||||
final int count,
|
||||
final int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(
|
||||
final CharSequence s,
|
||||
final int start,
|
||||
final int before,
|
||||
final int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
if ( !fingerprintMustBeConfigured ) {
|
||||
final boolean validInput = s.length() > 0;
|
||||
// encrypt or decrypt mode based on how much input or not
|
||||
fingerprintTextView.setText(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
|
||||
mode = validInput ? toggleMode(Cipher.ENCRYPT_MODE) : toggleMode(Cipher.DECRYPT_MODE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// callback for fingerprint findings
|
||||
fingerPrintHelper.setAuthenticationCallback(new FingerprintManagerCompat.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(
|
||||
final int errorCode,
|
||||
final CharSequence errString) {
|
||||
|
||||
// this is triggered on stop/start listening done by helper to switch between modes so don't restart here
|
||||
// errorCode = 5
|
||||
// errString = "Fingerprint operation canceled."
|
||||
//onFingerprintException();
|
||||
//fingerprintTextView.setText(errString);
|
||||
// true false fingerprint readings are handled otherwise with the toast messages, see below in code
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(
|
||||
final int helpCode,
|
||||
final CharSequence helpString) {
|
||||
|
||||
onFingerprintException(new Exception("onAuthenticationHelp"));
|
||||
fingerprintTextView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(final FingerprintManagerCompat.AuthenticationResult result) {
|
||||
|
||||
if (mode == Cipher.ENCRYPT_MODE) {
|
||||
|
||||
// newly store the entered password in encrypted way
|
||||
final String password = passwordView.getText().toString();
|
||||
fingerPrintHelper.encryptData(password);
|
||||
|
||||
} else if (mode == Cipher.DECRYPT_MODE) {
|
||||
|
||||
// retrieve the encrypted value from preferences
|
||||
final String encryptedValue = prefsNoBackup.getString(getPreferenceKeyValue(), null);
|
||||
if (encryptedValue != null) {
|
||||
fingerPrintHelper.decryptData(encryptedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
onFingerprintException(new Exception("onAuthenticationFailed"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getPreferenceKeyValue() {
|
||||
// makes it possible to store passwords uniqly per database
|
||||
return PREF_KEY_VALUE_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||
}
|
||||
|
||||
private String getPreferenceKeyIvSpec() {
|
||||
return PREF_KEY_IV_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private int toggleMode(final int newMode) {
|
||||
mode = newMode;
|
||||
switch (mode) {
|
||||
case Cipher.ENCRYPT_MODE:
|
||||
fingerPrintHelper.initEncryptData();
|
||||
break;
|
||||
case Cipher.DECRYPT_MODE:
|
||||
final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null);
|
||||
fingerPrintHelper.initDecryptData(ivSpecValue);
|
||||
break;
|
||||
}
|
||||
return newMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
|
||||
&& fingerPrintAnimatedVector != null) {
|
||||
fingerPrintAnimatedVector.stopScan();
|
||||
}
|
||||
|
||||
// stop listening when we go in background
|
||||
if (fingerPrintHelper != null) {
|
||||
fingerPrintHelper.stopListening();
|
||||
}
|
||||
}
|
||||
|
||||
private void setFingerPrintVisibility(int vis) {
|
||||
fingerprintContainerView.setVisibility(vis);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void checkAvailability() {
|
||||
|
||||
// fingerprint not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
if (!PrefsUtil.isFingerprintEnable(getApplicationContext())
|
||||
|| !fingerPrintHelper.isFingerprintSupported(FingerprintManagerCompat.from(this))) {
|
||||
setFingerPrintVisibility(View.GONE);
|
||||
}
|
||||
// fingerprint is available but not configured show icon but in disabled state with some information
|
||||
else {
|
||||
// show explanations
|
||||
fingerprintContainerView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
FingerPrintDialog fingerPrintDialog = new FingerPrintDialog();
|
||||
fingerPrintDialog.show(getSupportFragmentManager(), "fingerprintDialog");
|
||||
}
|
||||
});
|
||||
setFingerPrintVisibility(View.VISIBLE);
|
||||
|
||||
if (!fingerPrintHelper.hasEnrolledFingerprints()) {
|
||||
fingerprintImageView.setAlpha(0.3f);
|
||||
// This happens when no fingerprints are registered. Listening won't start
|
||||
fingerprintTextView.setText(R.string.configure_fingerprint);
|
||||
}
|
||||
// finally fingerprint available and configured so we can use it
|
||||
else {
|
||||
fingerprintMustBeConfigured = false;
|
||||
fingerprintImageView.setAlpha(1f);
|
||||
|
||||
// fingerprint available but no stored password found yet for this DB so show info don't listen
|
||||
if (prefsNoBackup.getString(getPreferenceKeyValue(), null) == null) {
|
||||
fingerprintTextView.setText(R.string.no_password_stored);
|
||||
}
|
||||
// all is set here so we can confirm to user and start listening for fingerprints
|
||||
else {
|
||||
fingerprintTextView.setText(R.string.scanning_fingerprint);
|
||||
// listen for decryption by default
|
||||
toggleMode(Cipher.DECRYPT_MODE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEncryptedResult(
|
||||
final String value,
|
||||
final String ivSpec) {
|
||||
|
||||
prefsNoBackup.edit()
|
||||
.putString(getPreferenceKeyValue(), value)
|
||||
.putString(getPreferenceKeyIvSpec(), ivSpec)
|
||||
.apply();
|
||||
// and remove visual input to reset UI
|
||||
confirmButtonView.performClick();
|
||||
fingerprintTextView.setText(R.string.encrypted_value_stored);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDecryptedResult(final String value) {
|
||||
// on decrypt enter it for the purchase/login action
|
||||
passwordView.setText(value);
|
||||
confirmButtonView.performClick();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onInvalidKeyException() {
|
||||
Toast.makeText(this, R.string.fingerprint_invalid_key, Toast.LENGTH_SHORT).show();
|
||||
checkAvailability(); // restarts listening
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onFingerprintException(Exception e) {
|
||||
//Toast.makeText(this, R.string.fingerprint_error, Toast.LENGTH_SHORT).show();
|
||||
checkAvailability();
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
@Override
|
||||
public void onCheckedChanged(
|
||||
CompoundButton buttonView,
|
||||
boolean isChecked) {
|
||||
|
||||
String newDefaultFileName;
|
||||
|
||||
if (isChecked) {
|
||||
newDefaultFileName = mDbUri.toString();
|
||||
} else {
|
||||
newDefaultFileName = "";
|
||||
}
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(KEY_DEFAULT_FILENAME, newDefaultFileName);
|
||||
EditorCompat.apply(editor);
|
||||
|
||||
BackupManagerCompat backupManager = new BackupManagerCompat(PasswordActivity.this);
|
||||
backupManager.dataChanged();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class OkClickHandler implements View.OnClickListener {
|
||||
|
||||
public void onClick(View view) {
|
||||
String pass = passwordView.getText().toString();
|
||||
String key = keyFileView.getText().toString();
|
||||
loadDatabase(pass, key);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadDatabase(
|
||||
String pass,
|
||||
String keyfile) {
|
||||
loadDatabase(pass, UriUtil.parseDefaultFile(keyfile));
|
||||
}
|
||||
|
||||
private void loadDatabase(
|
||||
String pass,
|
||||
Uri keyfile) {
|
||||
|
||||
// Clear before we load
|
||||
Database db = App.getDB();
|
||||
db.clear();
|
||||
|
||||
// Clear the shutdown flag
|
||||
App.clearShutdown();
|
||||
|
||||
if (!checkboxPasswordView.isChecked()) {
|
||||
pass = "";
|
||||
}
|
||||
if (!checkboxKeyfileView.isChecked()) {
|
||||
keyfile = null;
|
||||
}
|
||||
|
||||
Handler handler = new Handler();
|
||||
LoadDB task = new LoadDB(db, PasswordActivity.this, mDbUri, pass, keyfile, new AfterLoad(handler, db));
|
||||
ProgressTask pt = new ProgressTask(PasswordActivity.this, task, R.string.loading_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
private String getEditText(int resId) {
|
||||
return Util.getEditText(this, resId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuUtil.defaultMenuInflater(getMenuInflater(), menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
|
||||
default:
|
||||
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private final class AfterLoad extends OnFinish {
|
||||
|
||||
private Database db;
|
||||
|
||||
AfterLoad(
|
||||
Handler handler,
|
||||
Database db) {
|
||||
super(handler);
|
||||
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (db.passwordEncodingError) {
|
||||
PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper();
|
||||
dialog.show(PasswordActivity.this, new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(
|
||||
DialogInterface dialog,
|
||||
int which) {
|
||||
GroupActivity.Launch(PasswordActivity.this);
|
||||
}
|
||||
|
||||
});
|
||||
} else if (mSuccess) {
|
||||
GroupActivity.Launch(PasswordActivity.this);
|
||||
} else {
|
||||
displayMessage(PasswordActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InitTask extends AsyncTask<Intent, Void, Integer> {
|
||||
|
||||
String password = "";
|
||||
boolean launch_immediately = false;
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Intent... args) {
|
||||
Intent i = args[0];
|
||||
String action = i.getAction();
|
||||
if (action != null && action.equals(VIEW_INTENT)) {
|
||||
Uri incoming = i.getData();
|
||||
mDbUri = incoming;
|
||||
|
||||
mKeyUri = ClipDataCompat.getUriFromIntent(i, KEY_KEYFILE);
|
||||
|
||||
if (incoming == null) {
|
||||
return R.string.error_can_not_handle_uri;
|
||||
} else if (incoming.getScheme().equals("file")) {
|
||||
String fileName = incoming.getPath();
|
||||
|
||||
if (fileName.length() == 0) {
|
||||
// No file name
|
||||
return R.string.FileNotFound;
|
||||
}
|
||||
|
||||
File dbFile = new File(fileName);
|
||||
if (!dbFile.exists()) {
|
||||
// File does not exist
|
||||
return R.string.FileNotFound;
|
||||
}
|
||||
|
||||
if (mKeyUri == null) {
|
||||
mKeyUri = getKeyFile(mDbUri);
|
||||
}
|
||||
} else if (incoming.getScheme().equals("content")) {
|
||||
if (mKeyUri == null) {
|
||||
mKeyUri = getKeyFile(mDbUri);
|
||||
}
|
||||
} else {
|
||||
return R.string.error_can_not_handle_uri;
|
||||
}
|
||||
password = i.getStringExtra(KEY_PASSWORD);
|
||||
launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false);
|
||||
|
||||
} else {
|
||||
mDbUri = UriUtil.parseDefaultFile(i.getStringExtra(KEY_FILENAME));
|
||||
mKeyUri = UriUtil.parseDefaultFile(i.getStringExtra(KEY_KEYFILE));
|
||||
password = i.getStringExtra(KEY_PASSWORD);
|
||||
launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false);
|
||||
|
||||
if (mKeyUri == null || mKeyUri.toString().length() == 0) {
|
||||
mKeyUri = getKeyFile(mDbUri);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onPostExecute(Integer result) {
|
||||
if (result != null) {
|
||||
Toast.makeText(PasswordActivity.this, result, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
populateView();
|
||||
|
||||
Button confirmButton = (Button) findViewById(R.id.pass_ok);
|
||||
confirmButton.setOnClickListener(new OkClickHandler());
|
||||
|
||||
if (password != null) {
|
||||
passwordView.setText(password);
|
||||
}
|
||||
|
||||
CompoundButton defaultCheck = (CompoundButton) findViewById(R.id.default_database);
|
||||
defaultCheck.setOnCheckedChangeListener(new DefaultCheckChange());
|
||||
|
||||
View browseView = findViewById(R.id.browse_button);
|
||||
keyFileHelper = new KeyFileHelper(PasswordActivity.this);
|
||||
browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener());
|
||||
|
||||
retrieveSettings();
|
||||
|
||||
if (launch_immediately) {
|
||||
loadDatabase(password, mKeyUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.view.PwEntryView;
|
||||
import com.keepassdroid.view.PwGroupView;
|
||||
|
||||
public class PwGroupListAdapter extends BaseAdapter {
|
||||
|
||||
private GroupBaseActivity mAct;
|
||||
private PwGroup mGroup;
|
||||
private List<PwGroup> groupsForViewing;
|
||||
private List<PwEntry> entriesForViewing;
|
||||
private Comparator<PwEntry> entryComp = new PwEntry.EntryNameComparator();
|
||||
private Comparator<PwGroup> groupComp = new PwGroup.GroupNameComparator();
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public PwGroupListAdapter(GroupBaseActivity act, PwGroup group) {
|
||||
mAct = act;
|
||||
mGroup = group;
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(act);
|
||||
|
||||
filterAndSort();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDataSetChanged() {
|
||||
super.notifyDataSetChanged();
|
||||
|
||||
filterAndSort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDataSetInvalidated() {
|
||||
super.notifyDataSetInvalidated();
|
||||
|
||||
filterAndSort();
|
||||
}
|
||||
|
||||
private void filterAndSort() {
|
||||
entriesForViewing = new ArrayList<PwEntry>();
|
||||
|
||||
for (int i = 0; i < mGroup.childEntries.size(); i++) {
|
||||
PwEntry entry = mGroup.childEntries.get(i);
|
||||
if ( ! entry.isMetaStream() ) {
|
||||
entriesForViewing.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
boolean sortLists = prefs.getBoolean(mAct.getString(R.string.sort_key), mAct.getResources().getBoolean(R.bool.sort_default));
|
||||
if ( sortLists ) {
|
||||
groupsForViewing = new ArrayList<PwGroup>(mGroup.childGroups);
|
||||
|
||||
Collections.sort(entriesForViewing, entryComp);
|
||||
Collections.sort(groupsForViewing, groupComp);
|
||||
} else {
|
||||
groupsForViewing = mGroup.childGroups;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
|
||||
return groupsForViewing.size() + entriesForViewing.size();
|
||||
}
|
||||
|
||||
public Object getItem(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
int size = groupsForViewing.size();
|
||||
|
||||
if ( position < size ) {
|
||||
return createGroupView(position, convertView);
|
||||
} else {
|
||||
return createEntryView(position - size, convertView);
|
||||
}
|
||||
}
|
||||
|
||||
private View createGroupView(int position, View convertView) {
|
||||
PwGroup group = groupsForViewing.get(position);
|
||||
PwGroupView gv;
|
||||
|
||||
if (convertView == null || !(convertView instanceof PwGroupView)) {
|
||||
|
||||
gv = PwGroupView.getInstance(mAct, group);
|
||||
}
|
||||
else {
|
||||
gv = (PwGroupView) convertView;
|
||||
gv.convertView(group);
|
||||
|
||||
}
|
||||
|
||||
return gv;
|
||||
}
|
||||
|
||||
private PwEntryView createEntryView(int position, View convertView) {
|
||||
PwEntry entry = entriesForViewing.get(position);
|
||||
PwEntryView ev;
|
||||
|
||||
if (convertView == null || !(convertView instanceof PwEntryView)) {
|
||||
ev = PwEntryView.getInstance(mAct, entry, position);
|
||||
}
|
||||
else {
|
||||
ev = (PwEntryView) convertView;
|
||||
ev.convertView(entry, position);
|
||||
}
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
@@ -27,8 +27,10 @@ import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
public class AboutActivity extends StylishActivity {
|
||||
|
||||
@@ -56,6 +58,9 @@ public class AboutActivity extends StylishActivity {
|
||||
version = getString(R.string.version_label) + " " + version;
|
||||
TextView versionText = (TextView) findViewById(R.id.activity_about_version);
|
||||
versionText.setText(version);
|
||||
|
||||
TextView disclaimerText = (TextView) findViewById(R.id.disclaimer);
|
||||
disclaimerText.setText(getString(R.string.disclaimer_formal, new DateTime().getYear()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -18,7 +18,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
@@ -32,14 +32,14 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.SpannableString;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.text.util.Linkify;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -49,87 +49,59 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.ActivityCompat;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.exception.SamsungClipboardException;
|
||||
import com.keepassdroid.intents.Intents;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.password.PasswordActivity;
|
||||
import com.keepassdroid.tasks.UIToastTask;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.Types;
|
||||
import com.keepassdroid.utils.Util;
|
||||
import com.keepassdroid.view.EntryContentsView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.keepassdroid.settings.PrefsUtil.isClipboardNotificationsEnable;
|
||||
import static com.keepassdroid.settings.PreferencesUtil.isClipboardNotificationsEnable;
|
||||
|
||||
public class EntryActivity extends LockCloseHideActivity {
|
||||
public class EntryActivity extends LockingHideActivity {
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
public static final String KEY_REFRESH_POS = "refresh_pos";
|
||||
|
||||
public static final int NOTIFY_USERNAME = 1;
|
||||
public static final int NOTIFY_PASSWORD = 2;
|
||||
|
||||
public static void Launch(Activity act, PwEntry pw, int pos) {
|
||||
Intent i;
|
||||
|
||||
if ( pw instanceof PwEntryV4 ) {
|
||||
i = new Intent(act, EntryActivityV4.class);
|
||||
} else {
|
||||
i = new Intent(act, EntryActivity.class);
|
||||
}
|
||||
|
||||
i.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
i.putExtra(KEY_REFRESH_POS, pos);
|
||||
|
||||
act.startActivityForResult(i,0);
|
||||
}
|
||||
private ImageView titleIconView;
|
||||
private TextView titleView;
|
||||
private EntryContentsView entryContentsView;
|
||||
|
||||
protected PwEntry mEntry;
|
||||
private Timer mTimer = new Timer();
|
||||
private boolean mShowPassword;
|
||||
private int mPos;
|
||||
private NotificationManager mNM;
|
||||
private BroadcastReceiver mIntentReceiver;
|
||||
protected boolean readOnly = false;
|
||||
|
||||
private DateFormat dateFormat;
|
||||
private DateFormat timeFormat;
|
||||
|
||||
protected void setEntryView() {
|
||||
setContentView(R.layout.entry_view);
|
||||
}
|
||||
|
||||
protected void setupEditButtons() {
|
||||
View edit = findViewById(R.id.entry_edit);
|
||||
edit.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
EntryEditActivity.Launch(EntryActivity.this, mEntry);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (readOnly) {
|
||||
edit.setVisibility(View.GONE);
|
||||
}
|
||||
public static void launch(Activity act, PwEntry pw) {
|
||||
Intent intent = new Intent(act, EntryActivity.class);
|
||||
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
mShowPassword = ! prefs.getBoolean(getString(R.string.maskpass_key), getResources().getBoolean(R.bool.maskpass_default));
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setEntryView();
|
||||
|
||||
setContentView(R.layout.entry_view);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
@@ -138,10 +110,6 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
Context appCtx = getApplicationContext();
|
||||
dateFormat = android.text.format.DateFormat.getDateFormat(appCtx);
|
||||
timeFormat = android.text.format.DateFormat.getTimeFormat(appCtx);
|
||||
|
||||
Database db = App.getDB();
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! db.Loaded() ) {
|
||||
@@ -150,12 +118,11 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
}
|
||||
readOnly = db.readOnly;
|
||||
|
||||
setResult(KeePass.EXIT_NORMAL);
|
||||
mShowPassword = !PreferencesUtil.isPasswordMask(this);
|
||||
|
||||
// Get Entry from UUID
|
||||
Intent i = getIntent();
|
||||
UUID uuid = Types.bytestoUUID(i.getByteArrayExtra(KEY_ENTRY));
|
||||
mPos = i.getIntExtra(KEY_REFRESH_POS, -1);
|
||||
|
||||
mEntry = db.pm.entries.get(uuid);
|
||||
if (mEntry == null) {
|
||||
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show();
|
||||
@@ -169,9 +136,26 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
// Update last access time.
|
||||
mEntry.touch(false, false);
|
||||
|
||||
fillData(false);
|
||||
// Get views
|
||||
titleIconView = (ImageView) findViewById(R.id.entry_icon);
|
||||
titleView = (TextView) findViewById(R.id.entry_title);
|
||||
entryContentsView = (EntryContentsView) findViewById(R.id.entry_contents);
|
||||
entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this));
|
||||
|
||||
setupEditButtons();
|
||||
fillData();
|
||||
|
||||
// Setup Edit Buttons
|
||||
View edit = findViewById(R.id.entry_edit);
|
||||
edit.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
EntryEditActivity.Launch(EntryActivity.this, mEntry);
|
||||
}
|
||||
|
||||
});
|
||||
if (readOnly) {
|
||||
edit.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// If notifications enabled in settings
|
||||
if (isClipboardNotificationsEnable(getApplicationContext())) {
|
||||
@@ -254,60 +238,88 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
return notify;
|
||||
}
|
||||
|
||||
private String getDateTime(Date dt) {
|
||||
return dateFormat.format(dt) + " " + timeFormat.format(dt);
|
||||
|
||||
private void populateTitle(Drawable drawIcon, String text) {
|
||||
titleIconView.setImageDrawable(drawIcon);
|
||||
titleView.setText(text);
|
||||
}
|
||||
|
||||
protected void fillData(boolean trimList) {
|
||||
ImageView iv = (ImageView) findViewById(R.id.entry_icon);
|
||||
protected void fillData() {
|
||||
Database db = App.getDB();
|
||||
db.drawFactory.assignDrawableTo(iv, getResources(), mEntry.getIcon());
|
||||
|
||||
PwDatabase pm = db.pm;
|
||||
|
||||
populateText(R.id.entry_title, mEntry.getTitle(true, pm));
|
||||
populateText(R.id.entry_user_name, mEntry.getUsername(true, pm));
|
||||
// Assign title
|
||||
populateTitle(db.drawFactory.getIconDrawable(getResources(), mEntry.getIcon()),
|
||||
mEntry.getTitle(true, pm));
|
||||
|
||||
populateText(R.id.entry_url, mEntry.getUrl(true, pm));
|
||||
populateText(R.id.entry_password, mEntry.getPassword(true, pm));
|
||||
setPasswordStyle();
|
||||
// Assign basic fields
|
||||
entryContentsView.assignUserName(mEntry.getUsername(true, pm));
|
||||
entryContentsView.assignUserNameCopyListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm),
|
||||
getString(R.string.copy_field, getString(R.string.entry_user_name)));
|
||||
}
|
||||
});
|
||||
|
||||
populateText(R.id.entry_created, getDateTime(mEntry.getCreationTime()));
|
||||
populateText(R.id.entry_modified, getDateTime(mEntry.getLastModificationTime()));
|
||||
populateText(R.id.entry_accessed, getDateTime(mEntry.getLastAccessTime()));
|
||||
entryContentsView.assignPassword(mEntry.getPassword(true, pm));
|
||||
entryContentsView.assignPasswordCopyListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
timeoutCopyToClipboard(mEntry.getPassword(true, App.getDB().pm),
|
||||
getString(R.string.copy_field, getString(R.string.entry_password)));
|
||||
}
|
||||
});
|
||||
|
||||
entryContentsView.assignURL(mEntry.getUrl(true, pm));
|
||||
|
||||
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
|
||||
entryContentsView.assignComment(mEntry.getNotes(true, pm));
|
||||
|
||||
// Assign custom fields
|
||||
if (mEntry.allowExtraFields()) {
|
||||
entryContentsView.clearExtraFields();
|
||||
for (Map.Entry<String, String> field : mEntry.getExtraFields(pm).entrySet()) {
|
||||
final String label = field.getKey();
|
||||
final String value = field.getValue();
|
||||
entryContentsView.addExtraField(label, value, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
timeoutCopyToClipboard(value, getString(R.string.copy_field, label));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Assign dates
|
||||
entryContentsView.assignCreationDate(mEntry.getCreationTime());
|
||||
entryContentsView.assignModificationDate(mEntry.getLastModificationTime());
|
||||
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime());
|
||||
Date expires = mEntry.getExpiryTime();
|
||||
if ( mEntry.expires() ) {
|
||||
populateText(R.id.entry_expires, getDateTime(expires));
|
||||
entryContentsView.assignExpiresDate(expires);
|
||||
} else {
|
||||
populateText(R.id.entry_expires, R.string.never);
|
||||
entryContentsView.assignExpiresDate(getString(R.string.never));
|
||||
}
|
||||
populateText(R.id.entry_comment, mEntry.getNotes(true, pm));
|
||||
|
||||
}
|
||||
|
||||
private void populateText(int viewId, int resId) {
|
||||
TextView tv = (TextView) findViewById(viewId);
|
||||
tv.setText(resId);
|
||||
}
|
||||
|
||||
private void populateText(int viewId, String text) {
|
||||
TextView tv = (TextView) findViewById(viewId);
|
||||
tv.setText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if ( resultCode == KeePass.EXIT_REFRESH || resultCode == KeePass.EXIT_REFRESH_TITLE ) {
|
||||
fillData(true);
|
||||
if ( resultCode == KeePass.EXIT_REFRESH_TITLE ) {
|
||||
Intent ret = new Intent();
|
||||
ret.putExtra(KEY_REFRESH_POS, mPos);
|
||||
setResult(KeePass.EXIT_REFRESH, ret);
|
||||
switch (requestCode) {
|
||||
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
|
||||
fillData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void changeShowPasswordIcon(MenuItem togglePassword) {
|
||||
if ( mShowPassword ) {
|
||||
togglePassword.setTitle(R.string.menu_hide_password);
|
||||
togglePassword.setIcon(R.drawable.ic_visibility_off_white_24dp);
|
||||
} else {
|
||||
togglePassword.setTitle(R.string.menu_showpass);
|
||||
togglePassword.setIcon(R.drawable.ic_visibility_white_24dp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -317,27 +329,21 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.donationMenuInflater(inflater, menu);
|
||||
inflater.inflate(R.menu.entry, menu);
|
||||
inflater.inflate(R.menu.lock_database, menu);
|
||||
inflater.inflate(R.menu.database_lock, menu);
|
||||
|
||||
MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass);
|
||||
if ( mShowPassword ) {
|
||||
togglePassword.setTitle(R.string.menu_hide_password);
|
||||
togglePassword.setIcon(R.drawable.ic_visibility_off_white_24dp);
|
||||
if (!entryContentsView.isPasswordPresent()) {
|
||||
togglePassword.setVisible(false);
|
||||
} else {
|
||||
togglePassword.setTitle(R.string.menu_showpass);
|
||||
togglePassword.setIcon(R.drawable.ic_visibility_white_24dp);
|
||||
changeShowPasswordIcon(togglePassword);
|
||||
}
|
||||
|
||||
MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url);
|
||||
MenuItem copyUser = menu.findItem(R.id.menu_copy_user);
|
||||
MenuItem copyPass = menu.findItem(R.id.menu_copy_pass);
|
||||
|
||||
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
|
||||
// so mEntry may not be set
|
||||
if (mEntry == null) {
|
||||
gotoUrl.setVisible(false);
|
||||
copyUser.setVisible(false);
|
||||
copyPass.setVisible(false);
|
||||
}
|
||||
else {
|
||||
String url = mEntry.getUrl();
|
||||
@@ -345,29 +351,11 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
// disable button if url is not available
|
||||
gotoUrl.setVisible(false);
|
||||
}
|
||||
if ( mEntry.getUsername().length() == 0 ) {
|
||||
// disable button if username is not available
|
||||
copyUser.setVisible(false);
|
||||
}
|
||||
if ( mEntry.getPassword().length() == 0 ) {
|
||||
// disable button if password is not available
|
||||
copyPass.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setPasswordStyle() {
|
||||
TextView password = (TextView) findViewById(R.id.entry_password);
|
||||
|
||||
if ( mShowPassword ) {
|
||||
password.setTransformationMethod(null);
|
||||
} else {
|
||||
password.setTransformationMethod(PasswordTransformationMethod.getInstance());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
@@ -375,16 +363,9 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
return MenuUtil.onDonationItemSelected(this);
|
||||
|
||||
case R.id.menu_toggle_pass:
|
||||
if ( mShowPassword ) {
|
||||
item.setTitle(R.string.menu_showpass);
|
||||
item.setIcon(R.drawable.ic_visibility_white_24dp);
|
||||
mShowPassword = false;
|
||||
} else {
|
||||
item.setTitle(R.string.menu_hide_password);
|
||||
item.setIcon(R.drawable.ic_visibility_off_white_24dp);
|
||||
mShowPassword = true;
|
||||
}
|
||||
setPasswordStyle();
|
||||
mShowPassword = !mShowPassword;
|
||||
changeShowPasswordIcon(item);
|
||||
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
|
||||
return true;
|
||||
|
||||
case R.id.menu_goto_url:
|
||||
@@ -403,17 +384,9 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.menu_copy_user:
|
||||
timeoutCopyToClipboard(mEntry.getUsername(true, App.getDB().pm));
|
||||
return true;
|
||||
|
||||
case R.id.menu_copy_pass:
|
||||
timeoutCopyToClipboard(mEntry.getPassword(true, App.getDB().pm));
|
||||
return true;
|
||||
|
||||
case R.id.menu_lock:
|
||||
App.setShutdown();
|
||||
setResult(KeePass.EXIT_LOCK);
|
||||
setResult(PasswordActivity.RESULT_EXIT_LOCK);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
@@ -425,6 +398,13 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
}
|
||||
|
||||
private void timeoutCopyToClipboard(String text) {
|
||||
timeoutCopyToClipboard(text, "");
|
||||
}
|
||||
|
||||
private void timeoutCopyToClipboard(String text, String toastString) {
|
||||
if (!toastString.isEmpty())
|
||||
Toast.makeText(EntryActivity.this, toastString, Toast.LENGTH_LONG).show();
|
||||
|
||||
try {
|
||||
Util.copyToClipboard(this, text);
|
||||
} catch (SamsungClipboardException e) {
|
||||
@@ -442,6 +422,17 @@ public class EntryActivity extends LockCloseHideActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
// Transit data in previous Activity after an update
|
||||
/*
|
||||
TODO Slowdown when add entry as result
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
|
||||
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
|
||||
*/
|
||||
super.finish();
|
||||
}
|
||||
|
||||
// Setup to allow the toast to happen in the foreground
|
||||
final Handler uiThreadCallback = new Handler();
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwEntryV4;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwIconStandard;
|
||||
import com.keepassdroid.database.edit.AddEntry;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.database.edit.RunnableOnFinish;
|
||||
import com.keepassdroid.database.edit.UpdateEntry;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.fragments.GeneratePasswordDialogFragment;
|
||||
import com.keepassdroid.fragments.IconPickerDialogFragment;
|
||||
import com.keepassdroid.icons.Icons;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.tasks.ProgressTask;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.Types;
|
||||
import com.keepassdroid.utils.Util;
|
||||
import com.keepassdroid.view.EntryEditNewField;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntryEditActivity extends LockingHideActivity
|
||||
implements IconPickerDialogFragment.IconPickerListener,
|
||||
GeneratePasswordDialogFragment.GeneratePasswordListener {
|
||||
|
||||
// Keys for current Activity
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
public static final String KEY_PARENT = "parent";
|
||||
|
||||
// Keys for callback
|
||||
public static final int ADD_ENTRY_RESULT_CODE = 31;
|
||||
public static final int UPDATE_ENTRY_RESULT_CODE = 32;
|
||||
public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
|
||||
public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
|
||||
|
||||
protected PwEntry mEntry;
|
||||
protected PwEntry mCallbackNewEntry;
|
||||
protected boolean mIsNew;
|
||||
protected int mSelectedIconID = -1;
|
||||
|
||||
// Views
|
||||
private ScrollView scrollView;
|
||||
private TextView entryTitleView;
|
||||
private TextView entryUserNameView;
|
||||
private TextView entryUrlView;
|
||||
private TextView entryPasswordView;
|
||||
private TextView entryConfirmationPasswordView;
|
||||
private TextView entryCommentView;
|
||||
private ViewGroup entryExtraFieldsContainer;
|
||||
|
||||
/**
|
||||
* launch EntryEditActivity to update an existing entry
|
||||
* @param act from activity
|
||||
* @param pw Entry to update
|
||||
*/
|
||||
public static void Launch(Activity act, PwEntry pw) {
|
||||
Intent intent = new Intent(act, EntryEditActivity.class);
|
||||
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
|
||||
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* launch EntryEditActivity to add a new entry
|
||||
* @param act from activity
|
||||
* @param pwGroup Group who will contains new entry
|
||||
*/
|
||||
public static void Launch(Activity act, PwGroup pwGroup) {
|
||||
Intent intent = new Intent(act, EntryEditActivity.class);
|
||||
intent.putExtra(KEY_PARENT, pwGroup.getId());
|
||||
act.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.entry_edit);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
scrollView = (ScrollView) findViewById(R.id.entry_scroll);
|
||||
scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
|
||||
|
||||
entryTitleView = (TextView) findViewById(R.id.entry_title);
|
||||
entryUserNameView = (TextView) findViewById(R.id.entry_user_name);
|
||||
entryUrlView = (TextView) findViewById(R.id.entry_url);
|
||||
entryPasswordView = (TextView) findViewById(R.id.entry_password);
|
||||
entryConfirmationPasswordView = (TextView) findViewById(R.id.entry_confpassword);
|
||||
entryCommentView = (TextView) findViewById(R.id.entry_comment);
|
||||
entryExtraFieldsContainer = (ViewGroup) findViewById(R.id.advanced_container);
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
Database db = App.getDB();
|
||||
if ( ! db.Loaded() ) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
byte[] uuidBytes = intent.getByteArrayExtra(KEY_ENTRY);
|
||||
|
||||
PwDatabase pm = db.pm;
|
||||
if ( uuidBytes == null ) {
|
||||
PwGroupId parentId = (PwGroupId) intent.getSerializableExtra(KEY_PARENT);
|
||||
PwGroup parent = pm.groups.get(parentId);
|
||||
mEntry = PwEntry.getInstance(parent);
|
||||
mIsNew = true;
|
||||
} else {
|
||||
UUID uuid = Types.bytestoUUID(uuidBytes);
|
||||
mEntry = pm.entries.get(uuid);
|
||||
mIsNew = false;
|
||||
fillData();
|
||||
}
|
||||
|
||||
View iconButton = findViewById(R.id.icon_button);
|
||||
iconButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
IconPickerDialogFragment.launch(EntryEditActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
// Generate password button
|
||||
View generatePassword = findViewById(R.id.generate_button);
|
||||
generatePassword.setOnClickListener(new OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment();
|
||||
generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment");
|
||||
}
|
||||
});
|
||||
|
||||
// Save button
|
||||
View save = findViewById(R.id.entry_save);
|
||||
save.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
if (!validateBeforeSaving()) {
|
||||
return;
|
||||
}
|
||||
mCallbackNewEntry = populateNewEntry();
|
||||
|
||||
OnFinish onFinish = new AfterSave();
|
||||
EntryEditActivity act = EntryEditActivity.this;
|
||||
RunnableOnFinish task;
|
||||
if ( mIsNew ) {
|
||||
task = new AddEntry(act, App.getDB(), mCallbackNewEntry, onFinish);
|
||||
} else {
|
||||
task = new UpdateEntry(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
|
||||
}
|
||||
ProgressTask pt = new ProgressTask(act, task, R.string.saving_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
if (mEntry.allowExtraFields()) {
|
||||
View add = findViewById(R.id.add_new_field);
|
||||
add.setVisibility(View.VISIBLE);
|
||||
add.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
EntryEditNewField ees = new EntryEditNewField(EntryEditActivity.this);
|
||||
ees.setData("", new ProtectedString(false, ""));
|
||||
entryExtraFieldsContainer.addView(ees);
|
||||
|
||||
// Scroll bottom
|
||||
scrollView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scrollView.fullScroll(ScrollView.FOCUS_DOWN);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean validateBeforeSaving() {
|
||||
// Require title
|
||||
String title = entryTitleView.getText().toString();
|
||||
if ( title.length() == 0 ) {
|
||||
Toast.makeText(this, R.string.error_title_required, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate password
|
||||
String pass = entryPasswordView.getText().toString();
|
||||
String conf = entryConfirmationPasswordView.getText().toString();
|
||||
if ( ! pass.equals(conf) ) {
|
||||
Toast.makeText(this, R.string.error_pass_match, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate extra fields
|
||||
if (mEntry.allowExtraFields()) {
|
||||
for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
|
||||
EntryEditNewField entryEditNewField = (EntryEditNewField) entryExtraFieldsContainer.getChildAt(i);
|
||||
String key = entryEditNewField.getLabel();
|
||||
if (key == null || key.length() == 0) {
|
||||
Toast.makeText(this, R.string.error_string_key, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected PwEntry populateNewEntry() {
|
||||
if (mEntry instanceof PwEntryV4) {
|
||||
// TODO backup
|
||||
PwEntryV4 newEntry = (PwEntryV4) mEntry.clone(true);
|
||||
newEntry.history = (ArrayList<PwEntryV4>) newEntry.history.clone();
|
||||
newEntry.createBackup((PwDatabaseV4) App.getDB().pm);
|
||||
}
|
||||
|
||||
PwEntry newEntry = mEntry.clone(true);
|
||||
|
||||
Date now = Calendar.getInstance().getTime();
|
||||
newEntry.setLastAccessTime(now);
|
||||
newEntry.setLastModificationTime(now);
|
||||
|
||||
PwDatabase db = App.getDB().pm;
|
||||
newEntry.setTitle(entryTitleView.getText().toString(), db);
|
||||
if(mSelectedIconID != -1)
|
||||
// or TODO icon factory newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(mSelectedIconID));
|
||||
newEntry.setIcon(new PwIconStandard(mSelectedIconID));
|
||||
else {
|
||||
if (mIsNew) {
|
||||
newEntry.setIcon(App.getDB().pm.iconFactory.getIcon(0));
|
||||
}
|
||||
else {
|
||||
// Keep previous icon, if no new one was selected
|
||||
newEntry.setIcon(mEntry.icon);
|
||||
}
|
||||
}
|
||||
newEntry.setUrl(entryUrlView.getText().toString(), db);
|
||||
newEntry.setUsername(entryUserNameView.getText().toString(), db);
|
||||
newEntry.setNotes(entryCommentView.getText().toString(), db);
|
||||
newEntry.setPassword(entryPasswordView.getText().toString(), db);
|
||||
|
||||
if (newEntry.allowExtraFields()) {
|
||||
// Delete all new standard strings
|
||||
newEntry.removeExtraFields();
|
||||
// Add extra fields from views
|
||||
for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) {
|
||||
EntryEditNewField view = (EntryEditNewField) entryExtraFieldsContainer.getChildAt(i);
|
||||
String key = view.getLabel();
|
||||
String value = view.getValue();
|
||||
boolean protect = view.isProtected();
|
||||
newEntry.addField(key, new ProtectedString(protect, value));
|
||||
}
|
||||
}
|
||||
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.donationMenuInflater(inflater, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
case R.id.menu_donate:
|
||||
return MenuUtil.onDonationItemSelected(this);
|
||||
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
protected void fillData() {
|
||||
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
|
||||
App.getDB().drawFactory.assignDrawableTo(currIconButton, getResources(), mEntry.getIcon());
|
||||
|
||||
boolean visibilityFont = PreferencesUtil.fieldFontIsInVisibility(this);
|
||||
|
||||
entryTitleView.setText(mEntry.getTitle());
|
||||
entryUserNameView.setText(mEntry.getUsername());
|
||||
entryUrlView.setText(mEntry.getUrl());
|
||||
String password = mEntry.getPassword();
|
||||
entryPasswordView.setText(password);
|
||||
entryConfirmationPasswordView.setText(password);
|
||||
entryCommentView.setText(mEntry.getNotes());
|
||||
Util.applyFontVisibilityToTextView(visibilityFont, entryCommentView);
|
||||
|
||||
if (mEntry.allowExtraFields()) {
|
||||
LinearLayout container = (LinearLayout) findViewById(R.id.advanced_container);
|
||||
for (Map.Entry<String, ProtectedString> pair : mEntry.getExtraProtectedFields().entrySet()) {
|
||||
EntryEditNewField entryEditNewField = new EntryEditNewField(EntryEditActivity.this);
|
||||
entryEditNewField.setData(pair.getKey(), pair.getValue());
|
||||
entryEditNewField.setFontVisibility(visibilityFont);
|
||||
container.addView(entryEditNewField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
||||
ImageButton currIconButton = (ImageButton) findViewById(R.id.icon_button);
|
||||
currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptPassword(Bundle bundle) {
|
||||
String generatedPassword = bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID);
|
||||
entryPasswordView.setText(generatedPassword);
|
||||
entryConfirmationPasswordView.setText(generatedPassword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelPassword(Bundle bundle) {
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
// Assign entry callback as a result in all case
|
||||
if (mCallbackNewEntry != null) {
|
||||
Intent intentEntry = new Intent();
|
||||
if (mIsNew) {
|
||||
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
|
||||
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);
|
||||
} else {
|
||||
intentEntry.putExtra(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
|
||||
setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry);
|
||||
}
|
||||
}
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private final class AfterSave extends OnFinish {
|
||||
|
||||
AfterSave() {
|
||||
super(new Handler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ( mSuccess ) {
|
||||
finish();
|
||||
} else {
|
||||
displayMessage(EntryEditActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
426
app/src/main/java/com/keepassdroid/activities/GroupActivity.java
Normal file
426
app/src/main/java/com/keepassdroid/activities/GroupActivity.java
Normal file
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.SearchManager;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.keepassdroid.adapters.NodeAdapter;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.autofill.AutofillHelper;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwGroupId;
|
||||
import com.keepassdroid.database.PwNode;
|
||||
import com.keepassdroid.database.SortNodeEnum;
|
||||
import com.keepassdroid.database.edit.AddGroup;
|
||||
import com.keepassdroid.database.edit.DeleteEntry;
|
||||
import com.keepassdroid.database.edit.DeleteGroup;
|
||||
import com.keepassdroid.dialog.ReadOnlyDialog;
|
||||
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.fragments.GroupEditDialogFragment;
|
||||
import com.keepassdroid.fragments.IconPickerDialogFragment;
|
||||
import com.keepassdroid.password.PasswordActivity;
|
||||
import com.keepassdroid.search.SearchResultsActivity;
|
||||
import com.keepassdroid.tasks.ProgressTask;
|
||||
import com.keepassdroid.view.ListNodesWithAddButtonView;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class GroupActivity extends ListNodesActivity
|
||||
implements GroupEditDialogFragment.EditGroupListener, IconPickerDialogFragment.IconPickerListener {
|
||||
|
||||
protected boolean addGroupEnabled = false;
|
||||
protected boolean addEntryEnabled = false;
|
||||
protected boolean isRoot = false;
|
||||
protected boolean readOnly = false;
|
||||
protected EditGroupDialogAction editGroupDialogAction = EditGroupDialogAction.NONE;
|
||||
private ListNodesWithAddButtonView rootView;
|
||||
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
private enum EditGroupDialogAction {
|
||||
CREATION, UPDATE, NONE
|
||||
}
|
||||
|
||||
private static final String TAG = "Group Activity:";
|
||||
|
||||
public static void launch(Activity act) {
|
||||
launch(act, (PwGroup) null);
|
||||
}
|
||||
|
||||
public static void launch(Activity act, PwGroup group) {
|
||||
Intent intent = new Intent(act, GroupActivity.class);
|
||||
if ( group != null ) {
|
||||
intent.putExtra(KEY_ENTRY, group.getId());
|
||||
}
|
||||
act.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void launch(Activity act, AssistStructure assistStructure) {
|
||||
launch(act, null, assistStructure);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void launch(Activity act, PwGroup group, AssistStructure assistStructure) {
|
||||
if ( assistStructure != null ) {
|
||||
Intent intent = new Intent(act, GroupActivity.class);
|
||||
if ( group != null ) {
|
||||
intent.putExtra(KEY_ENTRY, group.getId());
|
||||
}
|
||||
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
||||
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||
} else {
|
||||
launch(act, group);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Log.w(TAG, "Retrieved tree");
|
||||
if ( mCurrentGroup == null ) {
|
||||
Log.w(TAG, "Group was null");
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct main view
|
||||
rootView = new ListNodesWithAddButtonView(this);
|
||||
rootView.enableAddGroup(addGroupEnabled);
|
||||
rootView.enableAddEntry(addEntryEnabled);
|
||||
setContentView(rootView);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar.setTitle("");
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if ( mCurrentGroup.getParent() != null )
|
||||
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
|
||||
|
||||
rootView.setAddGroupClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
editGroupDialogAction = EditGroupDialogAction.CREATION;
|
||||
GroupEditDialogFragment groupEditDialogFragment = new GroupEditDialogFragment();
|
||||
groupEditDialogFragment.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
}
|
||||
});
|
||||
rootView.setAddEntryClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
EntryEditActivity.Launch(GroupActivity.this, mCurrentGroup);
|
||||
}
|
||||
});
|
||||
|
||||
setGroupTitle();
|
||||
setGroupIcon();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
autofillHelper.retrieveAssistStructure(getIntent());
|
||||
}
|
||||
|
||||
Log.w(TAG, "Finished creating tree");
|
||||
|
||||
if (isRoot) {
|
||||
showWarnings();
|
||||
}
|
||||
}
|
||||
|
||||
protected PwGroup initCurrentGroup() {
|
||||
PwGroup currentGroup;
|
||||
Database db = App.getDB();
|
||||
readOnly = db.readOnly;
|
||||
PwGroup root = db.pm.rootGroup;
|
||||
|
||||
Log.w(TAG, "Creating tree view");
|
||||
PwGroupId pwGroupId = (PwGroupId) getIntent().getSerializableExtra(KEY_ENTRY);
|
||||
if ( pwGroupId == null ) {
|
||||
currentGroup = root;
|
||||
} else {
|
||||
currentGroup = db.pm.groups.get(pwGroupId);
|
||||
}
|
||||
|
||||
addGroupEnabled = !readOnly;
|
||||
addEntryEnabled = !readOnly;
|
||||
|
||||
isRoot = (currentGroup == root);
|
||||
if ( !currentGroup.allowAddEntryIfIsRoot() )
|
||||
addEntryEnabled = !isRoot && addEntryEnabled;
|
||||
|
||||
return currentGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView defineNodeList() {
|
||||
return (RecyclerView) findViewById(R.id.nodes_list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNodeClick(PwNode node) {
|
||||
// Add event when we have Autofill
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
mAdapter.registerANodeToUpdate(node);
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
GroupActivity.launch(this, (PwGroup) node, assistStructure);
|
||||
break;
|
||||
case ENTRY:
|
||||
// Build response with the entry selected
|
||||
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( assistStructure == null ){
|
||||
super.onNodeClick(node);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addOptionsToAdapter(NodeAdapter nodeAdapter) {
|
||||
super.addOptionsToAdapter(nodeAdapter);
|
||||
|
||||
nodeAdapter.setActivateContextMenu(true);
|
||||
nodeAdapter.setNodeMenuListener(new NodeAdapter.NodeMenuListener() {
|
||||
@Override
|
||||
public boolean onOpenMenuClick(PwNode node) {
|
||||
mAdapter.registerANodeToUpdate(node);
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
GroupActivity.launch(GroupActivity.this, (PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntryActivity.launch(GroupActivity.this, (PwEntry) node);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onEditMenuClick(PwNode node) {
|
||||
mAdapter.registerANodeToUpdate(node);
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
editGroupDialogAction = EditGroupDialogAction.UPDATE;
|
||||
GroupEditDialogFragment groupEditDialogFragment =
|
||||
GroupEditDialogFragment.build(node);
|
||||
groupEditDialogFragment.show(getSupportFragmentManager(),
|
||||
GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntryEditActivity.Launch(GroupActivity.this, (PwEntry) node);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDeleteMenuClick(PwNode node) {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
deleteGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
deleteEntry((PwEntry) node);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Show button on resume
|
||||
rootView.showButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
|
||||
super.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
|
||||
|
||||
// Show button if hide after sort
|
||||
rootView.showButton();
|
||||
}
|
||||
|
||||
protected void setGroupIcon() {
|
||||
if (mCurrentGroup != null) {
|
||||
ImageView iv = (ImageView) findViewById(R.id.icon);
|
||||
App.getDB().drawFactory.assignDrawableTo(iv, getResources(), mCurrentGroup.getIcon());
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteEntry(PwEntry entry) {
|
||||
Handler handler = new Handler();
|
||||
DeleteEntry task = new DeleteEntry(this, App.getDB(), entry,
|
||||
new AfterDeleteNode(handler, entry));
|
||||
ProgressTask pt = new ProgressTask(this, task, R.string.saving_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
private void deleteGroup(PwGroup group) {
|
||||
//TODO Verify trash recycle bin
|
||||
Handler handler = new Handler();
|
||||
DeleteGroup task = new DeleteGroup(this, App.getDB(), group,
|
||||
new AfterDeleteNode(handler, group));
|
||||
ProgressTask pt = new ProgressTask(this, task, R.string.saving_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.search, menu);
|
||||
inflater.inflate(R.menu.database_master_key, menu);
|
||||
inflater.inflate(R.menu.database_lock, menu);
|
||||
|
||||
// Get the SearchView and set the searchable configuration
|
||||
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
|
||||
assert searchManager != null;
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.menu_search);
|
||||
SearchView searchView = null;
|
||||
if (searchItem != null) {
|
||||
searchView = (SearchView) searchItem.getActionView();
|
||||
}
|
||||
if (searchView != null) {
|
||||
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
|
||||
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
|
||||
case R.id.menu_search:
|
||||
onSearchRequested();
|
||||
return true;
|
||||
|
||||
case R.id.menu_lock:
|
||||
App.setShutdown();
|
||||
setResult(PasswordActivity.RESULT_EXIT_LOCK);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
case R.id.menu_change_master_key:
|
||||
setPassword();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setPassword() {
|
||||
AssignMasterKeyDialogFragment dialog = new AssignMasterKeyDialogFragment();
|
||||
dialog.show(getSupportFragmentManager(), "passwordDialog");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void approveEditGroup(Bundle bundle) {
|
||||
String GroupName = bundle.getString(GroupEditDialogFragment.KEY_NAME);
|
||||
int GroupIconID = bundle.getInt(GroupEditDialogFragment.KEY_ICON_ID);
|
||||
switch (editGroupDialogAction) {
|
||||
case CREATION:
|
||||
// If edit group creation
|
||||
Handler handler = new Handler();
|
||||
AddGroup task = new AddGroup(this, App.getDB(), GroupName, GroupIconID, mCurrentGroup,
|
||||
new AfterAddNode(handler), false);
|
||||
ProgressTask pt = new ProgressTask(this, task, R.string.saving_database);
|
||||
pt.run();
|
||||
break;
|
||||
case UPDATE:
|
||||
// If edit group update
|
||||
// TODO UpdateGroup
|
||||
break;
|
||||
}
|
||||
editGroupDialogAction = EditGroupDialogAction.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelEditGroup(Bundle bundle) {
|
||||
// Do nothing here
|
||||
}
|
||||
|
||||
@Override
|
||||
// For icon in create tree dialog
|
||||
public void iconPicked(Bundle bundle) {
|
||||
GroupEditDialogFragment groupEditDialogFragment =
|
||||
(GroupEditDialogFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(GroupEditDialogFragment.TAG_CREATE_GROUP);
|
||||
if (groupEditDialogFragment != null) {
|
||||
groupEditDialogFragment.iconPicked(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
protected void showWarnings() {
|
||||
if (App.getDB().readOnly) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
if (prefs.getBoolean(getString(R.string.show_read_only_warning), true)) {
|
||||
Dialog dialog = new ReadOnlyDialog(this);
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.keepassdroid.adapters.NodeAdapter;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.ActivityCompat;
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwNode;
|
||||
import com.keepassdroid.database.edit.AfterAddNodeOnFinish;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.fragments.SortDialogFragment;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.tasks.UIToastTask;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.database.SortNodeEnum;
|
||||
import com.keepassdroid.view.AssignPasswordHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public abstract class ListNodesActivity extends LockingActivity
|
||||
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
NodeAdapter.OnNodeClickCallback,
|
||||
SortDialogFragment.SortSelectionListener {
|
||||
|
||||
public static final String KEY_ENTRY = "entry";
|
||||
|
||||
protected PwGroup mCurrentGroup;
|
||||
protected NodeAdapter mAdapter;
|
||||
|
||||
private SharedPreferences prefs;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if ( isFinishing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! App.getDB().Loaded() ) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
|
||||
ActivityCompat.invalidateOptionsMenu(this);
|
||||
|
||||
setContentView(R.layout.list_nodes);
|
||||
|
||||
mCurrentGroup = initCurrentGroup();
|
||||
|
||||
mAdapter = new NodeAdapter(this);
|
||||
addOptionsToAdapter(mAdapter);
|
||||
}
|
||||
|
||||
protected abstract PwGroup initCurrentGroup();
|
||||
|
||||
protected abstract RecyclerView defineNodeList();
|
||||
|
||||
protected void addOptionsToAdapter(NodeAdapter nodeAdapter) {
|
||||
mAdapter.setOnNodeClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Add elements to the list
|
||||
mAdapter.rebuildList(mCurrentGroup);
|
||||
assignListToNodeAdapter(defineNodeList());
|
||||
}
|
||||
|
||||
protected void setGroupTitle() {
|
||||
if ( mCurrentGroup != null ) {
|
||||
String name = mCurrentGroup.getName();
|
||||
TextView tv = (TextView) findViewById(R.id.group_name);
|
||||
if ( name != null && name.length() > 0 ) {
|
||||
if ( tv != null ) {
|
||||
tv.setText(name);
|
||||
}
|
||||
} else {
|
||||
if ( tv != null ) {
|
||||
tv.setText(getText(R.string.root));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void assignListToNodeAdapter(RecyclerView recyclerView) {
|
||||
recyclerView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
|
||||
// TODO mList.setTextFilterEnabled(true);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNodeClick(PwNode node) {
|
||||
mAdapter.registerANodeToUpdate(node);
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
GroupActivity.launch(this, (PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntryActivity.launch(this, (PwEntry) node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.donationMenuInflater(inflater, menu);
|
||||
inflater.inflate(R.menu.tree, menu);
|
||||
inflater.inflate(R.menu.default_menu, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
|
||||
// Toggle setting
|
||||
Editor editor = prefs.edit();
|
||||
editor.putString(getString(R.string.sort_node_key), sortNodeEnum.name());
|
||||
editor.putBoolean(getString(R.string.sort_ascending_key), ascending);
|
||||
editor.putBoolean(getString(R.string.sort_group_before_key), groupsBefore);
|
||||
editor.putBoolean(getString(R.string.sort_recycle_bin_bottom_key), recycleBinBottom);
|
||||
EditorCompat.apply(editor);
|
||||
|
||||
// Tell the adapter to refresh it's list
|
||||
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
|
||||
mAdapter.rebuildList(mCurrentGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
|
||||
case R.id.menu_sort:
|
||||
SortDialogFragment sortDialogFragment;
|
||||
|
||||
PwDatabase database = App.getDB().pm;
|
||||
/*
|
||||
// TODO Recycle bin bottom
|
||||
if (database.isRecycleBinAvailable() && database.isRecycleBinEnable()) {
|
||||
sortDialogFragment =
|
||||
SortDialogFragment.getInstance(
|
||||
PrefsUtil.getListSort(this),
|
||||
PrefsUtil.getAscendingSort(this),
|
||||
PrefsUtil.getGroupsBeforeSort(this),
|
||||
PrefsUtil.getRecycleBinBottomSort(this));
|
||||
} else {
|
||||
*/
|
||||
sortDialogFragment =
|
||||
SortDialogFragment.getInstance(
|
||||
PreferencesUtil.getListSort(this),
|
||||
PreferencesUtil.getAscendingSort(this),
|
||||
PreferencesUtil.getGroupsBeforeSort(this));
|
||||
//}
|
||||
|
||||
sortDialogFragment.show(getSupportFragmentManager(), "sortDialog");
|
||||
return true;
|
||||
|
||||
default:
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogPositiveClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
AssignPasswordHelper assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPassword, keyFile);
|
||||
assignPasswordHelper.assignPasswordInDatabase(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogNegativeClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
switch (requestCode) {
|
||||
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
|
||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE ||
|
||||
resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||
PwNode newNode = (PwNode) data.getSerializableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY);
|
||||
if (newNode != null) {
|
||||
if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE)
|
||||
mAdapter.addNode(newNode);
|
||||
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
|
||||
//mAdapter.updateLastNodeRegister(newNode);
|
||||
mAdapter.rebuildList(mCurrentGroup);
|
||||
}
|
||||
} else {
|
||||
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||
/*
|
||||
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
|
||||
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
|
||||
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
|
||||
*/
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
int flags = intent.getFlags();
|
||||
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
intent.setFlags(flags);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
super.startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
}
|
||||
|
||||
class AfterAddNode extends AfterAddNodeOnFinish {
|
||||
AfterAddNode(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
public void run(PwNode pwNode) {
|
||||
super.run();
|
||||
if (mSuccess) {
|
||||
mAdapter.addNode(pwNode);
|
||||
} else {
|
||||
displayMessage(ListNodesActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AfterDeleteNode extends OnFinish {
|
||||
private PwNode pwNode;
|
||||
|
||||
AfterDeleteNode(Handler handler, PwNode pwNode) {
|
||||
super(handler);
|
||||
this.pwNode = pwNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ( mSuccess) {
|
||||
mAdapter.removeNode(pwNode);
|
||||
PwGroup parent = pwNode.getParent();
|
||||
PwDatabase database = App.getDB().pm;
|
||||
if (database.isRecycleBinAvailable() && database.isRecycleBinEnable()) {
|
||||
PwGroup recycleBin = database.getRecycleBin();
|
||||
// Add trash if it doesn't exists
|
||||
if (parent.equals(recycleBin)
|
||||
&& mCurrentGroup.getParent() == null
|
||||
&& !mCurrentGroup.equals(recycleBin)) {
|
||||
mAdapter.addNode(parent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mHandler.post(new UIToastTask(ListNodesActivity.this, "Unrecoverable error: " + mMessage));
|
||||
App.setShutdown();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
@@ -28,10 +28,9 @@ import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.settings.PrefsUtil;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
import com.keepassdroid.timeout.TimeoutHelper;
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
|
||||
|
||||
public abstract class LockingActivity extends StylishActivity {
|
||||
@@ -41,7 +40,7 @@ public abstract class LockingActivity extends StylishActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (PrefsUtil.isLockDatabaseWhenScreenShutOffEnable(this)) {
|
||||
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(this)) {
|
||||
screenReceiver = new ScreenReceiver();
|
||||
registerReceiver(screenReceiver, new IntentFilter((Intent.ACTION_SCREEN_OFF)));
|
||||
} else
|
||||
@@ -52,17 +51,10 @@ public abstract class LockingActivity extends StylishActivity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
checkShutdown();
|
||||
TimeoutHelper.checkShutdown(this);
|
||||
TimeoutHelper.resume(this);
|
||||
}
|
||||
|
||||
private void checkShutdown() {
|
||||
if ( App.isShutdown() && App.getDB().Loaded() ) {
|
||||
setResult(KeePass.EXIT_LOCK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
@@ -84,9 +76,9 @@ public abstract class LockingActivity extends StylishActivity {
|
||||
|
||||
if(intent.getAction() != null) {
|
||||
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
|
||||
if (PrefsUtil.isLockDatabaseWhenScreenShutOffEnable(LockingActivity.this)) {
|
||||
if (PreferencesUtil.isLockDatabaseWhenScreenShutOffEnable(LockingActivity.this)) {
|
||||
App.setShutdown();
|
||||
checkShutdown();
|
||||
TimeoutHelper.checkShutdown(LockingActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,31 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.activities;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
public abstract class LockCloseActivity extends LockingActivity {
|
||||
import com.keepassdroid.compat.BuildCompat;
|
||||
|
||||
/**
|
||||
* Locking Hide Activity that sets FLAG_SECURE to prevent screenshots, and from
|
||||
* appearing in the recent app preview
|
||||
*/
|
||||
public abstract class LockingHideActivity extends LockingActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Several gingerbread devices have problems with FLAG_SECURE
|
||||
int ver = BuildCompat.getSdkVersion();
|
||||
if (ver >= BuildCompat.VERSION_CODE_ICE_CREAM_SANDWICH || ver < BuildCompat.VERSION_CODE_GINGERBREAD) {
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc) Workaround for HTC Linkify issues
|
||||
* @see android.app.Activity#startActivity(android.content.Intent)
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.keepassdroid.adapters;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
abstract class BasicViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
View container;
|
||||
ImageView icon;
|
||||
TextView text;
|
||||
|
||||
BasicViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.keepassdroid.adapters;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
class EntryViewHolder extends BasicViewHolder {
|
||||
|
||||
EntryViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
container = itemView.findViewById(R.id.entry_container);
|
||||
icon = (ImageView) itemView.findViewById(R.id.entry_icon);
|
||||
text = (TextView) itemView.findViewById(R.id.entry_text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.keepassdroid.adapters;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
class GroupViewHolder extends BasicViewHolder {
|
||||
|
||||
GroupViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
container = itemView.findViewById(R.id.group_container);
|
||||
icon = (ImageView) itemView.findViewById(R.id.group_icon);
|
||||
text = (TextView) itemView.findViewById(R.id.group_text);
|
||||
}
|
||||
}
|
||||
293
app/src/main/java/com/keepassdroid/adapters/NodeAdapter.java
Normal file
293
app/src/main/java/com/keepassdroid/adapters/NodeAdapter.java
Normal file
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.util.SortedListAdapterCallback;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwNode;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.database.SortNodeEnum;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
|
||||
|
||||
private SortedList<PwNode> nodeSortedList;
|
||||
|
||||
private Context context;
|
||||
private LayoutInflater inflater;
|
||||
private float textSize;
|
||||
private SortNodeEnum listSort;
|
||||
private boolean groupsBeforeSort;
|
||||
private boolean ascendingSort;
|
||||
|
||||
private OnNodeClickCallback onNodeClickCallback;
|
||||
private int nodePositionToUpdate;
|
||||
private NodeMenuListener nodeMenuListener;
|
||||
private boolean activateContextMenu;
|
||||
|
||||
/**
|
||||
* Create node list adapter with contextMenu or not
|
||||
* @param context Context to use
|
||||
*/
|
||||
public NodeAdapter(final Context context) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.context = context;
|
||||
this.textSize = PreferencesUtil.getListTextSize(context);
|
||||
this.listSort = PreferencesUtil.getListSort(context);
|
||||
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
|
||||
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
|
||||
this.activateContextMenu = false;
|
||||
this.nodePositionToUpdate = -1;
|
||||
|
||||
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
|
||||
@Override public int compare(PwNode item1, PwNode item2) {
|
||||
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort).compare(item1, item2);
|
||||
}
|
||||
|
||||
@Override public boolean areContentsTheSame(PwNode oldItem, PwNode newItem) {
|
||||
return oldItem.isContentVisuallyTheSame(newItem);
|
||||
}
|
||||
|
||||
@Override public boolean areItemsTheSame(PwNode item1, PwNode item2) {
|
||||
return item1.equals(item2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setActivateContextMenu(boolean activate) {
|
||||
this.activateContextMenu = activate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild the list by clear and build children from the group
|
||||
*/
|
||||
public void rebuildList(PwGroup group) {
|
||||
this.nodeSortedList.clear();
|
||||
this.nodeSortedList.addAll(group.getDirectChildren());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node to the list
|
||||
* @param node Node to add
|
||||
*/
|
||||
public void addNode(PwNode node) {
|
||||
nodeSortedList.add(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a node to update before an action
|
||||
* Call updateLastNodeRegister() after the action to update the node
|
||||
* @param node Node to register
|
||||
*/
|
||||
public void registerANodeToUpdate(PwNode node) {
|
||||
nodePositionToUpdate = nodeSortedList.indexOf(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the last Node register in the list
|
||||
* Work if only registerANodeToUpdate(PwNode node) is called before
|
||||
*/
|
||||
public void updateLastNodeRegister(PwNode node) {
|
||||
// Don't really update here, sorted list knows each original ref, so we just notify a change
|
||||
try {
|
||||
if (nodePositionToUpdate != -1) {
|
||||
// Don't know why but there is a bug to remove a node after this update
|
||||
nodeSortedList.updateItemAt(nodePositionToUpdate, node);
|
||||
nodeSortedList.recalculatePositionOfItemAt(nodePositionToUpdate);
|
||||
nodePositionToUpdate = -1;
|
||||
}
|
||||
else {
|
||||
Log.e(NodeAdapter.class.getName(), "registerANodeToUpdate must be called before updateLastNodeRegister");
|
||||
}
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
Log.e(NodeAdapter.class.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove node in the list
|
||||
* @param node Node to delete
|
||||
*/
|
||||
public void removeNode(PwNode node) {
|
||||
nodeSortedList.remove(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify a change sort of the list
|
||||
*/
|
||||
public void notifyChangeSort(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore) {
|
||||
this.listSort = sortNodeEnum;
|
||||
this.ascendingSort = ascending;
|
||||
this.groupsBeforeSort = groupsBefore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return nodeSortedList.get(position).getType().ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
BasicViewHolder basicViewHolder;
|
||||
View view;
|
||||
if (viewType == PwNode.Type.GROUP.ordinal()) {
|
||||
view = inflater.inflate(R.layout.list_nodes_group, parent, false);
|
||||
basicViewHolder = new GroupViewHolder(view);
|
||||
} else {
|
||||
view = inflater.inflate(R.layout.list_nodes_entry, parent, false);
|
||||
basicViewHolder = new EntryViewHolder(view);
|
||||
}
|
||||
return basicViewHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(BasicViewHolder holder, int position) {
|
||||
PwNode subNode = nodeSortedList.get(position);
|
||||
// Assign image
|
||||
App.getDB().drawFactory.assignDrawableTo(holder.icon,
|
||||
context.getResources(), subNode.getIcon());
|
||||
// Assign text
|
||||
holder.text.setText(subNode.getDisplayTitle());
|
||||
// Assign click
|
||||
holder.container.setOnClickListener(
|
||||
new OnNodeClickListener(subNode));
|
||||
// Context menu
|
||||
if (activateContextMenu) {
|
||||
holder.container.setOnCreateContextMenuListener(
|
||||
new ContextMenuBuilder(subNode, nodeMenuListener));
|
||||
}
|
||||
// Assign text size
|
||||
holder.text.setTextSize(textSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return nodeSortedList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a listener when a node is clicked
|
||||
*/
|
||||
public void setOnNodeClickListener(OnNodeClickCallback onNodeClickCallback) {
|
||||
this.onNodeClickCallback = onNodeClickCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a listener when an element of menu is clicked
|
||||
*/
|
||||
public void setNodeMenuListener(NodeMenuListener nodeMenuListener) {
|
||||
this.nodeMenuListener = nodeMenuListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback listener to redefine to do an action when a node is click
|
||||
*/
|
||||
public interface OnNodeClickCallback {
|
||||
void onNodeClick(PwNode node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu listener to redefine to do an action in menu
|
||||
*/
|
||||
public interface NodeMenuListener {
|
||||
boolean onOpenMenuClick(PwNode node);
|
||||
boolean onEditMenuClick(PwNode node);
|
||||
boolean onDeleteMenuClick(PwNode node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class for node listener
|
||||
*/
|
||||
private class OnNodeClickListener implements View.OnClickListener {
|
||||
private PwNode node;
|
||||
|
||||
OnNodeClickListener(PwNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (onNodeClickCallback != null)
|
||||
onNodeClickCallback.onNodeClick(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class for menu listener
|
||||
*/
|
||||
private class ContextMenuBuilder implements View.OnCreateContextMenuListener {
|
||||
|
||||
private static final int MENU_OPEN = Menu.FIRST;
|
||||
private static final int MENU_EDIT = MENU_OPEN + 1;
|
||||
private static final int MENU_DELETE = MENU_EDIT + 1;
|
||||
|
||||
private PwNode node;
|
||||
private NodeMenuListener menuListener;
|
||||
|
||||
ContextMenuBuilder(PwNode node, NodeMenuListener menuListener) {
|
||||
this.menuListener = menuListener;
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
|
||||
MenuItem clearMenu = contextMenu.add(Menu.NONE, MENU_OPEN, Menu.NONE, R.string.menu_open);
|
||||
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
if (!App.getDB().readOnly && !node.equals(App.getDB().pm.getRecycleBin())) {
|
||||
// TODO make edit for group
|
||||
// clearMenu = contextMenu.add(Menu.NONE, MENU_EDIT, Menu.NONE, R.string.menu_edit);
|
||||
// clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
clearMenu = contextMenu.add(Menu.NONE, MENU_DELETE, Menu.NONE, R.string.menu_delete);
|
||||
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
}
|
||||
}
|
||||
|
||||
private MenuItem.OnMenuItemClickListener mOnMyActionClickListener = new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (menuListener == null)
|
||||
return false;
|
||||
switch ( item.getItemId() ) {
|
||||
case MENU_OPEN:
|
||||
return menuListener.onOpenMenuClick(node);
|
||||
case MENU_EDIT:
|
||||
return menuListener.onEditMenuClick(node);
|
||||
case MENU_DELETE:
|
||||
return menuListener.onDeleteMenuClick(node);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,16 @@
|
||||
*/
|
||||
package com.keepassdroid.app;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.multidex.MultiDexApplication;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.compat.PRNGFixes;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.fileselect.RecentFileHistory;
|
||||
import com.keepassdroid.stylish.Stylish;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
public class App extends Application {
|
||||
public class App extends MultiDexApplication {
|
||||
private static Database db = null;
|
||||
private static boolean shutdown = false;
|
||||
private static Calendar calendar = null;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2017 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.autofill;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
import com.keepassdroid.fileselect.FileSelectActivity;
|
||||
import com.kunzisoft.keepass.KeePass;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class AutoFillAuthActivity extends KeePass {
|
||||
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
public static IntentSender getAuthIntentSenderForResponse(Context context) {
|
||||
final Intent intent = new Intent(context, AutoFillAuthActivity.class);
|
||||
return PendingIntent.getActivity(context, 0,
|
||||
intent, PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startFileSelectActivity() {
|
||||
// Pass extra for Autofill (EXTRA_ASSIST_STRUCTURE)
|
||||
AssistStructure assistStructure = autofillHelper.retrieveAssistStructure(getIntent());
|
||||
if (assistStructure != null) {
|
||||
FileSelectActivity.launch(this, assistStructure);
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
174
app/src/main/java/com/keepassdroid/autofill/AutofillHelper.java
Normal file
174
app/src/main/java/com/keepassdroid/autofill/AutofillHelper.java
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.autofill;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.service.autofill.Dataset;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class AutofillHelper {
|
||||
|
||||
public static final int AUTOFILL_RESPONSE_REQUEST_CODE = 8165;
|
||||
|
||||
private AssistStructure assistStructure = null;
|
||||
|
||||
public AssistStructure retrieveAssistStructure(Intent intent) {
|
||||
if (intent != null && intent.getExtras() != null) {
|
||||
assistStructure = intent.getParcelableExtra(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE);
|
||||
}
|
||||
return assistStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call retrieveAssistStructure before
|
||||
*/
|
||||
public AssistStructure getAssistStructure() {
|
||||
return assistStructure;
|
||||
}
|
||||
|
||||
public static void addAssistStructureExtraInIntent(Intent intent, AssistStructure assistStructure) {
|
||||
if (assistStructure != null) {
|
||||
intent.putExtra(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE, assistStructure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define if android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE is an extra bundle key present in the Intent
|
||||
*/
|
||||
public static boolean isIntentContainsExtraAssistStructureKey(Intent intent) {
|
||||
return (intent != null
|
||||
&& intent.getExtras() != null
|
||||
&& intent.getExtras().containsKey(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE));
|
||||
}
|
||||
|
||||
private @Nullable Dataset buildDataset(Context context, PwEntry entry,
|
||||
StructureParser.Result struct) {
|
||||
String title = makeEntryTitle(entry);
|
||||
RemoteViews views = newRemoteViews(context.getPackageName(), title);
|
||||
Dataset.Builder builder = new Dataset.Builder(views);
|
||||
builder.setId(entry.getUUID().toString());
|
||||
|
||||
if (entry.getPassword() != null) {
|
||||
AutofillValue value = AutofillValue.forText(entry.getPassword());
|
||||
struct.password.forEach(id -> builder.setValue(id, value));
|
||||
}
|
||||
if (entry.getUsername() != null) {
|
||||
AutofillValue value = AutofillValue.forText(entry.getUsername());
|
||||
List<AutofillId> ids = new ArrayList<>(struct.username);
|
||||
if (entry.getUsername().contains("@") || struct.username.isEmpty())
|
||||
ids.addAll(struct.email);
|
||||
ids.forEach(id -> builder.setValue(id, value));
|
||||
}
|
||||
try {
|
||||
return builder.build();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// if not value be set
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static private String makeEntryTitle(PwEntry entry) {
|
||||
if (!entry.getTitle().isEmpty() && !entry.getUsername().isEmpty())
|
||||
return String.format("%s (%s)", entry.getTitle(), entry.getUsername());
|
||||
if (!entry.getTitle().isEmpty())
|
||||
return entry.getTitle();
|
||||
if (!entry.getUsername().isEmpty())
|
||||
return entry.getUsername();
|
||||
if (!entry.getNotes().isEmpty())
|
||||
return entry.getNotes().trim();
|
||||
return ""; // TODO No title
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to hit when right key is selected
|
||||
*/
|
||||
public void buildResponseWhenEntrySelected(Activity activity, PwEntry entry) {
|
||||
Intent mReplyIntent;
|
||||
Intent intent = activity.getIntent();
|
||||
if (isIntentContainsExtraAssistStructureKey(intent)) {
|
||||
AssistStructure structure = intent.getParcelableExtra(android.view.autofill.AutofillManager.EXTRA_ASSIST_STRUCTURE);
|
||||
StructureParser.Result result = new StructureParser(structure).parse();
|
||||
|
||||
// New Response
|
||||
FillResponse.Builder responseBuilder = new FillResponse.Builder();
|
||||
Dataset dataset = buildDataset(activity, entry, result);
|
||||
responseBuilder.addDataset(dataset);
|
||||
mReplyIntent = new Intent();
|
||||
Log.d(activity.getClass().getName(), "Successed Autofill auth.");
|
||||
mReplyIntent.putExtra(
|
||||
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
|
||||
responseBuilder.build());
|
||||
activity.setResult(Activity.RESULT_OK, mReplyIntent);
|
||||
} else {
|
||||
Log.w(activity.getClass().getName(), "Failed Autofill auth.");
|
||||
activity.setResult(Activity.RESULT_CANCELED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to loop and close each activity with return data
|
||||
*/
|
||||
public static void onActivityResultSetResult(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == AUTOFILL_RESPONSE_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
activity.setResult(resultCode, data);
|
||||
}
|
||||
if (resultCode == Activity.RESULT_CANCELED) {
|
||||
activity.setResult(Activity.RESULT_CANCELED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to loop and close each activity with return data
|
||||
*/
|
||||
public static void onActivityResultSetResultAndFinish(Activity activity, int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == AUTOFILL_RESPONSE_REQUEST_CODE) {
|
||||
onActivityResultSetResult(activity, requestCode, resultCode, data);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
private static RemoteViews newRemoteViews(String packageName, String remoteViewsText) {
|
||||
RemoteViews presentation =
|
||||
new RemoteViews(packageName, R.layout.autofill_service_list_item);
|
||||
presentation.setTextViewText(R.id.text, remoteViewsText);
|
||||
return presentation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2017 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.autofill;
|
||||
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.service.autofill.AutofillService;
|
||||
import android.service.autofill.FillCallback;
|
||||
import android.service.autofill.FillContext;
|
||||
import android.service.autofill.FillRequest;
|
||||
import android.service.autofill.FillResponse;
|
||||
import android.service.autofill.SaveCallback;
|
||||
import android.service.autofill.SaveRequest;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class KeeAutofillService extends AutofillService {
|
||||
private static final String TAG = "KeeAutofillService";
|
||||
|
||||
@Override
|
||||
public void onFillRequest(@NonNull FillRequest request, @NonNull CancellationSignal cancellationSignal,
|
||||
@NonNull FillCallback callback) {
|
||||
List<FillContext> fillContexts = request.getFillContexts();
|
||||
AssistStructure latestStructure = fillContexts.get(fillContexts.size() - 1).getStructure();
|
||||
|
||||
cancellationSignal.setOnCancelListener(() ->
|
||||
Log.e(TAG, "Cancel autofill not implemented in this sample.")
|
||||
);
|
||||
|
||||
FillResponse.Builder responseBuilder = new FillResponse.Builder();
|
||||
// Check user's settings for authenticating Responses and Datasets.
|
||||
StructureParser.Result parseResult = new StructureParser(latestStructure).parse();
|
||||
AutofillId[] autofillIds = parseResult.allAutofillIds();
|
||||
if (!Arrays.asList(autofillIds).isEmpty()) {
|
||||
// If the entire Autofill Response is authenticated, AuthActivity is used
|
||||
// to generate Response.
|
||||
IntentSender sender = AutoFillAuthActivity.getAuthIntentSenderForResponse(this);
|
||||
RemoteViews presentation = new RemoteViews(getPackageName(), R.layout.autofill_service_unlock);
|
||||
responseBuilder.setAuthentication(autofillIds, sender, presentation);
|
||||
callback.onSuccess(responseBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveRequest(@NonNull SaveRequest request, @NonNull SaveCallback callback) {
|
||||
// TODO Save autofill
|
||||
//callback.onFailure(getString(R.string.autofill_not_support_save));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnected() {
|
||||
Log.d(TAG, "onConnected");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnected() {
|
||||
Log.d(TAG, "onDisconnected");
|
||||
}
|
||||
}
|
||||
115
app/src/main/java/com/keepassdroid/autofill/StructureParser.java
Normal file
115
app/src/main/java/com/keepassdroid/autofill/StructureParser.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.autofill;
|
||||
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.autofill.AutofillId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Parse AssistStructure and guess username and password fields.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
class StructureParser {
|
||||
static private final String TAG = StructureParser.class.getName();
|
||||
|
||||
final private AssistStructure structure;
|
||||
private Result result;
|
||||
private AutofillId usernameCandidate;
|
||||
|
||||
StructureParser(AssistStructure structure) {
|
||||
this.structure = structure;
|
||||
}
|
||||
|
||||
Result parse() {
|
||||
result = new Result();
|
||||
usernameCandidate = null;
|
||||
for (int i=0; i<structure.getWindowNodeCount(); ++i) {
|
||||
AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
|
||||
result.title.add(windowNode.getTitle());
|
||||
result.webDomain.add(windowNode.getRootViewNode().getWebDomain());
|
||||
parseViewNode(windowNode.getRootViewNode());
|
||||
}
|
||||
// If not explicit username field found, add the field just before password field.
|
||||
if (result.username.isEmpty() && result.email.isEmpty()
|
||||
&& !result.password.isEmpty() && usernameCandidate != null)
|
||||
result.username.add(usernameCandidate);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void parseViewNode(AssistStructure.ViewNode node) {
|
||||
String[] hints = node.getAutofillHints();
|
||||
if (hints != null && hints.length > 0) {
|
||||
if (Arrays.stream(hints).anyMatch(View.AUTOFILL_HINT_USERNAME::equals))
|
||||
result.username.add(node.getAutofillId());
|
||||
else if (Arrays.stream(hints).anyMatch(View.AUTOFILL_HINT_EMAIL_ADDRESS::equals))
|
||||
result.email.add(node.getAutofillId());
|
||||
else if (Arrays.stream(hints).anyMatch(View.AUTOFILL_HINT_PASSWORD::equals))
|
||||
result.password.add(node.getAutofillId());
|
||||
else
|
||||
Log.d(TAG, "unsupported hints");
|
||||
} else if (node.getAutofillType() == View.AUTOFILL_TYPE_TEXT) {
|
||||
int inputType = node.getInputType();
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) > 0)
|
||||
result.email.add(node.getAutofillId());
|
||||
else if ((inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD) > 0)
|
||||
result.password.add(node.getAutofillId());
|
||||
else if (result.password.isEmpty())
|
||||
usernameCandidate = node.getAutofillId();
|
||||
}
|
||||
|
||||
for (int i=0; i<node.getChildCount(); ++i)
|
||||
parseViewNode(node.getChildAt(i));
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
static class Result {
|
||||
final List<CharSequence> title;
|
||||
final List<String> webDomain;
|
||||
final List<AutofillId> username;
|
||||
final List<AutofillId> email;
|
||||
final List<AutofillId> password;
|
||||
|
||||
private Result() {
|
||||
title = new ArrayList<>();
|
||||
webDomain = new ArrayList<>();
|
||||
username = new ArrayList<>();
|
||||
email = new ArrayList<>();
|
||||
password = new ArrayList<>();
|
||||
}
|
||||
|
||||
AutofillId[] allAutofillIds() {
|
||||
ArrayList<AutofillId> all = new ArrayList<>();
|
||||
all.addAll(username);
|
||||
all.addAll(email);
|
||||
all.addAll(password);
|
||||
return all.toArray(new AutofillId[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,6 @@ public class StorageAF {
|
||||
if (!supportsStorageFramework()) { return false; }
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
|
||||
return prefs.getBoolean(ctx.getString(R.string.saf_key), ctx.getResources().getBoolean(R.bool.saf_default));
|
||||
return prefs.getBoolean(ctx.getString(R.string.saf_key), ctx.getResources().getBoolean(R.bool.settings_saf_default));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
@@ -25,9 +25,6 @@ import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.exception.ContentFileNotFoundException;
|
||||
import com.keepassdroid.database.exception.InvalidDBException;
|
||||
import com.keepassdroid.database.exception.InvalidPasswordException;
|
||||
@@ -37,6 +34,7 @@ import com.keepassdroid.database.load.ImporterFactory;
|
||||
import com.keepassdroid.database.save.PwDbOutput;
|
||||
import com.keepassdroid.icons.DrawableFactory;
|
||||
import com.keepassdroid.search.SearchDbHelper;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
@@ -48,14 +46,11 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.SyncFailedException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author bpellin
|
||||
*/
|
||||
public class Database {
|
||||
public Set<PwGroup> dirty = new HashSet<PwGroup>();
|
||||
public PwDatabase pm;
|
||||
public Uri mUri;
|
||||
public SearchDbHelper searchHelper;
|
||||
@@ -222,7 +217,6 @@ public class Database {
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
dirty.clear();
|
||||
drawFactory.clear();
|
||||
|
||||
pm = null;
|
||||
@@ -230,16 +224,4 @@ public class Database {
|
||||
loaded = false;
|
||||
passwordEncodingError = false;
|
||||
}
|
||||
|
||||
public void markAllGroupsAsDirty() {
|
||||
for ( PwGroup group : pm.getGroups() ) {
|
||||
dirty.add(group);
|
||||
}
|
||||
|
||||
// TODO: This should probably be abstracted out
|
||||
// The root tree in v3 is not an 'official' tree
|
||||
if ( pm instanceof PwDatabaseV3 ) {
|
||||
dirty.add(pm.rootGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,8 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -36,8 +32,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.os.DropBoxManager.Entry;
|
||||
|
||||
import com.keepassdroid.crypto.finalkey.FinalKey;
|
||||
import com.keepassdroid.crypto.finalkey.FinalKeyFactory;
|
||||
import com.keepassdroid.database.exception.InvalidKeyFileException;
|
||||
@@ -252,7 +246,7 @@ public abstract class PwDatabase {
|
||||
|
||||
public abstract void setNumRounds(long rounds) throws NumberFormatException;
|
||||
|
||||
public abstract boolean appSettingsEnabled();
|
||||
public abstract boolean algorithmSettingsEnabled();
|
||||
|
||||
public abstract PwEncryptionAlgorithm getEncAlgorithm();
|
||||
|
||||
@@ -262,7 +256,7 @@ public abstract class PwDatabase {
|
||||
parent = rootGroup;
|
||||
}
|
||||
|
||||
parent.childGroups.add(newGroup);
|
||||
parent.addChildGroup(newGroup);
|
||||
newGroup.setParent(parent);
|
||||
groups.put(newGroup.getId(), newGroup);
|
||||
|
||||
@@ -271,15 +265,16 @@ public abstract class PwDatabase {
|
||||
|
||||
public void removeGroupFrom(PwGroup remove, PwGroup parent) {
|
||||
// Remove tree from parent tree
|
||||
parent.childGroups.remove(remove);
|
||||
|
||||
if (parent != null) {
|
||||
parent.removeChildGroup(remove);
|
||||
}
|
||||
groups.remove(remove.getId());
|
||||
}
|
||||
|
||||
public void addEntryTo(PwEntry newEntry, PwGroup parent) {
|
||||
// Add entry to parent
|
||||
if (parent != null) {
|
||||
parent.childEntries.add(newEntry);
|
||||
parent.addChildEntry(newEntry);
|
||||
}
|
||||
newEntry.setParent(parent);
|
||||
|
||||
@@ -289,7 +284,7 @@ public abstract class PwDatabase {
|
||||
public void removeEntryFrom(PwEntry remove, PwGroup parent) {
|
||||
// Remove entry for parent
|
||||
if (parent != null) {
|
||||
parent.childEntries.remove(remove);
|
||||
parent.removeChildEntry(remove);
|
||||
}
|
||||
entries.remove(remove.getUUID());
|
||||
}
|
||||
@@ -337,28 +332,73 @@ public abstract class PwDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if RecycleBin is available or not for this version of database
|
||||
* @return true if RecycleBin enable
|
||||
*/
|
||||
public boolean isRecycleBinAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if RecycleBin is enable or not
|
||||
* @return true if RecycleBin enable, false if is not available or not enable
|
||||
*/
|
||||
public boolean isRecycleBinEnable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define if a Group must be delete or recycle
|
||||
* @param group Group to remove
|
||||
* @return true if group can be recycle, false elsewhere
|
||||
*/
|
||||
public boolean canRecycle(PwGroup group) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define if an Entry must be delete or recycle
|
||||
* @param entry Entry to remove
|
||||
* @return true if entry can be recycle, false elsewhere
|
||||
*/
|
||||
public boolean canRecycle(PwEntry entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void recycle(PwGroup group) {
|
||||
// Assume calls to this are protected by calling inRecyleBin
|
||||
throw new RuntimeException("Call not valid for .kdb databases.");
|
||||
}
|
||||
|
||||
public void recycle(PwEntry entry) {
|
||||
// Assume calls to this are protected by calling inRecyleBin
|
||||
throw new RuntimeException("Call not valid for .kdb databases.");
|
||||
}
|
||||
|
||||
public void undoRecycle(PwGroup group, PwGroup origParent) {
|
||||
throw new RuntimeException("Call not valid for .kdb databases.");
|
||||
}
|
||||
|
||||
public void undoRecycle(PwEntry entry, PwGroup origParent) {
|
||||
throw new RuntimeException("Call not valid for .kdb databases.");
|
||||
}
|
||||
|
||||
public void deleteGroup(PwGroup group) {
|
||||
PwGroup parent = group.getParent();
|
||||
removeGroupFrom(group, parent);
|
||||
parent.touch(false, true);
|
||||
}
|
||||
|
||||
public void deleteEntry(PwEntry entry) {
|
||||
PwGroup parent = entry.getParent();
|
||||
removeEntryFrom(entry, parent);
|
||||
parent.touch(false, true);
|
||||
}
|
||||
|
||||
// TODO Delete group
|
||||
public void undoDeleteGroup(PwGroup group, PwGroup origParent) {
|
||||
addGroupTo(group, origParent);
|
||||
}
|
||||
|
||||
public void undoDeleteEntry(PwEntry entry, PwGroup origParent) {
|
||||
|
||||
@@ -51,7 +51,6 @@ import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.keepassdroid.database.exception.InvalidKeyFileException;
|
||||
|
||||
@@ -164,7 +163,7 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
|
||||
List<PwGroup> rootChildGroups = getGrpRoots();
|
||||
root.setGroups(rootChildGroups);
|
||||
root.childEntries = new ArrayList<PwEntry>();
|
||||
root.childEntries = new ArrayList<>();
|
||||
root.level = -1;
|
||||
for (int i = 0; i < rootChildGroups.size(); i++) {
|
||||
PwGroupV3 grp = (PwGroupV3) rootChildGroups.get(i);
|
||||
@@ -180,12 +179,12 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
currentGroup.childEntries = getEntries(currentGroup);
|
||||
|
||||
// set parent in child entries
|
||||
for (int i = 0; i < currentGroup.childEntries.size(); i++) {
|
||||
for (int i = 0; i < currentGroup.numbersOfChildEntries(); i++) {
|
||||
PwEntryV3 entry = (PwEntryV3) currentGroup.childEntries.get(i);
|
||||
entry.parent = currentGroup;
|
||||
}
|
||||
// recursively construct child groups
|
||||
for (int i = 0; i < currentGroup.childGroups.size(); i++) {
|
||||
for (int i = 0; i < currentGroup.numbersOfChildGroups(); i++) {
|
||||
PwGroupV3 grp = (PwGroupV3) currentGroup.childGroups.get(i);
|
||||
grp.parent = currentGroup;
|
||||
constructTree((PwGroupV3) currentGroup.childGroups.get(i));
|
||||
@@ -263,7 +262,7 @@ public class PwDatabaseV3 extends PwDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appSettingsEnabled() {
|
||||
public boolean algorithmSettingsEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean appSettingsEnabled() {
|
||||
public boolean algorithmSettingsEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -361,14 +361,22 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecycleBinAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecycleBinEnable() {
|
||||
return recycleBinEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRecycle(PwGroup group) {
|
||||
if (!recycleBinEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PwGroup recycle = getRecycleBin();
|
||||
|
||||
return (recycle == null) || (!group.isContainedIn(recycle));
|
||||
}
|
||||
|
||||
@@ -377,11 +385,25 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
if (!recycleBinEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PwGroup parent = entry.getParent();
|
||||
return (parent != null) && canRecycle(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle(PwGroup group) {
|
||||
ensureRecycleBin();
|
||||
|
||||
PwGroup parent = group.getParent();
|
||||
removeGroupFrom(group, parent);
|
||||
parent.touch(false, true);
|
||||
|
||||
PwGroup recycleBin = getRecycleBin();
|
||||
addGroupTo(group, recycleBin);
|
||||
|
||||
group.touch(false, true);
|
||||
// TODO ? group.touchLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle(PwEntry entry) {
|
||||
ensureRecycleBin();
|
||||
@@ -397,6 +419,15 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
entry.touchLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoRecycle(PwGroup group, PwGroup origParent) {
|
||||
|
||||
PwGroup recycleBin = getRecycleBin();
|
||||
removeGroupFrom(group, recycleBin);
|
||||
|
||||
addGroupTo(group, origParent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoRecycle(PwEntry entry, PwGroup origParent) {
|
||||
|
||||
@@ -409,14 +440,13 @@ public class PwDatabaseV4 extends PwDatabase {
|
||||
@Override
|
||||
public void deleteEntry(PwEntry entry) {
|
||||
super.deleteEntry(entry);
|
||||
|
||||
deletedObjects.add(new PwDeletedObject(entry.getUUID()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoDeleteEntry(PwEntry entry, PwGroup origParent) {
|
||||
super.undoDeleteEntry(entry, origParent);
|
||||
|
||||
// TODO undo delete entry
|
||||
deletedObjects.remove(entry);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
@@ -32,7 +33,7 @@ import com.keepassdroid.utils.Types;
|
||||
* @author bpellin
|
||||
*
|
||||
*/
|
||||
public class PwDate implements Cloneable {
|
||||
public class PwDate implements Cloneable, Serializable {
|
||||
|
||||
private static final int DATE_SIZE = 5;
|
||||
|
||||
|
||||
@@ -19,36 +19,22 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import com.keepassdroid.database.iterator.EntrySearchStringIterator;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.keepassdroid.database.iterator.EntrySearchStringIterator;
|
||||
import com.keepassdroid.utils.SprEngine;
|
||||
|
||||
public abstract class PwEntry implements Cloneable {
|
||||
public abstract class PwEntry extends PwNode implements Cloneable {
|
||||
|
||||
protected static final String PMS_TAN_ENTRY = "<TAN>";
|
||||
|
||||
public static class EntryNameComparator implements Comparator<PwEntry> {
|
||||
|
||||
public int compare(PwEntry object1, PwEntry object2) {
|
||||
return object1.getTitle().compareToIgnoreCase(object2.getTitle());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public PwIconStandard icon = PwIconStandard.FIRST;
|
||||
|
||||
public PwEntry() {
|
||||
|
||||
}
|
||||
|
||||
public static PwEntry getInstance(PwGroup parent) {
|
||||
return PwEntry.getInstance(parent, true, true);
|
||||
}
|
||||
|
||||
public static PwEntry getInstance(PwGroup parent, boolean initId, boolean initDates) {
|
||||
if (parent instanceof PwGroupV3) {
|
||||
return new PwEntryV3((PwGroupV3)parent);
|
||||
}
|
||||
@@ -77,6 +63,11 @@ public abstract class PwEntry implements Cloneable {
|
||||
return (PwEntry) clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.ENTRY;
|
||||
}
|
||||
|
||||
public void assign(PwEntry source) {
|
||||
icon = source.icon;
|
||||
}
|
||||
@@ -109,12 +100,10 @@ public abstract class PwEntry implements Cloneable {
|
||||
public abstract String getPassword(boolean decodeRef, PwDatabase db);
|
||||
public abstract String getUrl(boolean decodeRef, PwDatabase db);
|
||||
public abstract String getNotes(boolean decodeRef, PwDatabase db);
|
||||
public abstract Date getCreationTime();
|
||||
public abstract Date getLastModificationTime();
|
||||
public abstract Date getLastAccessTime();
|
||||
public abstract Date getExpiryTime();
|
||||
public abstract boolean expires();
|
||||
public abstract PwGroup getParent();
|
||||
|
||||
public abstract void setTitle(String title, PwDatabase db);
|
||||
public abstract void setUsername(String user, PwDatabase db);
|
||||
@@ -147,7 +136,49 @@ public abstract class PwEntry implements Cloneable {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO encapsulate extra fields
|
||||
|
||||
/**
|
||||
* To redefine if version of entry allow extra field,
|
||||
* @return true if entry allows extra field
|
||||
*/
|
||||
public boolean allowExtraFields() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve extra fields to show, key is the label, value is the value of field
|
||||
* @param pm Database
|
||||
* @return Map of label/value
|
||||
*/
|
||||
public Map<String, String> getExtraFields(PwDatabase pm) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve extra protected fields to show, key is the label, value is the value protected of field
|
||||
* @return Map of label/value
|
||||
*/
|
||||
public Map<String, ProtectedString> getExtraProtectedFields() {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an extra field to the list
|
||||
* @param label Label of field, must be unique
|
||||
* @param value Value of field
|
||||
*/
|
||||
public void addField(String label, ProtectedString value) {}
|
||||
|
||||
/**
|
||||
* Delete all extra fields
|
||||
*/
|
||||
public void removeExtraFields() {}
|
||||
|
||||
/**
|
||||
* If it's a node with only meta information like Meta-info SYSTEM Database Color
|
||||
* @return false by default, true if it's a meta stream
|
||||
*/
|
||||
public boolean isMetaStream() {
|
||||
return false;
|
||||
}
|
||||
@@ -173,10 +204,87 @@ public abstract class PwEntry implements Cloneable {
|
||||
|
||||
public void touchLocation() { }
|
||||
|
||||
public abstract void setParent(PwGroup parent);
|
||||
|
||||
public boolean isSearchingEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
PwEntry pwEntry = (PwEntry) o;
|
||||
return isSameType(pwEntry)
|
||||
&& (getUUID() != null ? getUUID().equals(pwEntry.getUUID()) : pwEntry.getUUID() == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getUUID() != null ? getUUID().hashCode() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Entry by Name
|
||||
*/
|
||||
public static class EntryNameComparator implements Comparator<PwEntry> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public EntryNameComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public EntryNameComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwEntry object1, PwEntry object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int entryTitleComp = object1.getTitle().compareToIgnoreCase(object2.getTitle());
|
||||
// If same title, can be different
|
||||
if (entryTitleComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
entryTitleComp = -entryTitleComp;
|
||||
|
||||
return entryTitleComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Entry by Creation
|
||||
*/
|
||||
public static class EntryCreationComparator implements Comparator<PwEntry> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public EntryCreationComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public EntryCreationComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwEntry object1, PwEntry object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int entryCreationComp = object1.getCreationTime().compareTo(object2.getCreationTime());
|
||||
// If same creation, can be different
|
||||
if (entryCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
entryCreationComp = -entryCreationComp;
|
||||
|
||||
return entryCreationComp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,14 +43,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
package com.keepassdroid.database;
|
||||
|
||||
// PhoneID
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
|
||||
/**
|
||||
* Structure containing information about one entry.
|
||||
@@ -191,7 +191,6 @@ public class PwEntryV3 extends PwEntry {
|
||||
tLastMod = new PwDate(now);
|
||||
tExpire = new PwDate(NEVER_EXPIRE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,7 +281,6 @@ public class PwEntryV3 extends PwEntry {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void assign(PwEntry source) {
|
||||
|
||||
|
||||
@@ -19,19 +19,21 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import com.keepassdroid.database.security.ProtectedBinary;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.utils.SprEngine;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.keepassdroid.database.security.ProtectedBinary;
|
||||
import com.keepassdroid.database.security.ProtectedString;
|
||||
import com.keepassdroid.utils.SprEngine;
|
||||
|
||||
public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
public static final String STR_TITLE = "Title";
|
||||
public static final String STR_USERNAME = "UserName";
|
||||
@@ -41,14 +43,14 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
|
||||
public PwGroupV4 parent;
|
||||
public UUID uuid = PwDatabaseV4.UUID_ZERO;
|
||||
public HashMap<String, ProtectedString> strings = new HashMap<String, ProtectedString>();
|
||||
public HashMap<String, ProtectedBinary> binaries = new HashMap<String, ProtectedBinary>();
|
||||
private HashMap<String, ProtectedString> fields = new HashMap<>();
|
||||
public HashMap<String, ProtectedBinary> binaries = new HashMap<>();
|
||||
public PwIconCustom customIcon = PwIconCustom.ZERO;
|
||||
public String foregroundColor = "";
|
||||
public String backgroupColor = "";
|
||||
public String overrideURL = "";
|
||||
public AutoType autoType = new AutoType();
|
||||
public ArrayList<PwEntryV4> history = new ArrayList<PwEntryV4>();
|
||||
public ArrayList<PwEntryV4> history = new ArrayList<>();
|
||||
|
||||
private Date parentGroupLastMod = PwDatabaseV4.DEFAULT_NOW;
|
||||
private Date creation = PwDatabaseV4.DEFAULT_NOW;
|
||||
@@ -62,7 +64,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
public String tags = "";
|
||||
public Map<String, String> customData = new HashMap<String, String>();
|
||||
|
||||
public class AutoType implements Cloneable {
|
||||
public class AutoType implements Cloneable, Serializable {
|
||||
private static final long OBF_OPT_NONE = 0;
|
||||
|
||||
public boolean enabled = true;
|
||||
@@ -123,29 +125,19 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PwEntry clone(boolean deepStrings) {
|
||||
PwEntryV4 entry = (PwEntryV4) super.clone(deepStrings);
|
||||
|
||||
if (deepStrings) {
|
||||
entry.strings = (HashMap<String, ProtectedString>) strings.clone();
|
||||
entry.fields = (HashMap<String, ProtectedString>) fields.clone();
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public PwEntryV4 cloneDeep() {
|
||||
PwEntryV4 entry = (PwEntryV4) clone(true);
|
||||
|
||||
entry.binaries = (HashMap<String, ProtectedBinary>) binaries.clone();
|
||||
entry.history = (ArrayList<PwEntryV4>) history.clone();
|
||||
entry.autoType = (AutoType) autoType.clone();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assign(PwEntry source) {
|
||||
|
||||
@@ -162,7 +154,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
private void assign(PwEntryV4 source) {
|
||||
parent = source.parent;
|
||||
uuid = source.uuid;
|
||||
strings = source.strings;
|
||||
fields = source.fields;
|
||||
binaries = source.binaries;
|
||||
customIcon = source.customIcon;
|
||||
foregroundColor = source.foregroundColor;
|
||||
@@ -313,7 +305,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
}
|
||||
|
||||
public String getString(String key) {
|
||||
ProtectedString value = strings.get(key);
|
||||
ProtectedString value = fields.get(key);
|
||||
|
||||
if ( value == null ) return new String("");
|
||||
|
||||
@@ -322,7 +314,7 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
|
||||
public void setString(String key, String value, boolean protect) {
|
||||
ProtectedString ps = new ProtectedString(protect, value);
|
||||
strings.put(key, ps);
|
||||
fields.put(key, ps);
|
||||
}
|
||||
|
||||
public Date getLocationChanged() {
|
||||
@@ -367,23 +359,27 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
} else {
|
||||
return customIcon;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static boolean IsStandardString(String key) {
|
||||
return key.equals(STR_TITLE) || key.equals(STR_USERNAME)
|
||||
|| key.equals(STR_PASSWORD) || key.equals(STR_URL)
|
||||
|| key.equals(STR_NOTES);
|
||||
}
|
||||
|
||||
public void createBackup(PwDatabaseV4 db) {
|
||||
PwEntryV4 copy = cloneDeep();
|
||||
copy.history = new ArrayList<PwEntryV4>();
|
||||
copy.history = new ArrayList<>();
|
||||
history.add(copy);
|
||||
|
||||
if (db != null) maintainBackups(db);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public PwEntryV4 cloneDeep() {
|
||||
PwEntryV4 entry = (PwEntryV4) clone(true);
|
||||
|
||||
entry.binaries = (HashMap<String, ProtectedBinary>) binaries.clone();
|
||||
entry.history = (ArrayList<PwEntryV4>) history.clone();
|
||||
entry.autoType = (AutoType) autoType.clone();
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private boolean maintainBackups(PwDatabaseV4 db) {
|
||||
boolean deleted = false;
|
||||
|
||||
@@ -433,12 +429,72 @@ public class PwEntryV4 extends PwEntry implements ITimeLogger {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowExtraFields() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Map<String, ProtectedString> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ProtectedString> getExtraProtectedFields() {
|
||||
Map<String, ProtectedString> protectedFields = super.getExtraProtectedFields();
|
||||
if (fields.size() > 0) {
|
||||
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
|
||||
String key = pair.getKey();
|
||||
if (!PwEntryV4.IsStandardField(key)) {
|
||||
protectedFields.put(key, pair.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
return protectedFields;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtraFields(PwDatabase pm) {
|
||||
Map<String, String> extraFields = super.getExtraFields(pm);
|
||||
SprEngine spr = SprEngine.getInstance(pm);
|
||||
// Display custom fields
|
||||
if (fields.size() > 0) {
|
||||
for (Map.Entry<String, ProtectedString> pair : fields.entrySet()) {
|
||||
String key = pair.getKey();
|
||||
// TODO Add hidden style for protection field
|
||||
if (!PwEntryV4.IsStandardField(key)) {
|
||||
extraFields.put(key, spr.compile(pair.getValue().toString(), this, pm));
|
||||
}
|
||||
}
|
||||
}
|
||||
return extraFields;
|
||||
}
|
||||
|
||||
public static boolean IsStandardField(String key) {
|
||||
return key.equals(STR_TITLE) || key.equals(STR_USERNAME)
|
||||
|| key.equals(STR_PASSWORD) || key.equals(STR_URL)
|
||||
|| key.equals(STR_NOTES);
|
||||
}
|
||||
|
||||
public void addField(String label, ProtectedString value) {
|
||||
fields.put(label, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExtraFields() {
|
||||
Iterator<Entry<String, ProtectedString>> iter = fields.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<String, ProtectedString> pair = iter.next();
|
||||
if (!PwEntryV4.IsStandardField(pair.getKey())) {
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final long FIXED_LENGTH_SIZE = 128; // Approximate fixed length size
|
||||
public long getSize() {
|
||||
long size = FIXED_LENGTH_SIZE;
|
||||
|
||||
for (Entry<String, ProtectedString> pair : strings.entrySet()) {
|
||||
for (Entry<String, ProtectedString> pair : fields.entrySet()) {
|
||||
size += pair.getKey().length();
|
||||
size += pair.getValue().length();
|
||||
}
|
||||
|
||||
@@ -19,26 +19,99 @@
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import com.keepassdroid.utils.StrUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import com.keepassdroid.utils.StrUtil;
|
||||
public abstract class PwGroup extends PwNode {
|
||||
|
||||
public abstract class PwGroup {
|
||||
public List<PwGroup> childGroups = new ArrayList<PwGroup>();
|
||||
public List<PwEntry> childEntries = new ArrayList<PwEntry>();
|
||||
// TODO Change dependency and make private
|
||||
public List<PwGroup> childGroups = new ArrayList<>();
|
||||
public List<PwEntry> childEntries = new ArrayList<>();
|
||||
public String name = "";
|
||||
public PwIconStandard icon;
|
||||
|
||||
public abstract PwGroup getParent();
|
||||
public abstract void setParent(PwGroup parent);
|
||||
private List<PwNode> children = new ArrayList<>();
|
||||
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
setId(newId);
|
||||
name = nm;
|
||||
}
|
||||
|
||||
public void addChildGroup(PwGroup group) {
|
||||
this.childGroups.add(group);
|
||||
}
|
||||
|
||||
public void addChildEntry(PwEntry entry) {
|
||||
this.childEntries.add(entry);
|
||||
}
|
||||
|
||||
public void removeChildGroup(PwGroup group) {
|
||||
this.childGroups.remove(group);
|
||||
}
|
||||
|
||||
public void removeChildEntry(PwEntry entry) {
|
||||
this.childEntries.remove(entry);
|
||||
}
|
||||
|
||||
public int numbersOfChildGroups() {
|
||||
return childGroups.size();
|
||||
}
|
||||
|
||||
public int numbersOfChildEntries() {
|
||||
return childEntries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.GROUP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter MetaStream entries and return children
|
||||
* @return List of direct children (one level below) as PwNode
|
||||
*/
|
||||
public List<PwNode> getDirectChildren() {
|
||||
children.clear();
|
||||
children.addAll(childGroups);
|
||||
for(PwEntry child : childEntries) {
|
||||
if (!child.isMetaStream())
|
||||
children.add(child);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of direct elements in Node (one level below)
|
||||
* @return Size of child elements, default is 0
|
||||
*/
|
||||
public int numberOfDirectChildren() {
|
||||
return childGroups.size() + childEntries.size();
|
||||
}
|
||||
|
||||
public boolean isContainedIn(PwGroup container) {
|
||||
PwGroup cur = this;
|
||||
while (cur != null) {
|
||||
if (cur == container) {
|
||||
return true;
|
||||
}
|
||||
cur = cur.getParent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract PwGroupId getId();
|
||||
public abstract void setId(PwGroupId id);
|
||||
|
||||
@Override
|
||||
public String getDisplayTitle() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public abstract Date getLastMod();
|
||||
@@ -47,47 +120,16 @@ public abstract class PwGroup {
|
||||
return icon;
|
||||
}
|
||||
|
||||
public void sortGroupsByName() {
|
||||
Collections.sort(childGroups, new GroupNameComparator());
|
||||
}
|
||||
|
||||
public static class GroupNameComparator implements Comparator<PwGroup> {
|
||||
|
||||
public int compare(PwGroup object1, PwGroup object2) {
|
||||
return object1.getName().compareToIgnoreCase(object2.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public abstract void setLastAccessTime(Date date);
|
||||
|
||||
public abstract void setLastModificationTime(Date date);
|
||||
|
||||
public void sortEntriesByName() {
|
||||
Collections.sort(childEntries, new PwEntry.EntryNameComparator());
|
||||
}
|
||||
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
setId(newId);
|
||||
name = nm;
|
||||
}
|
||||
|
||||
public boolean isContainedIn(PwGroup container) {
|
||||
PwGroup cur = this;
|
||||
while (cur != null) {
|
||||
if (cur == container) {
|
||||
return true;
|
||||
}
|
||||
|
||||
cur = cur.getParent();
|
||||
}
|
||||
|
||||
public boolean allowAddEntryIfIsRoot() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void touch(boolean modified, boolean touchParents) {
|
||||
Date now = new Date();
|
||||
|
||||
setLastAccessTime(now);
|
||||
|
||||
if (modified) {
|
||||
@@ -100,7 +142,6 @@ public abstract class PwGroup {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void searchEntries(SearchParameters sp, List<PwEntry> listStorage) {
|
||||
if (sp == null) { return; }
|
||||
if (listStorage == null) { return; }
|
||||
@@ -147,7 +188,6 @@ public abstract class PwGroup {
|
||||
complement.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
pg = complement;
|
||||
}
|
||||
else {
|
||||
@@ -181,19 +221,94 @@ public abstract class PwGroup {
|
||||
if (entryHandler != null) {
|
||||
for (PwEntry entry : childEntries) {
|
||||
if (!entryHandler.operate(entry)) return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (PwGroup group : childGroups) {
|
||||
|
||||
if ((groupHandler != null) && !groupHandler.operate(group)) return false;
|
||||
|
||||
group.preOrderTraverseTree(groupHandler, entryHandler);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
PwGroup pwGroup = (PwGroup) o;
|
||||
return isSameType(pwGroup)
|
||||
&& (getId() != null ? getId().equals(pwGroup.getId()) : pwGroup.getId() == null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
PwGroupId groupId = getId();
|
||||
return groupId != null ? groupId.hashCode() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Group comparator by name
|
||||
*/
|
||||
public static class GroupNameComparator implements Comparator<PwGroup> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public GroupNameComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public GroupNameComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwGroup object1, PwGroup object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int groupNameComp = object1.getName().compareToIgnoreCase(object2.getName());
|
||||
// If same name, can be different
|
||||
if (groupNameComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
groupNameComp = -groupNameComp;
|
||||
|
||||
return groupNameComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Group comparator by name
|
||||
*/
|
||||
public static class GroupCreationComparator implements Comparator<PwGroup> {
|
||||
|
||||
private boolean ascending;
|
||||
|
||||
public GroupCreationComparator() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public GroupCreationComparator(boolean ascending) {
|
||||
this.ascending = ascending;
|
||||
}
|
||||
|
||||
public int compare(PwGroup object1, PwGroup object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
int groupCreationComp = object1.getCreationTime().compareTo(object2.getCreationTime());
|
||||
// If same creation, can be different
|
||||
if (groupCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
// If descending
|
||||
if (!ascending)
|
||||
groupCreationComp = -groupCreationComp;
|
||||
|
||||
return groupCreationComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.keepassdroid.database;
|
||||
|
||||
public abstract class PwGroupId {
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class PwGroupId implements Serializable {
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class PwGroupIdV3 extends PwGroupId {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
Integer i = Integer.valueOf(id);
|
||||
Integer i = id;
|
||||
return i.hashCode();
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,15 @@ Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
|
||||
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.keepassdroid.utils.Types;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @author Brian Pellin <bpellin@gmail.com>
|
||||
* @author Naomaru Itoi <nao@phoneid.org>
|
||||
@@ -43,8 +46,6 @@ import java.util.List;
|
||||
* @author Dominik Reichl <dominik.reichl@t-online.de>
|
||||
*/
|
||||
public class PwGroupV3 extends PwGroup {
|
||||
public PwGroupV3() {
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return name;
|
||||
@@ -155,4 +156,11 @@ public class PwGroupV3 extends PwGroup {
|
||||
tLastMod = new PwDate(date);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getCreationTime() {
|
||||
if(tCreation != null)
|
||||
return tCreation.getJDate();
|
||||
else
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +48,7 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
private long usageCount = 0;
|
||||
public Map<String, String> customData = new HashMap<String, String>();
|
||||
|
||||
public PwGroupV4() {
|
||||
|
||||
}
|
||||
public PwGroupV4() {}
|
||||
|
||||
public PwGroupV4(boolean createUUID, boolean setTimes, String name, PwIconStandard icon) {
|
||||
if (createUUID) {
|
||||
@@ -65,6 +63,13 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
super.initNewGroup(nm, newId);
|
||||
|
||||
lastAccess = lastMod = creation = parentGroupLastMod = new Date();
|
||||
}
|
||||
|
||||
public void AddGroup(PwGroupV4 subGroup, boolean takeOwnership) {
|
||||
AddGroup(subGroup, takeOwnership, false);
|
||||
}
|
||||
@@ -87,7 +92,7 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
public void AddEntry(PwEntryV4 pe, boolean takeOwnership, boolean updateLocationChanged) {
|
||||
assert(pe != null);
|
||||
|
||||
childEntries.add(pe);
|
||||
addChildEntry(pe);
|
||||
|
||||
if ( takeOwnership ) pe.parent = this;
|
||||
|
||||
@@ -102,7 +107,7 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
public void buildChildGroupsRecursive(List<PwGroup> list) {
|
||||
list.add(this);
|
||||
|
||||
for ( int i = 0; i < childGroups.size(); i++) {
|
||||
for ( int i = 0; i < numbersOfChildGroups(); i++) {
|
||||
PwGroupV4 child = (PwGroupV4) childGroups.get(i);
|
||||
child.buildChildGroupsRecursive(list);
|
||||
|
||||
@@ -110,11 +115,11 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
}
|
||||
|
||||
public void buildChildEntriesRecursive(List<PwEntry> list) {
|
||||
for ( int i = 0; i < childEntries.size(); i++ ) {
|
||||
for ( int i = 0; i < numbersOfChildEntries(); i++ ) {
|
||||
list.add(childEntries.get(i));
|
||||
}
|
||||
|
||||
for ( int i = 0; i < childGroups.size(); i++ ) {
|
||||
for ( int i = 0; i < numbersOfChildGroups(); i++ ) {
|
||||
PwGroupV4 child = (PwGroupV4) childGroups.get(i);
|
||||
child.buildChildEntriesRecursive(list);
|
||||
}
|
||||
@@ -168,7 +173,6 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
|
||||
public void setCreationTime(Date date) {
|
||||
creation = date;
|
||||
|
||||
}
|
||||
|
||||
public void setExpiryTime(Date date) {
|
||||
@@ -204,7 +208,11 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
@Override
|
||||
public void setParent(PwGroup prt) {
|
||||
parent = (PwGroupV4) prt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowAddEntryIfIsRoot() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -216,13 +224,6 @@ public class PwGroupV4 extends PwGroup implements ITimeLogger {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initNewGroup(String nm, PwGroupId newId) {
|
||||
super.initNewGroup(nm, newId);
|
||||
|
||||
lastAccess = lastMod = creation = parentGroupLastMod = new Date();
|
||||
}
|
||||
|
||||
public boolean isSearchEnabled() {
|
||||
PwGroupV4 group = this;
|
||||
while (group != null) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.keepassdroid.database;
|
||||
|
||||
public abstract class PwIcon {
|
||||
import java.io.Serializable;
|
||||
|
||||
public abstract class PwIcon implements Serializable {
|
||||
|
||||
public boolean isMetaStreamIcon() {
|
||||
return false;
|
||||
|
||||
88
app/src/main/java/com/keepassdroid/database/PwNode.java
Normal file
88
app/src/main/java/com/keepassdroid/database/PwNode.java
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Abstract class who manage Groups and Entries
|
||||
*/
|
||||
public abstract class PwNode implements Serializable {
|
||||
|
||||
/**
|
||||
* Type of available Nodes
|
||||
*/
|
||||
public enum Type {
|
||||
GROUP, ENTRY
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Type of Node
|
||||
*/
|
||||
public abstract Type getType();
|
||||
|
||||
/**
|
||||
* @return Title to display as view
|
||||
*/
|
||||
public abstract String getDisplayTitle();
|
||||
|
||||
/**
|
||||
* @return Visual icon
|
||||
*/
|
||||
public abstract PwIcon getIcon();
|
||||
|
||||
/**
|
||||
* @return Creation date and time of the node
|
||||
*/
|
||||
public abstract Date getCreationTime();
|
||||
|
||||
/**
|
||||
* Retrieve the parent node
|
||||
* @return PwGroup parent as group
|
||||
*/
|
||||
public abstract PwGroup getParent();
|
||||
|
||||
/**
|
||||
* Assign a parent to this node
|
||||
*/
|
||||
public abstract void setParent(PwGroup parent);
|
||||
|
||||
/**
|
||||
* If the content (type, title, icon) is visually the same
|
||||
* @param o Node to compare
|
||||
* @return True if visually as o
|
||||
*/
|
||||
public boolean isContentVisuallyTheSame(PwNode o) {
|
||||
return getType().equals(o.getType())
|
||||
&& getDisplayTitle().equals(o.getDisplayTitle())
|
||||
&& getIcon().equals(o.getIcon());
|
||||
}
|
||||
|
||||
/**
|
||||
* Define if it's the same type of another node
|
||||
* @param otherNode The other node to test
|
||||
* @return true if both have the same type
|
||||
*/
|
||||
boolean isSameType(PwNode otherNode) {
|
||||
return getType() != null ? getType().equals(otherNode.getType()) : otherNode.getType() == null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.keepassdroid.database;
|
||||
|
||||
public enum PwVersion {
|
||||
V3, V4
|
||||
}
|
||||
176
app/src/main/java/com/keepassdroid/database/SortNodeEnum.java
Normal file
176
app/src/main/java/com/keepassdroid/database/SortNodeEnum.java
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package com.keepassdroid.database;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public enum SortNodeEnum {
|
||||
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
|
||||
|
||||
public Comparator<PwNode> getNodeComparator(boolean ascending, boolean groupsBefore) {
|
||||
switch (this) {
|
||||
case DB:
|
||||
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
|
||||
default:
|
||||
case TITLE:
|
||||
return new NodeTitleComparator(ascending, groupsBefore);
|
||||
case USERNAME:
|
||||
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
|
||||
case CREATION_TIME:
|
||||
return new NodeCreationComparator(ascending, groupsBefore);
|
||||
case LAST_MODIFY_TIME:
|
||||
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
|
||||
case LAST_ACCESS_TIME:
|
||||
return new NodeCreationComparator(ascending, groupsBefore); // TODO Sort
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class NodeComparator implements Comparator<PwNode> {
|
||||
boolean ascending;
|
||||
boolean groupsBefore;
|
||||
|
||||
NodeComparator() {
|
||||
this(true, true);
|
||||
}
|
||||
|
||||
NodeComparator(boolean groupsBefore) {
|
||||
this(true, groupsBefore);
|
||||
}
|
||||
|
||||
NodeComparator(boolean ascending, boolean groupsBefore) {
|
||||
this.ascending = ascending;
|
||||
this.groupsBefore = groupsBefore;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of Node by Title, Groups first, Entries second
|
||||
*/
|
||||
public static class NodeTitleComparator extends NodeComparator {
|
||||
|
||||
public NodeTitleComparator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NodeTitleComparator(boolean groupsBefore) {
|
||||
super(groupsBefore);
|
||||
}
|
||||
|
||||
public NodeTitleComparator(boolean ascending, boolean groupsBefore) {
|
||||
super(ascending, groupsBefore);
|
||||
}
|
||||
|
||||
public int compare(PwNode object1, PwNode object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
if (object1 instanceof PwGroup) {
|
||||
if (object2 instanceof PwGroup) {
|
||||
return new PwGroup.GroupNameComparator(ascending)
|
||||
.compare((PwGroup) object1, (PwGroup) object2);
|
||||
} else if (object2 instanceof PwEntry) {
|
||||
if(groupsBefore)
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (object1 instanceof PwEntry) {
|
||||
if(object2 instanceof PwEntry) {
|
||||
return new PwEntry.EntryNameComparator(ascending)
|
||||
.compare((PwEntry) object1, (PwEntry) object2);
|
||||
} else if (object2 instanceof PwGroup) {
|
||||
if(groupsBefore)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
int nodeNameComp = object1.getDisplayTitle()
|
||||
.compareToIgnoreCase(object2.getDisplayTitle());
|
||||
// If same name, can be different
|
||||
if (nodeNameComp == 0)
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
return nodeNameComp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator of node by creation, Groups first, Entries second
|
||||
*/
|
||||
public static class NodeCreationComparator extends NodeComparator {
|
||||
|
||||
public NodeCreationComparator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NodeCreationComparator(boolean groupsBefore) {
|
||||
super(groupsBefore);
|
||||
}
|
||||
|
||||
|
||||
public NodeCreationComparator(boolean ascending, boolean groupsBefore) {
|
||||
super(ascending, groupsBefore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(PwNode object1, PwNode object2) {
|
||||
if (object1.equals(object2))
|
||||
return 0;
|
||||
|
||||
if (object1 instanceof PwGroup) {
|
||||
if (object2 instanceof PwGroup) {
|
||||
return new PwGroup.GroupCreationComparator(ascending)
|
||||
.compare((PwGroup) object1, (PwGroup) object2);
|
||||
} else if (object2 instanceof PwEntry) {
|
||||
if(groupsBefore)
|
||||
return -1;
|
||||
else
|
||||
return 1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (object1 instanceof PwEntry) {
|
||||
if(object2 instanceof PwEntry) {
|
||||
return new PwEntry.EntryCreationComparator(ascending)
|
||||
.compare((PwEntry) object1, (PwEntry) object2);
|
||||
} else if (object2 instanceof PwGroup) {
|
||||
if(groupsBefore)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
int nodeCreationComp = object1.getCreationTime()
|
||||
.compareTo(object2.getCreationTime());
|
||||
// If same creation, can be different
|
||||
if (nodeCreationComp == 0) {
|
||||
return object1.hashCode() - object2.hashCode();
|
||||
}
|
||||
return nodeCreationComp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,21 +21,16 @@ package com.keepassdroid.database.edit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
|
||||
public class AddEntry extends RunnableOnFinish {
|
||||
protected Database mDb;
|
||||
private PwEntry mEntry;
|
||||
private Context ctx;
|
||||
|
||||
public static AddEntry getInstance(Context ctx, Database db, PwEntry entry, OnFinish finish) {
|
||||
return new AddEntry(ctx, db, entry, finish);
|
||||
}
|
||||
|
||||
protected AddEntry(Context ctx, Database db, PwEntry entry, OnFinish finish) {
|
||||
public AddEntry(Context ctx, Database db, PwEntry entry, OnFinish finish) {
|
||||
super(finish);
|
||||
|
||||
mDb = db;
|
||||
@@ -56,27 +51,18 @@ public class AddEntry extends RunnableOnFinish {
|
||||
|
||||
private class AfterAdd extends OnFinish {
|
||||
|
||||
public AfterAdd(OnFinish finish) {
|
||||
AfterAdd(OnFinish finish) {
|
||||
super(finish);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PwDatabase pm = mDb.pm;
|
||||
if ( mSuccess ) {
|
||||
|
||||
PwGroup parent = mEntry.getParent();
|
||||
|
||||
// Mark parent tree dirty
|
||||
mDb.dirty.add(parent);
|
||||
|
||||
} else {
|
||||
if ( !mSuccess ) {
|
||||
pm.removeEntryFrom(mEntry, mEntry.getParent());
|
||||
}
|
||||
|
||||
// TODO if add entry callback
|
||||
super.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ package com.keepassdroid.database.edit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
|
||||
@@ -32,16 +32,12 @@ public class AddGroup extends RunnableOnFinish {
|
||||
private PwGroup mGroup;
|
||||
private PwGroup mParent;
|
||||
private Context ctx;
|
||||
protected boolean mDontSave;
|
||||
private boolean mDontSave;
|
||||
|
||||
|
||||
public static AddGroup getInstance(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, boolean dontSave) {
|
||||
return new AddGroup(ctx, db, name, iconid, parent, finish, dontSave);
|
||||
}
|
||||
|
||||
|
||||
private AddGroup(Context ctx, Database db, String name, int iconid, PwGroup parent, OnFinish finish, boolean dontSave) {
|
||||
super(finish);
|
||||
public AddGroup(Context ctx, Database db, String name, int iconid,
|
||||
PwGroup parent, AfterAddNodeOnFinish afterAddNode,
|
||||
boolean dontSave) {
|
||||
super(afterAddNode);
|
||||
|
||||
mDb = db;
|
||||
mName = name;
|
||||
@@ -63,8 +59,6 @@ public class AddGroup extends RunnableOnFinish {
|
||||
mGroup.icon = mDb.pm.iconFactory.getIcon(mIconID);
|
||||
pm.addGroupTo(mGroup, mParent);
|
||||
|
||||
//mParent.sortGroupsByName();
|
||||
|
||||
// Commit to disk
|
||||
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
|
||||
save.run();
|
||||
@@ -72,24 +66,23 @@ public class AddGroup extends RunnableOnFinish {
|
||||
|
||||
private class AfterAdd extends OnFinish {
|
||||
|
||||
public AfterAdd(OnFinish finish) {
|
||||
AfterAdd(OnFinish finish) {
|
||||
super(finish);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PwDatabase pm = mDb.pm;
|
||||
if ( mSuccess ) {
|
||||
// Mark parent group dirty
|
||||
mDb.dirty.add(mParent);
|
||||
} else {
|
||||
if ( !mSuccess ) {
|
||||
pm.removeGroupFrom(mGroup, mParent);
|
||||
}
|
||||
|
||||
super.run();
|
||||
// TODO Better callback
|
||||
AfterAddNodeOnFinish afterAddNode =
|
||||
(AfterAddNodeOnFinish) super.mOnFinish;
|
||||
afterAddNode.mSuccess = mSuccess;
|
||||
afterAddNode.mMessage = mMessage;
|
||||
afterAddNode.run(mGroup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.keepassdroid.database.edit;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import com.keepassdroid.database.PwNode;
|
||||
|
||||
public abstract class AfterAddNodeOnFinish extends OnFinish {
|
||||
public AfterAddNodeOnFinish(Handler handler) {
|
||||
super(handler);
|
||||
}
|
||||
|
||||
public abstract void run(PwNode pwNode);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ package com.keepassdroid.database.edit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
|
||||
@@ -21,7 +21,7 @@ package com.keepassdroid.database.edit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
@@ -71,8 +71,6 @@ public class DeleteEntry extends RunnableOnFinish {
|
||||
// Commit database
|
||||
SaveDB save = new SaveDB(ctx, mDb, mFinish, mDontSave);
|
||||
save.run();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private class AfterDelete extends OnFinish {
|
||||
@@ -81,7 +79,7 @@ public class DeleteEntry extends RunnableOnFinish {
|
||||
private PwEntry mEntry;
|
||||
private boolean recycled;
|
||||
|
||||
public AfterDelete(OnFinish finish, PwGroup parent, PwEntry entry, boolean r) {
|
||||
AfterDelete(OnFinish finish, PwGroup parent, PwEntry entry, boolean r) {
|
||||
super(finish);
|
||||
|
||||
mParent = parent;
|
||||
@@ -92,18 +90,7 @@ public class DeleteEntry extends RunnableOnFinish {
|
||||
@Override
|
||||
public void run() {
|
||||
PwDatabase pm = mDb.pm;
|
||||
if ( mSuccess ) {
|
||||
// Mark parent dirty
|
||||
if ( mParent != null ) {
|
||||
mDb.dirty.add(mParent);
|
||||
}
|
||||
|
||||
if (recycled) {
|
||||
PwGroup recycleBin = pm.getRecycleBin();
|
||||
mDb.dirty.add(recycleBin);
|
||||
mDb.dirty.add(mDb.pm.rootGroup);
|
||||
}
|
||||
} else {
|
||||
if ( !mSuccess ) {
|
||||
if (recycled) {
|
||||
pm.undoRecycle(mEntry, mParent);
|
||||
}
|
||||
@@ -111,11 +98,9 @@ public class DeleteEntry extends RunnableOnFinish {
|
||||
pm.undoDeleteEntry(mEntry, mParent);
|
||||
}
|
||||
}
|
||||
// TODO Callback after delete entry
|
||||
|
||||
super.run();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,109 +19,115 @@
|
||||
*/
|
||||
package com.keepassdroid.database.edit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import android.content.Context;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.GroupBaseActivity;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DeleteGroup extends RunnableOnFinish {
|
||||
|
||||
private Context mContext;
|
||||
private Database mDb;
|
||||
private PwGroup mGroup;
|
||||
private GroupBaseActivity mAct;
|
||||
private boolean mDontSave;
|
||||
|
||||
public DeleteGroup(Database db, PwGroup group, GroupBaseActivity act, OnFinish finish) {
|
||||
public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish) {
|
||||
super(finish);
|
||||
setMembers(db, group, act, false);
|
||||
setMembers(ctx, db, group, false);
|
||||
}
|
||||
|
||||
public DeleteGroup(Database db, PwGroup group, GroupBaseActivity act, OnFinish finish, boolean dontSave) {
|
||||
public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish, boolean dontSave) {
|
||||
super(finish);
|
||||
setMembers(db, group, act, dontSave);
|
||||
setMembers(ctx, db, group, dontSave);
|
||||
}
|
||||
|
||||
|
||||
public DeleteGroup(Database db, PwGroup group, OnFinish finish, boolean dontSave) {
|
||||
super(finish);
|
||||
setMembers(db, group, null, dontSave);
|
||||
setMembers(null, db, group, dontSave);
|
||||
}
|
||||
|
||||
private void setMembers(Database db, PwGroup group, GroupBaseActivity act, boolean dontSave) {
|
||||
private void setMembers(Context ctx, Database db, PwGroup group, boolean dontSave) {
|
||||
mDb = db;
|
||||
mGroup = group;
|
||||
mAct = act;
|
||||
mContext = ctx;
|
||||
mDontSave = dontSave;
|
||||
|
||||
mFinish = new AfterDelete(mFinish);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PwDatabase pm = mDb.pm;
|
||||
PwGroup parent = mGroup.getParent();
|
||||
|
||||
// Remove Group from parent
|
||||
boolean recycle = pm.canRecycle(mGroup);
|
||||
if (recycle) {
|
||||
pm.recycle(mGroup);
|
||||
}
|
||||
else {
|
||||
// TODO tests
|
||||
// Remove child entries
|
||||
List<PwEntry> childEnt = new ArrayList<PwEntry>(mGroup.childEntries);
|
||||
List<PwEntry> childEnt = new ArrayList<>(mGroup.childEntries);
|
||||
for ( int i = 0; i < childEnt.size(); i++ ) {
|
||||
DeleteEntry task = new DeleteEntry(mAct, mDb, childEnt.get(i), null, true);
|
||||
DeleteEntry task = new DeleteEntry(mContext, mDb, childEnt.get(i), null, true);
|
||||
task.run();
|
||||
}
|
||||
|
||||
// Remove child groups
|
||||
List<PwGroup> childGrp = new ArrayList<PwGroup>(mGroup.childGroups);
|
||||
List<PwGroup> childGrp = new ArrayList<>(mGroup.childGroups);
|
||||
for ( int i = 0; i < childGrp.size(); i++ ) {
|
||||
DeleteGroup task = new DeleteGroup(mDb, childGrp.get(i), mAct, null, true);
|
||||
DeleteGroup task = new DeleteGroup(mContext, mDb, childGrp.get(i), null, true);
|
||||
task.run();
|
||||
}
|
||||
|
||||
|
||||
// Remove from parent
|
||||
PwGroup parent = mGroup.getParent();
|
||||
if ( parent != null ) {
|
||||
parent.childGroups.remove(mGroup);
|
||||
}
|
||||
pm.deleteGroup(mGroup);
|
||||
|
||||
// Remove from PwDatabaseV3
|
||||
// TODO ENcapsulate
|
||||
mDb.pm.getGroups().remove(mGroup);
|
||||
}
|
||||
|
||||
// Save
|
||||
SaveDB save = new SaveDB(mAct, mDb, mFinish, mDontSave);
|
||||
save.run();
|
||||
mFinish = new AfterDelete(mFinish, parent, mGroup, recycle);
|
||||
|
||||
// Commit Database
|
||||
SaveDB save = new SaveDB(mContext, mDb, mFinish, mDontSave);
|
||||
save.run();
|
||||
}
|
||||
|
||||
private class AfterDelete extends OnFinish {
|
||||
public AfterDelete(OnFinish finish) {
|
||||
|
||||
private PwGroup mParent;
|
||||
private PwGroup mGroup;
|
||||
private boolean recycled;
|
||||
|
||||
AfterDelete(OnFinish finish, PwGroup parent, PwGroup mGroup, boolean recycle) {
|
||||
super(finish);
|
||||
|
||||
this.mParent = parent;
|
||||
this.mGroup = mGroup;
|
||||
this.recycled = recycle;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if ( mSuccess ) {
|
||||
// Remove from tree global
|
||||
mDb.pm.groups.remove(mGroup.getId());
|
||||
|
||||
// Remove tree from the dirty global (if it is present), not a big deal if this fails
|
||||
mDb.dirty.remove(mGroup);
|
||||
|
||||
// Mark parent dirty
|
||||
PwGroup parent = mGroup.getParent();
|
||||
if ( parent != null ) {
|
||||
mDb.dirty.add(parent);
|
||||
PwDatabase pm = mDb.pm;
|
||||
if ( !mSuccess ) {
|
||||
if (recycled) {
|
||||
pm.undoRecycle(mGroup, mParent);
|
||||
}
|
||||
mDb.dirty.add(mDb.pm.rootGroup);
|
||||
} else {
|
||||
else {
|
||||
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
|
||||
App.setShutdown();
|
||||
// TODO TEST pm.undoDeleteGroup(mGroup, mParent);
|
||||
}
|
||||
}
|
||||
// TODO Callback after delete group
|
||||
|
||||
super.run();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.exception.ArcFourException;
|
||||
import com.keepassdroid.database.exception.ContentFileNotFoundException;
|
||||
@@ -78,7 +78,7 @@ public class LoadDB extends RunnableOnFinish {
|
||||
finish(false, mCtx.getString(R.string.file_not_found_content));
|
||||
return;
|
||||
} catch (FileNotFoundException e) {
|
||||
finish(false, mCtx.getString(R.string.FileNotFound));
|
||||
finish(false, mCtx.getString(R.string.file_not_found));
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
finish(false, e.getMessage());
|
||||
@@ -104,6 +104,9 @@ public class LoadDB extends RunnableOnFinish {
|
||||
} catch (OutOfMemoryError e) {
|
||||
finish(false, mCtx.getString(R.string.error_out_of_memory));
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
finish(false, e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
finish(true);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.keepassdroid.database.edit;
|
||||
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
|
||||
|
||||
public abstract class RunnableOnFinish implements Runnable {
|
||||
|
||||
@@ -23,7 +23,7 @@ import android.content.Context;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.exception.PwDbOutputException;
|
||||
|
||||
public class SaveDB extends RunnableOnFinish {
|
||||
|
||||
@@ -23,7 +23,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.exception.InvalidKeyFileException;
|
||||
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
||||
|
||||
@@ -21,9 +21,8 @@ package com.keepassdroid.database.edit;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwEntry;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
|
||||
public class UpdateEntry extends RunnableOnFinish {
|
||||
private Database mDb;
|
||||
@@ -61,36 +60,19 @@ public class UpdateEntry extends RunnableOnFinish {
|
||||
private class AfterUpdate extends OnFinish {
|
||||
private PwEntry mBackup;
|
||||
|
||||
public AfterUpdate(PwEntry backup, OnFinish finish) {
|
||||
AfterUpdate(PwEntry backup, OnFinish finish) {
|
||||
super(finish);
|
||||
|
||||
mBackup = backup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if ( mSuccess ) {
|
||||
// Mark group dirty if title or icon changes
|
||||
if ( ! mBackup.getTitle().equals(mNewE.getTitle()) || ! mBackup.getIcon().equals(mNewE.getIcon()) ) {
|
||||
PwGroup parent = mBackup.getParent();
|
||||
if ( parent != null ) {
|
||||
// Resort entries
|
||||
parent.sortEntriesByName();
|
||||
|
||||
// Mark parent group dirty
|
||||
mDb.dirty.add(parent);
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ( !mSuccess ) {
|
||||
// If we fail to save, back out changes to global structure
|
||||
mOldE.assign(mBackup);
|
||||
}
|
||||
|
||||
// TODO Callback for update entry
|
||||
super.run();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -35,14 +35,14 @@ public class EntrySearchStringIteratorV4 extends EntrySearchStringIterator {
|
||||
|
||||
public EntrySearchStringIteratorV4(PwEntryV4 entry) {
|
||||
this.sp = SearchParametersV4.DEFAULT;
|
||||
setIterator = entry.strings.entrySet().iterator();
|
||||
setIterator = entry.getFields().entrySet().iterator();
|
||||
advance();
|
||||
|
||||
}
|
||||
|
||||
public EntrySearchStringIteratorV4(PwEntryV4 entry, SearchParametersV4 sp) {
|
||||
this.sp = sp;
|
||||
setIterator = entry.strings.entrySet().iterator();
|
||||
setIterator = entry.getFields().entrySet().iterator();
|
||||
advance();
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.keepassdroid.database.load;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.exception.InvalidDBException;
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwDate;
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.keepassdroid.database.load;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
import com.keepassdroid.database.PwDatabaseV3Debug;
|
||||
import com.keepassdroid.database.exception.InvalidDBException;
|
||||
|
||||
|
||||
@@ -46,11 +46,10 @@ import org.xmlpull.v1.XmlPullParserFactory;
|
||||
|
||||
import biz.source_code.base64Coder.Base64Coder;
|
||||
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
import com.keepassdroid.crypto.CipherFactory;
|
||||
import com.keepassdroid.crypto.PwStreamCipherFactory;
|
||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||
import com.keepassdroid.database.BinaryPool;
|
||||
import com.keepassdroid.database.ITimeLogger;
|
||||
import com.keepassdroid.database.PwCompressionAlgorithm;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
@@ -863,7 +862,7 @@ public class ImporterV4 extends Importer {
|
||||
} else if ( ctx == KdbContext.EntryTimes && name.equalsIgnoreCase(ElemTimes) ) {
|
||||
return KdbContext.Entry;
|
||||
} else if ( ctx == KdbContext.EntryString && name.equalsIgnoreCase(ElemString) ) {
|
||||
ctxEntry.strings.put(ctxStringName, ctxStringValue);
|
||||
ctxEntry.addField(ctxStringName, ctxStringValue);
|
||||
ctxStringName = null;
|
||||
ctxStringValue = null;
|
||||
|
||||
@@ -922,7 +921,7 @@ public class ImporterV4 extends Importer {
|
||||
byte[] buf = Base64Coder.decode(sDate);
|
||||
if (buf.length != 8) {
|
||||
byte[] buf8 = new byte[8];
|
||||
System.arraycopy(buf, 0, buf8, 0, buf.length);
|
||||
System.arraycopy(buf, 0, buf8, 0, Math.min(buf.length, 8));
|
||||
buf = buf8;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
*/
|
||||
package com.keepassdroid.database.load;
|
||||
|
||||
import com.keepassdroid.UpdateStatus;
|
||||
import com.keepassdroid.tasks.UpdateStatus;
|
||||
import com.keepassdroid.database.PwDatabaseV4Debug;
|
||||
import com.keepassdroid.database.exception.InvalidDBException;
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ public class PwDbV3Output extends PwDbOutput {
|
||||
groupList.add(group);
|
||||
|
||||
// Recurse over children
|
||||
for ( int i = 0; i < group.childGroups.size(); i++ ) {
|
||||
for ( int i = 0; i < group.numbersOfChildGroups(); i++ ) {
|
||||
sortGroup((PwGroupV3) group.childGroups.get(i), groupList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ public class PwDbV4Output extends PwDbOutput {
|
||||
|
||||
writeList(ElemTimes, entry);
|
||||
|
||||
writeList(entry.strings, true);
|
||||
writeList(entry.getFields(), true);
|
||||
writeList(entry.binaries);
|
||||
writeList(ElemAutoType, entry.autoType);
|
||||
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
*/
|
||||
package com.keepassdroid.database.security;
|
||||
|
||||
public class ProtectedString {
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ProtectedString implements Serializable {
|
||||
|
||||
private String string;
|
||||
private boolean protect;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
class DeleteFileHistoryAsyncTask extends AsyncTask<FileSelectBean, Void, Void> {
|
||||
|
||||
private AfterDeleteFileHistoryListener afterDeleteFileHistoryListener;
|
||||
private RecentFileHistory fileHistory;
|
||||
private FileSelectAdapter adapter;
|
||||
|
||||
DeleteFileHistoryAsyncTask(AfterDeleteFileHistoryListener afterDeleteFileHistoryListener, RecentFileHistory fileHistory, FileSelectAdapter adapter) {
|
||||
this.afterDeleteFileHistoryListener = afterDeleteFileHistoryListener;
|
||||
this.fileHistory = fileHistory;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
protected Void doInBackground(FileSelectBean... args) {
|
||||
fileHistory.deleteFile(args[0].getFileUri());
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Void v) {
|
||||
adapter.notifyDataSetChanged();
|
||||
if (adapter.getItemCount() == 0) {
|
||||
if(afterDeleteFileHistoryListener != null)
|
||||
afterDeleteFileHistoryListener.afterDeleteFile();
|
||||
}
|
||||
}
|
||||
|
||||
public interface AfterDeleteFileHistoryListener {
|
||||
void afterDeleteFile();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.text.DateFormat;
|
||||
|
||||
public class FileInformationDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String FILE_SELECT_BEEN_ARG = "FILE_SELECT_BEEN_ARG";
|
||||
|
||||
public static FileInformationDialogFragment newInstance(FileSelectBean fileSelectBean) {
|
||||
FileInformationDialogFragment fileInformationDialogFragment =
|
||||
new FileInformationDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(FILE_SELECT_BEEN_ARG, fileSelectBean);
|
||||
fileInformationDialogFragment.setArguments(args);
|
||||
return fileInformationDialogFragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
View root = inflater.inflate(R.layout.file_selection_information, null);
|
||||
|
||||
if (getArguments() != null && getArguments().containsKey(FILE_SELECT_BEEN_ARG)) {
|
||||
FileSelectBean fileSelectBean = (FileSelectBean) getArguments().getSerializable(FILE_SELECT_BEEN_ARG);
|
||||
TextView fileWarningView = (TextView) root.findViewById(R.id.file_warning);
|
||||
if(fileSelectBean != null) {
|
||||
TextView fileNameView = (TextView) root.findViewById(R.id.file_filename);
|
||||
TextView filePathView = (TextView) root.findViewById(R.id.file_path);
|
||||
TextView fileSizeView = (TextView) root.findViewById(R.id.file_size);
|
||||
TextView fileModificationView = (TextView) root.findViewById(R.id.file_modification);
|
||||
fileWarningView.setVisibility(View.GONE);
|
||||
fileNameView.setText(fileSelectBean.getFileName());
|
||||
filePathView.setText(Uri.decode(fileSelectBean.getFileUri().toString()));
|
||||
fileSizeView.setText(String.valueOf(fileSelectBean.getSize()));
|
||||
fileModificationView.setText(DateFormat.getDateTimeInstance()
|
||||
.format(fileSelectBean.getLastModification()));
|
||||
if(fileSelectBean.notFound())
|
||||
showFileNotFound(fileWarningView);
|
||||
} else
|
||||
showFileNotFound(fileWarningView);
|
||||
}
|
||||
|
||||
builder.setView(root);
|
||||
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
|
||||
}
|
||||
});
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private void showFileNotFound(TextView fileWarningView) {
|
||||
fileWarningView.setVisibility(View.VISIBLE);
|
||||
fileWarningView.setText(R.string.file_not_found);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -21,123 +21,161 @@ package com.keepassdroid.fileselect;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.AssignMasterKeyDialog;
|
||||
import com.keepassdroid.CreateFileDialog;
|
||||
import com.keepassdroid.GroupActivity;
|
||||
import com.keepassdroid.PasswordActivity;
|
||||
import com.keepassdroid.ProgressTask;
|
||||
import com.keepassdroid.activities.GroupActivity;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.compat.ContentResolverCompat;
|
||||
import com.keepassdroid.compat.StorageAF;
|
||||
import com.keepassdroid.autofill.AutofillHelper;
|
||||
import com.keepassdroid.database.edit.CreateDB;
|
||||
import com.keepassdroid.database.edit.FileOnFinish;
|
||||
import com.keepassdroid.database.exception.ContentFileNotFoundException;
|
||||
import com.keepassdroid.intents.Intents;
|
||||
import com.keepassdroid.fragments.AssignMasterKeyDialogFragment;
|
||||
import com.keepassdroid.fragments.CreateFileDialogFragment;
|
||||
import com.keepassdroid.password.PasswordActivity;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
import com.keepassdroid.tasks.ProgressTask;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.Interaction;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.keepassdroid.utils.Util;
|
||||
import com.keepassdroid.view.AssignPasswordHelper;
|
||||
import com.keepassdroid.view.FileNameView;
|
||||
import com.keepassdroid.view.KeyFileHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import permissions.dispatcher.NeedsPermission;
|
||||
import permissions.dispatcher.OnNeverAskAgain;
|
||||
import permissions.dispatcher.OnPermissionDenied;
|
||||
import permissions.dispatcher.OnShowRationale;
|
||||
import permissions.dispatcher.PermissionRequest;
|
||||
import permissions.dispatcher.RuntimePermissions;
|
||||
|
||||
@RuntimePermissions
|
||||
public class FileSelectActivity extends StylishActivity implements
|
||||
CreateFileDialog.DefinePathDialogListener ,
|
||||
AssignMasterKeyDialog.AssignPasswordDialogListener {
|
||||
CreateFileDialogFragment.DefinePathDialogListener ,
|
||||
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
FileSelectAdapter.FileItemOpenListener,
|
||||
FileSelectAdapter.FileSelectClearListener,
|
||||
FileSelectAdapter.FileInformationShowListener {
|
||||
|
||||
private static final String TAG = "FileSelectActivity";
|
||||
|
||||
private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 111;
|
||||
private ListView mList;
|
||||
private BaseAdapter mAdapter;
|
||||
private static final String EXTRA_STAY = "EXTRA_STAY";
|
||||
|
||||
private static final int CMENU_CLEAR = Menu.FIRST;
|
||||
|
||||
public static final int FILE_BROWSE = 1;
|
||||
public static final int GET_CONTENT = 2;
|
||||
public static final int OPEN_DOC = 3;
|
||||
private FileSelectAdapter mAdapter;
|
||||
private View fileListTitle;
|
||||
|
||||
private RecentFileHistory fileHistory;
|
||||
|
||||
private boolean recentMode = false;
|
||||
// TODO Consultation Mode
|
||||
private boolean consultationMode = false;
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
private EditText openFileNameView;
|
||||
private FileNameView fileNameView;
|
||||
|
||||
private AssignPasswordHelper assignPasswordHelper;
|
||||
private Uri databaseUri;
|
||||
|
||||
private KeyFileHelper keyFileHelper;
|
||||
|
||||
public static void launch(Activity activity) {
|
||||
Intent intent = new Intent(activity, FileSelectActivity.class);
|
||||
// only to avoid visible flickering when redirecting
|
||||
activity.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void launch(Activity activity, AssistStructure assistStructure) {
|
||||
if ( assistStructure != null ) {
|
||||
Intent intent = new Intent(activity, FileSelectActivity.class);
|
||||
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
||||
activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||
} else {
|
||||
launch(activity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (AutofillHelper.isIntentContainsExtraAssistStructureKey(getIntent()))
|
||||
consultationMode = true;
|
||||
}
|
||||
|
||||
fileHistory = App.getFileHistory();
|
||||
|
||||
setContentView(R.layout.file_selection);
|
||||
if (fileHistory.hasRecentFiles()) {
|
||||
recentMode = true;
|
||||
} else {
|
||||
findViewById(R.id.file_listtop).setVisibility(View.INVISIBLE);
|
||||
}
|
||||
fileListTitle = findViewById(R.id.file_list_title);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
mList = (ListView)findViewById(R.id.file_list);
|
||||
openFileNameView = (EditText) findViewById(R.id.file_filename);
|
||||
fileNameView = (FileNameView) findViewById(R.id.file_select);
|
||||
|
||||
mList.setOnItemClickListener(
|
||||
new AdapterView.OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
|
||||
onListItemClick((ListView)parent, v, position, id);
|
||||
// Set the initial value of the filename
|
||||
String defaultPath = Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
+ getString(R.string.database_file_path_default)
|
||||
+ getString(R.string.database_file_name_default)
|
||||
+ getString(R.string.database_file_extension_default);
|
||||
openFileNameView.setText(defaultPath);
|
||||
|
||||
RecyclerView mListFiles = (RecyclerView) findViewById(R.id.file_list);
|
||||
mListFiles.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
// To retrieve info for AutoFill
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
autofillHelper.retrieveAssistStructure(getIntent());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Open button
|
||||
View openButton = findViewById(R.id.open_database);
|
||||
openButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
String fileName = Util.getEditText(FileSelectActivity.this,
|
||||
R.id.file_filename);
|
||||
String fileName = openFileNameView.getText().toString();
|
||||
try {
|
||||
PasswordActivity.Launch(FileSelectActivity.this, fileName);
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.retrieveAssistStructure(getIntent());
|
||||
if (assistStructure != null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this,
|
||||
fileName,
|
||||
assistStructure);
|
||||
}
|
||||
}
|
||||
if (assistStructure == null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this, fileName);
|
||||
}
|
||||
}
|
||||
catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
@@ -145,7 +183,7 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.FileNotFound, Toast.LENGTH_LONG).show();
|
||||
R.string.file_not_found, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -154,73 +192,32 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
View createButton = findViewById(R.id.create_database);
|
||||
createButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
CreateFileDialog createFileDialog = new CreateFileDialog();
|
||||
createFileDialog.show(getSupportFragmentManager(), "createFileDialog");
|
||||
FileSelectActivityPermissionsDispatcher
|
||||
.openCreateFileDialogFragmentWithPermissionCheck(FileSelectActivity.this);
|
||||
}
|
||||
});
|
||||
|
||||
keyFileHelper = new KeyFileHelper(this);
|
||||
View browseButton = findViewById(R.id.browse_button);
|
||||
browseButton.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
public void onClick(View v) {
|
||||
if (StorageAF.useStorageFramework(FileSelectActivity.this)) {
|
||||
Intent i = new Intent(StorageAF.ACTION_OPEN_DOCUMENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION|
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION|
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
startActivityForResult(i, OPEN_DOC);
|
||||
browseButton.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener(
|
||||
new KeyFileHelper.ClickDataUriCallback() {
|
||||
@Override
|
||||
public Uri onRequestIntentFilePicker() {
|
||||
return Uri.parse("file://" + openFileNameView.getText().toString());
|
||||
}
|
||||
else {
|
||||
Intent i;
|
||||
i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.setType("*/*");
|
||||
}));
|
||||
|
||||
try {
|
||||
startActivityForResult(i, GET_CONTENT);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
lookForOpenIntentsFilePicker();
|
||||
} catch (SecurityException e) {
|
||||
lookForOpenIntentsFilePicker();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Construct adapter with listeners
|
||||
mAdapter = new FileSelectAdapter(FileSelectActivity.this, fileHistory.getDbList());
|
||||
mAdapter.setOnItemClickListener(this);
|
||||
mAdapter.setFileSelectClearListener(this);
|
||||
mAdapter.setFileInformationShowListener(this);
|
||||
mListFiles.setAdapter(mAdapter);
|
||||
|
||||
private void lookForOpenIntentsFilePicker() {
|
||||
if (Interaction.isIntentAvailable(FileSelectActivity.this, Intents.OPEN_INTENTS_FILE_BROWSE)) {
|
||||
Intent i = new Intent(Intents.OPEN_INTENTS_FILE_BROWSE);
|
||||
i.setData(Uri.parse("file://" + Util.getEditText(FileSelectActivity.this, R.id.file_filename)));
|
||||
try {
|
||||
startActivityForResult(i, FILE_BROWSE);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
showBrowserDialog();
|
||||
}
|
||||
} else {
|
||||
showBrowserDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private void showBrowserDialog() {
|
||||
BrowserDialog diag = new BrowserDialog(FileSelectActivity.this);
|
||||
diag.show();
|
||||
}
|
||||
});
|
||||
|
||||
// Set the initial value of the filename
|
||||
openFileNameView = (EditText) findViewById(R.id.file_filename);
|
||||
String defaultPath = Environment.getExternalStorageDirectory().getAbsolutePath()
|
||||
+ getString(R.string.database_file_path_default)
|
||||
+ getString(R.string.database_file_name_default)
|
||||
+ getString(R.string.database_file_extension_default);
|
||||
openFileNameView.setText(defaultPath);
|
||||
|
||||
fillData();
|
||||
|
||||
registerForContextMenu(mList);
|
||||
|
||||
// Load default database
|
||||
// Load default database if not an orientation change
|
||||
if (! (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(EXTRA_STAY)
|
||||
&& savedInstanceState.getBoolean(EXTRA_STAY, false)) ) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String fileName = prefs.getString(PasswordActivity.KEY_DEFAULT_FILENAME, "");
|
||||
|
||||
@@ -235,21 +232,72 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
File db = new File(path);
|
||||
|
||||
if (db.exists()) {
|
||||
launchPasswordActivityWithPath(path);
|
||||
}
|
||||
} else {
|
||||
if (dbUri != null)
|
||||
launchPasswordActivityWithPath(dbUri.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void launchPasswordActivityWithPath(String path) {
|
||||
try {
|
||||
PasswordActivity.Launch(FileSelectActivity.this, path);
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this,
|
||||
path,
|
||||
assistStructure);
|
||||
}
|
||||
}
|
||||
if (assistStructure == null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this, path);
|
||||
}
|
||||
// Delete flickering for kitkat <=
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
|
||||
overridePendingTransition(0, 0);
|
||||
} catch (Exception e) {
|
||||
// Ignore exception
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
fileNameView.updateExternalStorageWarning();
|
||||
updateTitleFileListView();
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
else {
|
||||
try {
|
||||
PasswordActivity.Launch(FileSelectActivity.this, dbUri.toString());
|
||||
} catch (Exception e) {
|
||||
// Ignore exception
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
// only to keep the current activity
|
||||
outState.putBoolean(EXTRA_STAY, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
// NOTE: delegate the permission handling to generated method
|
||||
FileSelectActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
public void openCreateFileDialogFragment() {
|
||||
CreateFileDialogFragment createFileDialogFragment = new CreateFileDialogFragment();
|
||||
createFileDialogFragment.show(getSupportFragmentManager(), "createFileDialogFragment");
|
||||
}
|
||||
|
||||
private void updateTitleFileListView() {
|
||||
if(mAdapter.getItemCount() == 0)
|
||||
fileListTitle.setVisibility(View.INVISIBLE);
|
||||
else
|
||||
fileListTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,8 +364,8 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
public boolean onDefinePathDialogPositiveClick(Uri pathFile) {
|
||||
databaseUri = pathFile;
|
||||
if(createDatabaseFile(pathFile)) {
|
||||
AssignMasterKeyDialog assignMasterKeyDialog = new AssignMasterKeyDialog();
|
||||
assignMasterKeyDialog.show(getSupportFragmentManager(), "passwordDialog");
|
||||
AssignMasterKeyDialogFragment assignMasterKeyDialogFragment = new AssignMasterKeyDialogFragment();
|
||||
assignMasterKeyDialogFragment.show(getSupportFragmentManager(), "passwordDialog");
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
@@ -390,136 +438,112 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
// Add to recent files
|
||||
fileHistory.createFile(mUri, getFilename());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
GroupActivity.Launch(FileSelectActivity.this);
|
||||
updateTitleFileListView();
|
||||
GroupActivity.launch(FileSelectActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillData() {
|
||||
mAdapter = new ArrayAdapter<>(FileSelectActivity.this, R.layout.file_row, R.id.file_filename, fileHistory.getDbList());
|
||||
mList.setAdapter(mAdapter);
|
||||
@Override
|
||||
public void onFileItemOpenListener(int itemPosition) {
|
||||
new OpenFileHistoryAsyncTask(new OpenFileHistoryAsyncTask.AfterOpenFileHistoryListener() {
|
||||
@Override
|
||||
public void afterOpenFile(String fileName, String keyFile) {
|
||||
try {
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this,
|
||||
fileName, keyFile, assistStructure);
|
||||
}
|
||||
}
|
||||
if (assistStructure == null) {
|
||||
PasswordActivity.launch(FileSelectActivity.this, fileName, keyFile);
|
||||
}
|
||||
} catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found_content, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} catch (FileNotFoundException e) {
|
||||
Toast.makeText(FileSelectActivity.this,
|
||||
R.string.file_not_found, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
updateTitleFileListView();
|
||||
}
|
||||
}, fileHistory).execute(itemPosition);
|
||||
}
|
||||
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
new OpenFileHistoryAsyncTask(this, fileHistory).execute(position);
|
||||
@Override
|
||||
public void onClickFileInformation(FileSelectBean fileSelectBean) {
|
||||
if (fileSelectBean != null) {
|
||||
FileInformationDialogFragment fileInformationDialogFragment =
|
||||
FileInformationDialogFragment.newInstance(fileSelectBean);
|
||||
fileInformationDialogFragment.show(getSupportFragmentManager(), "fileInformation");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onFileSelectClearListener(final FileSelectBean fileSelectBean) {
|
||||
new DeleteFileHistoryAsyncTask(new DeleteFileHistoryAsyncTask.AfterDeleteFileHistoryListener() {
|
||||
@Override
|
||||
public void afterDeleteFile() {
|
||||
fileHistory.deleteFile(fileSelectBean.getFileUri());
|
||||
mAdapter.notifyDataSetChanged();
|
||||
updateTitleFileListView();
|
||||
}
|
||||
}, fileHistory, mAdapter).execute(fileSelectBean);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
fillData();
|
||||
|
||||
String filename = null;
|
||||
if (requestCode == FILE_BROWSE && resultCode == RESULT_OK) {
|
||||
filename = data.getDataString();
|
||||
if (filename != null) {
|
||||
if (filename.startsWith("file://")) {
|
||||
filename = filename.substring(7);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
filename = URLDecoder.decode(filename);
|
||||
}
|
||||
|
||||
}
|
||||
else if ((requestCode == GET_CONTENT || requestCode == OPEN_DOC) && resultCode == RESULT_OK) {
|
||||
if (data != null) {
|
||||
Uri uri = data.getData();
|
||||
keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
|
||||
new KeyFileHelper.KeyFileCallback() {
|
||||
@Override
|
||||
public void onKeyFileResultCallback(Uri uri) {
|
||||
if (uri != null) {
|
||||
if (StorageAF.useStorageFramework(this)) {
|
||||
try {
|
||||
// try to persist read and write permissions
|
||||
ContentResolver resolver = getContentResolver();
|
||||
ContentResolverCompat.takePersistableUriPermission(resolver, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
ContentResolverCompat.takePersistableUriPermission(resolver, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
} catch (Exception e) {
|
||||
// nop
|
||||
}
|
||||
}
|
||||
if (requestCode == GET_CONTENT) {
|
||||
uri = UriUtil.translate(this, uri);
|
||||
}
|
||||
filename = uri.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filename != null) {
|
||||
String filename = uri.toString();
|
||||
openFileNameView.setText(filename);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
void showRationaleForExternalStorage(final PermissionRequest request) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_write_database)
|
||||
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// check for storage permission
|
||||
checkStoragePermission();
|
||||
|
||||
// Check to see if we need to change modes
|
||||
if ( fileHistory.hasRecentFiles() != recentMode ) {
|
||||
// Restart the activity
|
||||
Intent intent = getIntent();
|
||||
startActivity(intent);
|
||||
finish();
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.proceed();
|
||||
}
|
||||
|
||||
FileNameView fnv = (FileNameView) findViewById(R.id.file_select);
|
||||
fnv.updateExternalStorageWarning();
|
||||
}
|
||||
|
||||
private void checkStoragePermission() {
|
||||
// Here, thisActivity is the current activity
|
||||
if (ContextCompat.checkSelfPermission(FileSelectActivity.this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
// Should we show an explanation?
|
||||
//if (ActivityCompat.shouldShowRequestPermissionRationale(FileSelectActivity.this,
|
||||
// Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
|
||||
// Show an explanation to the user *asynchronously* -- don't block
|
||||
// this thread waiting for the user's response! After the user
|
||||
// sees the explanation, try again to request the permission.
|
||||
|
||||
//} else {
|
||||
|
||||
// No explanation needed, we can request the permission.
|
||||
|
||||
ActivityCompat.requestPermissions(FileSelectActivity.this,
|
||||
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||
MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE);
|
||||
|
||||
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
|
||||
// app-defined int constant. The callback method gets the
|
||||
// result of the request.
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
String permissions[], int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
|
||||
// permission was granted, yay! Do the
|
||||
// contacts-related task you need to do.
|
||||
|
||||
} else {
|
||||
|
||||
// permission denied, boo! Disable the
|
||||
// functionality that depends on this permission.
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.cancel();
|
||||
}
|
||||
return;
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
// other 'case' lines to check for other
|
||||
// permissions this app might request
|
||||
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
void showDeniedForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
void showNeverAskForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -534,83 +558,4 @@ public class FileSelectActivity extends StylishActivity implements
|
||||
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item)
|
||||
&& super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v,
|
||||
ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
|
||||
menu.add(0, CMENU_CLEAR, 0, R.string.remove_from_filelist);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
super.onContextItemSelected(item);
|
||||
|
||||
if ( item.getItemId() == CMENU_CLEAR ) {
|
||||
AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) item.getMenuInfo();
|
||||
|
||||
TextView tv = (TextView) acmi.targetView;
|
||||
String filename = tv.getText().toString();
|
||||
new DeleteFileHistoryAsyncTask(fileHistory, mAdapter).execute(filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class OpenFileHistoryAsyncTask extends AsyncTask<Integer, Void, Void> {
|
||||
|
||||
private WeakReference<Activity> weakActivity;
|
||||
private RecentFileHistory fileHistory;
|
||||
private String fileName;
|
||||
private String keyFile;
|
||||
|
||||
OpenFileHistoryAsyncTask(Activity activity, RecentFileHistory fileHistory) {
|
||||
this.weakActivity = new WeakReference<>(activity);
|
||||
this.fileHistory = fileHistory;
|
||||
}
|
||||
|
||||
protected Void doInBackground(Integer... args) {
|
||||
int position = args[0];
|
||||
fileName = fileHistory.getDatabaseAt(position);
|
||||
keyFile = fileHistory.getKeyfileAt(position);
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Void v) {
|
||||
try {
|
||||
PasswordActivity.Launch(weakActivity.get(), fileName, keyFile);
|
||||
}
|
||||
catch (ContentFileNotFoundException e) {
|
||||
Toast.makeText(weakActivity.get(), R.string.file_not_found_content, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
Toast.makeText(weakActivity.get(), R.string.FileNotFound, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteFileHistoryAsyncTask extends AsyncTask<String, Void, Void> {
|
||||
|
||||
private RecentFileHistory fileHistory;
|
||||
private BaseAdapter adapter;
|
||||
|
||||
DeleteFileHistoryAsyncTask(RecentFileHistory fileHistory, BaseAdapter adapter) {
|
||||
this.fileHistory = fileHistory;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
protected java.lang.Void doInBackground(String... args) {
|
||||
fileHistory.deleteFile(Uri.parse(args[0]));
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Void v) {
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.ColorInt;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class FileSelectAdapter extends RecyclerView.Adapter<FileSelectViewHolder> {
|
||||
|
||||
private static final int MENU_CLEAR = 1;
|
||||
|
||||
private Context context;
|
||||
private LayoutInflater inflater;
|
||||
private List<String> listFiles;
|
||||
private FileItemOpenListener fileItemOpenListener;
|
||||
private FileSelectClearListener fileSelectClearListener;
|
||||
private FileInformationShowListener fileInformationShowListener;
|
||||
|
||||
private @ColorInt
|
||||
int warningColor;
|
||||
|
||||
FileSelectAdapter(Context context, List<String> listFiles) {
|
||||
this.inflater = LayoutInflater.from(context);
|
||||
this.context = context;
|
||||
this.listFiles = listFiles;
|
||||
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(R.attr.colorAccentCompat, typedValue, true);
|
||||
warningColor = typedValue.data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSelectViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = inflater.inflate(R.layout.file_row, parent, false);
|
||||
return new FileSelectViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(FileSelectViewHolder holder, int position) {
|
||||
FileSelectBean fileSelectBean = new FileSelectBean(context, listFiles.get(position));
|
||||
// Context menu creation
|
||||
holder.fileContainer.setOnCreateContextMenuListener(new ContextMenuBuilder(fileSelectBean));
|
||||
// Click item to open file
|
||||
if (fileItemOpenListener != null)
|
||||
holder.fileContainer.setOnClickListener(new FileItemClickListener(position));
|
||||
// Assign file name
|
||||
if (PreferencesUtil.isFullFilePathEnable(context))
|
||||
holder.fileName.setText(Uri.decode(fileSelectBean.getFileUri().toString()));
|
||||
else
|
||||
holder.fileName.setText(fileSelectBean.getFileName());
|
||||
holder.fileName.setTextSize(PreferencesUtil.getListTextSize(context));
|
||||
// Set warning
|
||||
if (fileSelectBean.notFound()) {
|
||||
holder.fileInformation.setColorFilter(
|
||||
warningColor,
|
||||
android.graphics.PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
// Click on information
|
||||
if (fileInformationShowListener != null)
|
||||
holder.fileInformation.setOnClickListener(new FileInformationClickListener(fileSelectBean));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return listFiles.size();
|
||||
}
|
||||
|
||||
void setOnItemClickListener(FileItemOpenListener fileItemOpenListener) {
|
||||
this.fileItemOpenListener = fileItemOpenListener;
|
||||
}
|
||||
|
||||
void setFileSelectClearListener(FileSelectClearListener fileSelectClearListener) {
|
||||
this.fileSelectClearListener = fileSelectClearListener;
|
||||
}
|
||||
|
||||
void setFileInformationShowListener(FileInformationShowListener fileInformationShowListener) {
|
||||
this.fileInformationShowListener = fileInformationShowListener;
|
||||
}
|
||||
|
||||
public interface FileItemOpenListener {
|
||||
void onFileItemOpenListener(int itemPosition);
|
||||
}
|
||||
|
||||
public interface FileSelectClearListener {
|
||||
boolean onFileSelectClearListener(FileSelectBean fileSelectBean);
|
||||
}
|
||||
|
||||
public interface FileInformationShowListener {
|
||||
void onClickFileInformation(FileSelectBean fileSelectBean);
|
||||
}
|
||||
|
||||
private class FileItemClickListener implements View.OnClickListener {
|
||||
|
||||
private int position;
|
||||
|
||||
FileItemClickListener(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
fileItemOpenListener.onFileItemOpenListener(position);
|
||||
}
|
||||
}
|
||||
|
||||
private class FileInformationClickListener implements View.OnClickListener {
|
||||
|
||||
private FileSelectBean fileSelectBean;
|
||||
|
||||
FileInformationClickListener(FileSelectBean fileSelectBean) {
|
||||
this.fileSelectBean = fileSelectBean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
fileInformationShowListener.onClickFileInformation(fileSelectBean);
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextMenuBuilder implements View.OnCreateContextMenuListener {
|
||||
|
||||
private FileSelectBean fileSelectBean;
|
||||
|
||||
public ContextMenuBuilder(FileSelectBean fileSelectBean) {
|
||||
this.fileSelectBean = fileSelectBean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
|
||||
MenuItem clearMenu = contextMenu.add(Menu.NONE, MENU_CLEAR, Menu.NONE, R.string.remove_from_filelist);
|
||||
clearMenu.setOnMenuItemClickListener(mOnMyActionClickListener);
|
||||
}
|
||||
|
||||
private MenuItem.OnMenuItemClickListener mOnMyActionClickListener = new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (fileSelectClearListener == null)
|
||||
return false;
|
||||
switch ( item.getItemId() ) {
|
||||
case MENU_CLEAR:
|
||||
return fileSelectClearListener.onFileSelectClearListener(fileSelectBean);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2017 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
public class FileSelectBean implements Serializable {
|
||||
|
||||
private static final String EXTERNAL_STORAGE_AUTHORITY = "com.android.externalstorage.documents";
|
||||
|
||||
private String fileName = "";
|
||||
private Uri fileUri;
|
||||
private Date lastModification = new Date();
|
||||
private long size = 0;
|
||||
|
||||
public FileSelectBean(Context context, String pathFile) {
|
||||
fileUri = Uri.parse(pathFile);
|
||||
if (EXTERNAL_STORAGE_AUTHORITY.equals(fileUri.getAuthority())) {
|
||||
DocumentFile file = DocumentFile.fromSingleUri(context, fileUri);
|
||||
size = file.length();
|
||||
fileName = file.getName();
|
||||
lastModification = new Date(file.lastModified());
|
||||
} else {
|
||||
File file = new File(fileUri.getPath());
|
||||
size = file.length();
|
||||
fileName = file.getName();
|
||||
lastModification = new Date(file.lastModified());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean notFound() {
|
||||
return getSize() == 0;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public Uri getFileUri() {
|
||||
return fileUri;
|
||||
}
|
||||
|
||||
public void setFileUri(Uri fileUri) {
|
||||
this.fileUri = fileUri;
|
||||
}
|
||||
|
||||
public Date getLastModification() {
|
||||
return lastModification;
|
||||
}
|
||||
|
||||
public void setLastModification(Date lastModification) {
|
||||
this.lastModification = lastModification;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
@@ -17,29 +17,25 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.view;
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class GroupViewOnlyView extends GroupAddEntryView {
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public GroupViewOnlyView(Context context) {
|
||||
this(context, null);
|
||||
class FileSelectViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
View fileContainer;
|
||||
TextView fileName;
|
||||
ImageView fileInformation;
|
||||
|
||||
FileSelectViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
fileContainer = itemView.findViewById(R.id.file_container);
|
||||
fileName = (TextView) itemView.findViewById(R.id.file_filename);
|
||||
fileInformation = (ImageView) itemView.findViewById(R.id.file_information);
|
||||
}
|
||||
|
||||
public GroupViewOnlyView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
inflate(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void inflate(Context context) {
|
||||
super.inflate(context);
|
||||
|
||||
// Hide the buttons
|
||||
addButton.setVisibility(GONE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
class OpenFileHistoryAsyncTask extends AsyncTask<Integer, Void, Void> {
|
||||
|
||||
private AfterOpenFileHistoryListener afterOpenFileHistoryListener;
|
||||
private RecentFileHistory fileHistory;
|
||||
private String fileName;
|
||||
private String keyFile;
|
||||
|
||||
OpenFileHistoryAsyncTask(AfterOpenFileHistoryListener afterOpenFileHistoryListener, RecentFileHistory fileHistory) {
|
||||
this.afterOpenFileHistoryListener = afterOpenFileHistoryListener;
|
||||
this.fileHistory = fileHistory;
|
||||
}
|
||||
|
||||
protected Void doInBackground(Integer... args) {
|
||||
int position = args[0];
|
||||
fileName = fileHistory.getDatabaseAt(position);
|
||||
keyFile = fileHistory.getKeyfileAt(position);
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void onPostExecute(Void v) {
|
||||
afterOpenFileHistoryListener.afterOpenFile(fileName, keyFile);
|
||||
}
|
||||
|
||||
public interface AfterOpenFileHistoryListener {
|
||||
void afterOpenFile(String fileName, String keyFile);
|
||||
}
|
||||
}
|
||||
@@ -19,14 +19,6 @@
|
||||
*/
|
||||
package com.keepassdroid.fileselect;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||
@@ -34,6 +26,14 @@ import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RecentFileHistory {
|
||||
|
||||
private static String DB_KEY = "recent_databases";
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.keepassdroid.fingerprint;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
@@ -32,18 +31,23 @@ import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v4.os.CancellationSignal;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.keepassdroid.compat.BuildCompat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class FingerPrintHelper {
|
||||
|
||||
private static final String ALIAS_KEY = "example-key";
|
||||
private static final String FINGERPRINT_KEYSTORE_KEY = "example-key";
|
||||
|
||||
private FingerprintManagerCompat fingerprintManager;
|
||||
private KeyStore keyStore = null;
|
||||
@@ -74,7 +78,7 @@ public class FingerPrintHelper {
|
||||
}
|
||||
|
||||
public void stopListening() {
|
||||
if (!isFingerprintInitialized()) {
|
||||
if (!isFingerprintInitialized(false)) {
|
||||
return;
|
||||
}
|
||||
if (cancellationSignal != null) {
|
||||
@@ -83,14 +87,6 @@ public class FingerPrintHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public interface FingerPrintCallback {
|
||||
void handleEncryptedResult(String value, String ivSpec);
|
||||
void handleDecryptedResult(String value);
|
||||
void onInvalidKeyException();
|
||||
void onFingerprintException(Exception e);
|
||||
}
|
||||
|
||||
@TargetApi(BuildCompat.VERSION_CODE_M)
|
||||
public FingerPrintHelper(
|
||||
final Context context,
|
||||
final FingerPrintCallback fingerPrintCallback) {
|
||||
@@ -118,25 +114,29 @@ public class FingerPrintHelper {
|
||||
setInitOk(true);
|
||||
} catch (final Exception e) {
|
||||
setInitOk(false);
|
||||
fingerPrintCallback.onFingerprintException(e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFingerprintSupported(FingerprintManagerCompat fingerprintManager) {
|
||||
return Build.VERSION.SDK_INT >= BuildCompat.VERSION_CODE_M
|
||||
public static boolean isFingerprintSupported(FingerprintManagerCompat fingerprintManager) {
|
||||
return fingerprintManager != null
|
||||
&& fingerprintManager.isHardwareDetected();
|
||||
}
|
||||
|
||||
public boolean isFingerprintInitialized() {
|
||||
return isFingerprintInitialized(true);
|
||||
}
|
||||
|
||||
public boolean isFingerprintInitialized(boolean throwException) {
|
||||
boolean isFingerprintInit = hasEnrolledFingerprints() && initOk;
|
||||
if (!isFingerprintInit && fingerPrintCallback != null) {
|
||||
fingerPrintCallback.onFingerprintException(new Exception("FingerPrint not initialized"));
|
||||
if(throwException)
|
||||
fingerPrintCallback.onFingerPrintException(new Exception("FingerPrint not initialized"));
|
||||
}
|
||||
return isFingerprintInit;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public void initEncryptData() {
|
||||
if (!isFingerprintInitialized()) {
|
||||
return;
|
||||
@@ -144,16 +144,16 @@ public class FingerPrintHelper {
|
||||
try {
|
||||
createNewKeyIfNeeded(false); // no need to keep deleting existing keys
|
||||
keyStore.load(null);
|
||||
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
|
||||
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
|
||||
stopListening();
|
||||
startListening();
|
||||
|
||||
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
|
||||
deleteEntryKey();
|
||||
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||
fingerPrintCallback.onInvalidKeyException();
|
||||
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
|
||||
} catch (final Exception e) {
|
||||
fingerPrintCallback.onFingerprintException(e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class FingerPrintHelper {
|
||||
try {
|
||||
// actual do encryption here
|
||||
byte[] encrypted = cipher.doFinal(value.getBytes());
|
||||
final String encryptedValue = Base64.encodeToString(encrypted, 0 /* flags */);
|
||||
final String encryptedValue = Base64.encodeToString(encrypted, Base64.DEFAULT);
|
||||
|
||||
// passes updated iv spec on to callback so this can be stored for decryption
|
||||
final IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
|
||||
@@ -172,11 +172,10 @@ public class FingerPrintHelper {
|
||||
fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
|
||||
|
||||
} catch (final Exception e) {
|
||||
fingerPrintCallback.onFingerprintException(e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public void initDecryptData(final String ivSpecValue) {
|
||||
if (!isFingerprintInitialized()) {
|
||||
return;
|
||||
@@ -184,20 +183,20 @@ public class FingerPrintHelper {
|
||||
try {
|
||||
createNewKeyIfNeeded(false);
|
||||
keyStore.load(null);
|
||||
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
|
||||
final SecretKey key = (SecretKey) keyStore.getKey(FINGERPRINT_KEYSTORE_KEY, null);
|
||||
|
||||
// important to restore spec here that was used for decryption
|
||||
final byte[] iv = Base64.decode(ivSpecValue, Base64.DEFAULT);
|
||||
final IvParameterSpec spec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
|
||||
stopListening();
|
||||
startListening();
|
||||
|
||||
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||
fingerPrintCallback.onInvalidKeyException();
|
||||
fingerPrintCallback.onInvalidKeyException(invalidKeyException);
|
||||
} catch (final UnrecoverableKeyException unrecoverableKeyException) {
|
||||
deleteEntryKey();
|
||||
} catch (final Exception e) {
|
||||
fingerPrintCallback.onFingerprintException(e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,14 +206,16 @@ public class FingerPrintHelper {
|
||||
}
|
||||
try {
|
||||
// actual decryption here
|
||||
final byte[] encrypted = Base64.decode(encryptedValue, 0);
|
||||
final byte[] encrypted = Base64.decode(encryptedValue, Base64.DEFAULT);
|
||||
byte[] decrypted = cipher.doFinal(encrypted);
|
||||
final String decryptedString = new String(decrypted);
|
||||
|
||||
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
|
||||
fingerPrintCallback.handleDecryptedResult(decryptedString);
|
||||
} catch (final BadPaddingException badPaddingException) {
|
||||
fingerPrintCallback.onInvalidKeyException(badPaddingException);
|
||||
} catch (final Exception e) {
|
||||
fingerPrintCallback.onFingerprintException(e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,18 +227,17 @@ public class FingerPrintHelper {
|
||||
try {
|
||||
keyStore.load(null);
|
||||
if (allowDeleteExisting
|
||||
&& keyStore.containsAlias(ALIAS_KEY)) {
|
||||
|
||||
keyStore.deleteEntry(ALIAS_KEY);
|
||||
&& keyStore.containsAlias(FINGERPRINT_KEYSTORE_KEY)) {
|
||||
keyStore.deleteEntry(FINGERPRINT_KEYSTORE_KEY);
|
||||
}
|
||||
|
||||
// Create new key if needed
|
||||
if (!keyStore.containsAlias(ALIAS_KEY)) {
|
||||
if (!keyStore.containsAlias(FINGERPRINT_KEYSTORE_KEY)) {
|
||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||
// and the constrains (purposes) in the constructor of the Builder
|
||||
keyGenerator.init(
|
||||
new KeyGenParameterSpec.Builder(
|
||||
ALIAS_KEY,
|
||||
FINGERPRINT_KEYSTORE_KEY,
|
||||
KeyProperties.PURPOSE_ENCRYPT |
|
||||
KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
@@ -249,7 +249,21 @@ public class FingerPrintHelper {
|
||||
keyGenerator.generateKey();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
fingerPrintCallback.onFingerprintException(e);
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteEntryKey() {
|
||||
try {
|
||||
keyStore.load(null);
|
||||
keyStore.deleteEntry(FINGERPRINT_KEYSTORE_KEY);
|
||||
} catch (KeyStoreException
|
||||
| CertificateException
|
||||
| NoSuchAlgorithmException
|
||||
| IOException
|
||||
| NullPointerException e) {
|
||||
if (fingerPrintCallback != null)
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,8 +271,6 @@ public class FingerPrintHelper {
|
||||
public boolean hasEnrolledFingerprints() {
|
||||
// fingerprint hardware supported and api level OK
|
||||
return isFingerprintSupported(fingerprintManager)
|
||||
&& fingerprintManager != null
|
||||
&& fingerprintManager.isHardwareDetected()
|
||||
// fingerprints enrolled
|
||||
&& fingerprintManager.hasEnrolledFingerprints()
|
||||
// and lockscreen configured
|
||||
@@ -269,4 +281,44 @@ public class FingerPrintHelper {
|
||||
this.initOk = initOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entry key in keystore
|
||||
*/
|
||||
public static void deleteEntryKeyInKeystoreForFingerprints(final Context context,
|
||||
final FingerPrintErrorCallback fingerPrintCallback) {
|
||||
FingerPrintHelper fingerPrintHelper = new FingerPrintHelper(
|
||||
context, new FingerPrintCallback() {
|
||||
@Override
|
||||
public void handleEncryptedResult(String value, String ivSpec) {}
|
||||
|
||||
@Override
|
||||
public void handleDecryptedResult(String value) {}
|
||||
|
||||
@Override
|
||||
public void onInvalidKeyException(Exception e) {
|
||||
fingerPrintCallback.onInvalidKeyException(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFingerPrintException(Exception e) {
|
||||
fingerPrintCallback.onFingerPrintException(e);
|
||||
}
|
||||
});
|
||||
fingerPrintHelper.deleteEntryKey();
|
||||
}
|
||||
|
||||
public interface FingerPrintErrorCallback {
|
||||
void onInvalidKeyException(Exception e);
|
||||
void onFingerPrintException(Exception e);
|
||||
}
|
||||
|
||||
public interface FingerPrintCallback extends FingerPrintErrorCallback {
|
||||
void handleEncryptedResult(String value, String ivSpec);
|
||||
void handleDecryptedResult(String value);
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
NOT_CONFIGURED_MODE, STORE_MODE, OPEN_MODE
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -42,7 +42,7 @@ import com.keepassdroid.utils.UriUtil;
|
||||
import com.keepassdroid.view.KeyFileHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class AssignMasterKeyDialog extends DialogFragment {
|
||||
public class AssignMasterKeyDialogFragment extends DialogFragment {
|
||||
|
||||
private String masterPassword;
|
||||
private Uri mKeyfile;
|
||||
@@ -226,7 +226,7 @@ public class AssignMasterKeyDialog extends DialogFragment {
|
||||
mListener.onAssignKeyDialogPositiveClick(
|
||||
passwordCheckBox.isChecked(), masterPassword,
|
||||
keyfileCheckBox.isChecked(), mKeyfile);
|
||||
AssignMasterKeyDialog.this.dismiss();
|
||||
AssignMasterKeyDialogFragment.this.dismiss();
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -244,7 +244,7 @@ public class AssignMasterKeyDialog extends DialogFragment {
|
||||
mListener.onAssignKeyDialogPositiveClick(
|
||||
passwordCheckBox.isChecked(), masterPassword,
|
||||
keyfileCheckBox.isChecked(), mKeyfile);
|
||||
AssignMasterKeyDialog.this.dismiss();
|
||||
AssignMasterKeyDialogFragment.this.dismiss();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
@@ -38,6 +38,7 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
|
||||
import com.keepassdroid.fileselect.FilePickerStylishActivity;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
@@ -45,7 +46,7 @@ import com.nononsenseapps.filepicker.Utils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class CreateFileDialog extends DialogFragment implements AdapterView.OnItemSelectedListener{
|
||||
public class CreateFileDialogFragment extends DialogFragment implements AdapterView.OnItemSelectedListener{
|
||||
|
||||
private final int FILE_CODE = 3853;
|
||||
|
||||
@@ -134,7 +135,7 @@ public class CreateFileDialog extends DialogFragment implements AdapterView.OnIt
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if(mListener.onDefinePathDialogPositiveClick(buildPath()))
|
||||
CreateFileDialog.this.dismiss();
|
||||
CreateFileDialogFragment.this.dismiss();
|
||||
}
|
||||
});
|
||||
Button negativeButton = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_NEGATIVE);
|
||||
@@ -142,7 +143,7 @@ public class CreateFileDialog extends DialogFragment implements AdapterView.OnIt
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
if(mListener.onDefinePathDialogNegativeClick(buildPath()))
|
||||
CreateFileDialog.this.dismiss();
|
||||
CreateFileDialogFragment.this.dismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -36,12 +36,12 @@ import android.widget.SeekBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.password.PasswordGenerator;
|
||||
import com.keepassdroid.settings.PrefsUtil;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class GeneratePasswordFragment extends DialogFragment {
|
||||
public class GeneratePasswordDialogFragment extends DialogFragment {
|
||||
|
||||
public static final String KEY_PASSWORD_ID = "KEY_PASSWORD_ID";
|
||||
|
||||
@@ -102,7 +102,7 @@ public class GeneratePasswordFragment extends DialogFragment {
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||
});
|
||||
seekBar.setProgress(PrefsUtil.getDefaultPasswordLength(getContext().getApplicationContext()));
|
||||
seekBar.setProgress(PreferencesUtil.getDefaultPasswordLength(getContext().getApplicationContext()));
|
||||
|
||||
Button genPassButton = (Button) root.findViewById(R.id.generate_password_button);
|
||||
genPassButton.setOnClickListener(new OnClickListener() {
|
||||
@@ -149,7 +149,7 @@ public class GeneratePasswordFragment extends DialogFragment {
|
||||
bracketsBox.setChecked(false);
|
||||
|
||||
Set<String> defaultPasswordChars =
|
||||
PrefsUtil.getDefaultPasswordCharacters(getContext().getApplicationContext());
|
||||
PreferencesUtil.getDefaultPasswordCharacters(getContext().getApplicationContext());
|
||||
for(String passwordChar : defaultPasswordChars) {
|
||||
if (passwordChar.equals(getString(R.string.value_password_uppercase))) {
|
||||
uppercaseBox.setChecked(true);
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -32,56 +32,78 @@ import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.database.PwNode;
|
||||
import com.keepassdroid.icons.Icons;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class GroupEditFragment extends DialogFragment
|
||||
implements IconPickerFragment.IconPickerListener {
|
||||
public class GroupEditDialogFragment extends DialogFragment
|
||||
implements IconPickerDialogFragment.IconPickerListener {
|
||||
|
||||
public static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
|
||||
|
||||
public static final String KEY_NAME = "name";
|
||||
public static final String KEY_ICON_ID = "icon_id";
|
||||
|
||||
private CreateGroupListener createGroupListener;
|
||||
private EditGroupListener editGroupListener;
|
||||
|
||||
private TextView nameField;
|
||||
private ImageButton iconButton;
|
||||
private int mSelectedIconID;
|
||||
private View root;
|
||||
|
||||
public static GroupEditDialogFragment build(PwNode group) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_NAME, group.getDisplayTitle());
|
||||
// TODO Change
|
||||
bundle.putInt(KEY_ICON_ID, group.getIcon().hashCode());
|
||||
GroupEditDialogFragment fragment = new GroupEditDialogFragment();
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
// Verify that the host activity implements the callback interface
|
||||
try {
|
||||
// Instantiate the NoticeDialogListener so we can send events to the host
|
||||
createGroupListener = (CreateGroupListener) context;
|
||||
createGroupListener = (CreateGroupListener) context;
|
||||
editGroupListener = (EditGroupListener) context;
|
||||
} catch (ClassCastException e) {
|
||||
// The activity doesn't implement the interface, throw exception
|
||||
throw new ClassCastException(context.toString()
|
||||
+ " must implement " + GroupEditFragment.class.getName());
|
||||
+ " must implement " + GroupEditDialogFragment.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
root = inflater.inflate(R.layout.group_edit, null);
|
||||
nameField = (TextView) root.findViewById(R.id.group_name);
|
||||
iconButton = (ImageButton) root.findViewById(R.id.icon_button);
|
||||
|
||||
if (getArguments() != null
|
||||
&& getArguments().containsKey(KEY_NAME)
|
||||
&& getArguments().containsKey(KEY_ICON_ID)) {
|
||||
nameField.setText(getArguments().getString(KEY_NAME));
|
||||
populateIcon(getArguments().getInt(KEY_ICON_ID));
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(root)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
TextView nameField = (TextView) root.findViewById(R.id.group_name);
|
||||
String name = nameField.getText().toString();
|
||||
|
||||
if ( name.length() > 0 ) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(KEY_NAME, name);
|
||||
bundle.putInt(KEY_ICON_ID, mSelectedIconID);
|
||||
createGroupListener.approveCreateGroup(bundle);
|
||||
editGroupListener.approveEditGroup(bundle);
|
||||
|
||||
GroupEditFragment.this.getDialog().cancel();
|
||||
GroupEditDialogFragment.this.getDialog().cancel();
|
||||
}
|
||||
else {
|
||||
Toast.makeText(getContext(), R.string.error_no_name, Toast.LENGTH_LONG).show();
|
||||
@@ -91,32 +113,35 @@ public class GroupEditFragment extends DialogFragment
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Bundle bundle = new Bundle();
|
||||
createGroupListener.cancelCreateGroup(bundle);
|
||||
editGroupListener.cancelEditGroup(bundle);
|
||||
|
||||
GroupEditFragment.this.getDialog().cancel();
|
||||
GroupEditDialogFragment.this.getDialog().cancel();
|
||||
}
|
||||
});
|
||||
|
||||
final ImageButton iconButton = (ImageButton) root.findViewById(R.id.icon_button);
|
||||
iconButton.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
IconPickerFragment iconPickerFragment = new IconPickerFragment();
|
||||
iconPickerFragment.show(getFragmentManager(), "IconPickerFragment");
|
||||
IconPickerDialogFragment iconPickerDialogFragment = new IconPickerDialogFragment();
|
||||
iconPickerDialogFragment.show(getFragmentManager(), "IconPickerDialogFragment");
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
mSelectedIconID = bundle.getInt(IconPickerFragment.KEY_ICON_ID);
|
||||
ImageButton currIconButton = (ImageButton) root.findViewById(R.id.icon_button);
|
||||
currIconButton.setImageResource(Icons.iconToResId(mSelectedIconID));
|
||||
private void populateIcon(int iconId) {
|
||||
iconButton.setImageResource(Icons.iconToResId(iconId));
|
||||
}
|
||||
|
||||
public interface CreateGroupListener {
|
||||
void approveCreateGroup(Bundle bundle);
|
||||
void cancelCreateGroup(Bundle bundle);
|
||||
@Override
|
||||
public void iconPicked(Bundle bundle) {
|
||||
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
|
||||
populateIcon(mSelectedIconID);
|
||||
}
|
||||
|
||||
public interface EditGroupListener {
|
||||
void approveEditGroup(Bundle bundle);
|
||||
void cancelEditGroup(Bundle bundle);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
@@ -41,13 +41,13 @@ import com.keepassdroid.icons.Icons;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
|
||||
|
||||
public class IconPickerFragment extends DialogFragment {
|
||||
public class IconPickerDialogFragment extends DialogFragment {
|
||||
public static final String KEY_ICON_ID = "icon_id";
|
||||
private IconPickerListener iconPickerListener;
|
||||
|
||||
public static void Launch(StylishActivity activity) {
|
||||
public static void launch(StylishActivity activity) {
|
||||
// Create an instance of the dialog fragment and show it
|
||||
IconPickerFragment dialog = new IconPickerFragment();
|
||||
IconPickerDialogFragment dialog = new IconPickerDialogFragment();
|
||||
dialog.show(activity.getSupportFragmentManager(), "NoticeDialogFragment");
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public class IconPickerFragment extends DialogFragment {
|
||||
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
IconPickerFragment.this.getDialog().cancel();
|
||||
IconPickerDialogFragment.this.getDialog().cancel();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2018 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.IdRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import com.keepassdroid.database.SortNodeEnum;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class SortDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String SORT_NODE_ENUM_BUNDLE_KEY = "SORT_NODE_ENUM_BUNDLE_KEY";
|
||||
private static final String SORT_ASCENDING_BUNDLE_KEY = "SORT_ASCENDING_BUNDLE_KEY";
|
||||
private static final String SORT_GROUPS_BEFORE_BUNDLE_KEY = "SORT_GROUPS_BEFORE_BUNDLE_KEY";
|
||||
private static final String SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY = "SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY";
|
||||
|
||||
private SortSelectionListener mListener;
|
||||
|
||||
private SortNodeEnum sortNodeEnum;
|
||||
private @IdRes
|
||||
int mCheckedId;
|
||||
private boolean mGroupsBefore;
|
||||
private boolean mAscending;
|
||||
private boolean mRecycleBinBottom;
|
||||
|
||||
private static Bundle buildBundle(SortNodeEnum sortNodeEnum,
|
||||
boolean ascending,
|
||||
boolean groupsBefore) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(SORT_NODE_ENUM_BUNDLE_KEY, sortNodeEnum.name());
|
||||
bundle.putBoolean(SORT_ASCENDING_BUNDLE_KEY, ascending);
|
||||
bundle.putBoolean(SORT_GROUPS_BEFORE_BUNDLE_KEY, groupsBefore);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public static SortDialogFragment getInstance(SortNodeEnum sortNodeEnum,
|
||||
boolean ascending,
|
||||
boolean groupsBefore) {
|
||||
Bundle bundle = buildBundle(sortNodeEnum, ascending, groupsBefore);
|
||||
SortDialogFragment fragment = new SortDialogFragment();
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public static SortDialogFragment getInstance(SortNodeEnum sortNodeEnum,
|
||||
boolean ascending,
|
||||
boolean groupsBefore,
|
||||
boolean recycleBinBottom) {
|
||||
Bundle bundle = buildBundle(sortNodeEnum, ascending, groupsBefore);
|
||||
bundle.putBoolean(SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY, recycleBinBottom);
|
||||
SortDialogFragment fragment = new SortDialogFragment();
|
||||
fragment.setArguments(bundle);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
try {
|
||||
mListener = (SortSelectionListener) context;
|
||||
} catch (ClassCastException e) {
|
||||
throw new ClassCastException(context.toString()
|
||||
+ " must implement " + SortSelectionListener.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
|
||||
sortNodeEnum = SortNodeEnum.TITLE;
|
||||
mAscending = true;
|
||||
mGroupsBefore = true;
|
||||
boolean recycleBinAllowed = false;
|
||||
mRecycleBinBottom = true;
|
||||
|
||||
if (getArguments() != null) {
|
||||
if (getArguments().containsKey(SORT_NODE_ENUM_BUNDLE_KEY))
|
||||
sortNodeEnum = SortNodeEnum.valueOf(getArguments().getString(SORT_NODE_ENUM_BUNDLE_KEY));
|
||||
if (getArguments().containsKey(SORT_ASCENDING_BUNDLE_KEY))
|
||||
mAscending = getArguments().getBoolean(SORT_ASCENDING_BUNDLE_KEY);
|
||||
if (getArguments().containsKey(SORT_GROUPS_BEFORE_BUNDLE_KEY))
|
||||
mGroupsBefore = getArguments().getBoolean(SORT_GROUPS_BEFORE_BUNDLE_KEY);
|
||||
if (getArguments().containsKey(SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY)) {
|
||||
recycleBinAllowed = true;
|
||||
mRecycleBinBottom = getArguments().getBoolean(SORT_RECYCLE_BIN_BOTTOM_BUNDLE_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
mCheckedId = retrieveViewFromEnum(sortNodeEnum);
|
||||
|
||||
View rootView = inflater.inflate(R.layout.sort_selection, null);
|
||||
builder.setTitle(R.string.sort_menu);
|
||||
builder.setView(rootView)
|
||||
// Add action buttons
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
mListener.onSortSelected(sortNodeEnum, mAscending, mGroupsBefore, mRecycleBinBottom);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {}
|
||||
});
|
||||
|
||||
CompoundButton ascendingView = (CompoundButton) rootView.findViewById(R.id.sort_selection_ascending);
|
||||
// Check if is ascending or descending
|
||||
ascendingView.setChecked(mAscending);
|
||||
ascendingView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
mAscending = isChecked;
|
||||
}
|
||||
});
|
||||
|
||||
CompoundButton groupsBeforeView = (CompoundButton) rootView.findViewById(R.id.sort_selection_groups_before);
|
||||
// Check if groups before
|
||||
groupsBeforeView.setChecked(mGroupsBefore);
|
||||
groupsBeforeView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
mGroupsBefore = isChecked;
|
||||
}
|
||||
});
|
||||
|
||||
CompoundButton recycleBinBottomView = (CompoundButton) rootView.findViewById(R.id.sort_selection_recycle_bin_bottom);
|
||||
if (!recycleBinAllowed) {
|
||||
recycleBinBottomView.setVisibility(View.GONE);
|
||||
} else {
|
||||
// Check if recycle bin at the bottom
|
||||
recycleBinBottomView.setChecked(mRecycleBinBottom);
|
||||
recycleBinBottomView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
mRecycleBinBottom = isChecked;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RadioGroup sortSelectionRadioGroupView = (RadioGroup) rootView.findViewById(R.id.sort_selection_radio_group);
|
||||
// Check value by default
|
||||
sortSelectionRadioGroupView.check(mCheckedId);
|
||||
sortSelectionRadioGroupView.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(RadioGroup group, int checkedId) {
|
||||
sortNodeEnum = retrieveSortEnumFromViewId(checkedId);
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
private @IdRes
|
||||
int retrieveViewFromEnum(SortNodeEnum sortNodeEnum) {
|
||||
switch (sortNodeEnum) {
|
||||
case DB:
|
||||
return R.id.sort_selection_db;
|
||||
default:
|
||||
case TITLE:
|
||||
return R.id.sort_selection_title;
|
||||
case USERNAME:
|
||||
return R.id.sort_selection_username;
|
||||
case CREATION_TIME:
|
||||
return R.id.sort_selection_creation_time;
|
||||
case LAST_MODIFY_TIME:
|
||||
return R.id.sort_selection_last_modify_time;
|
||||
case LAST_ACCESS_TIME:
|
||||
return R.id.sort_selection_last_access_time;
|
||||
}
|
||||
}
|
||||
|
||||
private SortNodeEnum retrieveSortEnumFromViewId(@IdRes int checkedId) {
|
||||
SortNodeEnum sortNodeEnum;
|
||||
// Change enum
|
||||
switch (checkedId) {
|
||||
case R.id.sort_selection_db:
|
||||
sortNodeEnum = SortNodeEnum.DB;
|
||||
break;
|
||||
default:
|
||||
case R.id.sort_selection_title:
|
||||
sortNodeEnum = SortNodeEnum.TITLE;
|
||||
break;
|
||||
case R.id.sort_selection_username:
|
||||
sortNodeEnum = SortNodeEnum.USERNAME;
|
||||
break;
|
||||
case R.id.sort_selection_creation_time:
|
||||
sortNodeEnum = SortNodeEnum.CREATION_TIME;
|
||||
break;
|
||||
case R.id.sort_selection_last_modify_time:
|
||||
sortNodeEnum = SortNodeEnum.LAST_MODIFY_TIME;
|
||||
break;
|
||||
case R.id.sort_selection_last_access_time:
|
||||
sortNodeEnum = SortNodeEnum.LAST_ACCESS_TIME;
|
||||
break;
|
||||
}
|
||||
return sortNodeEnum;
|
||||
}
|
||||
|
||||
public interface SortSelectionListener {
|
||||
void onSortSelected(SortNodeEnum sortNodeEnum,
|
||||
boolean ascending,
|
||||
boolean groupsBefore,
|
||||
boolean recycleBinBottom);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.keepassdroid;
|
||||
package com.keepassdroid.fragments;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
@@ -10,13 +10,13 @@ import android.support.v7.app.AlertDialog;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class UnavailableFeatureDialog extends DialogFragment {
|
||||
public class UnavailableFeatureDialogFragment extends DialogFragment {
|
||||
|
||||
private static final String MIN_REQUIRED_VERSION_ARG = "MIN_REQUIRED_VERSION_ARG";
|
||||
private int minVersionRequired = Build.VERSION_CODES.M;
|
||||
|
||||
public static UnavailableFeatureDialog getInstance(int minVersionRequired) {
|
||||
UnavailableFeatureDialog fragment = new UnavailableFeatureDialog();
|
||||
public static UnavailableFeatureDialogFragment getInstance(int minVersionRequired) {
|
||||
UnavailableFeatureDialogFragment fragment = new UnavailableFeatureDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(MIN_REQUIRED_VERSION_ARG, minVersionRequired);
|
||||
fragment.setArguments(args);
|
||||
@@ -30,7 +30,16 @@ public class UnavailableFeatureDialog extends DialogFragment {
|
||||
minVersionRequired = getArguments().getInt(MIN_REQUIRED_VERSION_ARG);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setMessage(getString(R.string.unavailable_feature_text, Build.VERSION.SDK_INT, minVersionRequired))
|
||||
|
||||
String message = getString(R.string.unavailable_feature_text).concat("\n");
|
||||
if(Build.VERSION.SDK_INT < minVersionRequired)
|
||||
message = message.concat(getString(R.string.unavailable_feature_version,
|
||||
Build.VERSION.SDK_INT,
|
||||
minVersionRequired));
|
||||
else
|
||||
message = message.concat(getString(R.string.unavailable_feature_hardware));
|
||||
|
||||
builder.setMessage(message)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) { }
|
||||
});
|
||||
@@ -53,10 +53,11 @@ public class DrawableFactory {
|
||||
|
||||
public void assignDrawableTo(ImageView iv, Resources res, PwIcon icon) {
|
||||
Drawable draw = getIconDrawable(res, icon);
|
||||
if (iv != null && draw != null)
|
||||
iv.setImageDrawable(draw);
|
||||
}
|
||||
|
||||
private Drawable getIconDrawable(Resources res, PwIcon icon) {
|
||||
public Drawable getIconDrawable(Resources res, PwIcon icon) {
|
||||
if (icon instanceof PwIconStandard) {
|
||||
return getIconDrawable(res, (PwIconStandard) icon);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,974 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.keepassdroid.password;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.activities.GroupActivity;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.autofill.AutofillHelper;
|
||||
import com.keepassdroid.compat.BackupManagerCompat;
|
||||
import com.keepassdroid.compat.ClipDataCompat;
|
||||
import com.keepassdroid.compat.EditorCompat;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.edit.LoadDB;
|
||||
import com.keepassdroid.database.edit.OnFinish;
|
||||
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
||||
import com.keepassdroid.fingerprint.FingerPrintAnimatedVector;
|
||||
import com.keepassdroid.fingerprint.FingerPrintHelper;
|
||||
import com.keepassdroid.settings.PreferencesUtil;
|
||||
import com.keepassdroid.stylish.StylishActivity;
|
||||
import com.keepassdroid.tasks.ProgressTask;
|
||||
import com.keepassdroid.utils.EmptyUtils;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.keepassdroid.utils.UriUtil;
|
||||
import com.keepassdroid.view.FingerPrintDialog;
|
||||
import com.keepassdroid.view.KeyFileHelper;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import permissions.dispatcher.NeedsPermission;
|
||||
import permissions.dispatcher.OnNeverAskAgain;
|
||||
import permissions.dispatcher.OnPermissionDenied;
|
||||
import permissions.dispatcher.OnShowRationale;
|
||||
import permissions.dispatcher.PermissionRequest;
|
||||
import permissions.dispatcher.RuntimePermissions;
|
||||
|
||||
import static com.keepassdroid.fingerprint.FingerPrintHelper.Mode.NOT_CONFIGURED_MODE;
|
||||
import static com.keepassdroid.fingerprint.FingerPrintHelper.Mode.OPEN_MODE;
|
||||
import static com.keepassdroid.fingerprint.FingerPrintHelper.Mode.STORE_MODE;
|
||||
|
||||
@RuntimePermissions
|
||||
public class PasswordActivity extends StylishActivity
|
||||
implements FingerPrintHelper.FingerPrintCallback, UriIntentInitTaskCallback {
|
||||
|
||||
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
||||
public static final int RESULT_EXIT_LOCK = 1450;
|
||||
|
||||
private static final String KEY_PASSWORD = "password";
|
||||
private static final String KEY_LAUNCH_IMMEDIATELY = "launchImmediately";
|
||||
|
||||
private Uri mDbUri = null;
|
||||
SharedPreferences prefs;
|
||||
SharedPreferences prefsNoBackup;
|
||||
|
||||
private FingerPrintHelper fingerPrintHelper;
|
||||
private boolean fingerprintMustBeConfigured = true;
|
||||
private boolean mRememberKeyfile;
|
||||
|
||||
private FingerPrintHelper.Mode fingerPrintMode;
|
||||
private static final String PREF_KEY_VALUE_PREFIX = "valueFor_"; // key is a combination of db file name and this prefix
|
||||
private static final String PREF_KEY_IV_PREFIX = "ivFor_"; // key is a combination of db file name and this prefix
|
||||
|
||||
private View fingerprintContainerView;
|
||||
private FingerPrintAnimatedVector fingerPrintAnimatedVector;
|
||||
private TextView fingerprintTextView;
|
||||
private TextView filenameView;
|
||||
private EditText passwordView;
|
||||
private EditText keyFileView;
|
||||
private Button confirmButtonView;
|
||||
private CompoundButton checkboxPasswordView;
|
||||
private CompoundButton checkboxKeyfileView;
|
||||
private CompoundButton checkboxDefaultDatabaseView;
|
||||
|
||||
private DefaultCheckChange defaultCheckChange;
|
||||
private ValidateButtonViewClickListener validateButtonViewClickListener;
|
||||
|
||||
private KeyFileHelper keyFileHelper;
|
||||
|
||||
private AutofillHelper autofillHelper;
|
||||
|
||||
public static void launch(
|
||||
Activity act,
|
||||
String fileName) throws FileNotFoundException {
|
||||
launch(act, fileName, "");
|
||||
}
|
||||
|
||||
public static void launch(
|
||||
Activity act,
|
||||
String fileName,
|
||||
String keyFile) throws FileNotFoundException {
|
||||
verifyFileNameUriFromLaunch(fileName);
|
||||
|
||||
Intent intent = new Intent(act, PasswordActivity.class);
|
||||
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
|
||||
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
|
||||
// only to avoid visible flickering when redirecting
|
||||
act.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void launch(
|
||||
Activity act,
|
||||
String fileName,
|
||||
AssistStructure assistStructure) throws FileNotFoundException {
|
||||
launch(act, fileName, "", assistStructure);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public static void launch(
|
||||
Activity act,
|
||||
String fileName,
|
||||
String keyFile,
|
||||
AssistStructure assistStructure) throws FileNotFoundException {
|
||||
verifyFileNameUriFromLaunch(fileName);
|
||||
|
||||
if ( assistStructure != null ) {
|
||||
Intent intent = new Intent(act, PasswordActivity.class);
|
||||
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
|
||||
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
|
||||
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
|
||||
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
|
||||
} else {
|
||||
launch(act, fileName, keyFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyFileNameUriFromLaunch(String fileName) throws FileNotFoundException {
|
||||
if (EmptyUtils.isNullOrEmpty(fileName)) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
Uri uri = UriUtil.parseDefaultFile(fileName);
|
||||
assert uri != null;
|
||||
String scheme = uri.getScheme();
|
||||
|
||||
if (!EmptyUtils.isNullOrEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
|
||||
File dbFile = new File(uri.getPath());
|
||||
if (!dbFile.exists()) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(
|
||||
int requestCode,
|
||||
int resultCode,
|
||||
Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
boolean keyFileResult = false;
|
||||
if (keyFileHelper != null) {
|
||||
keyFileResult = keyFileHelper.onActivityResultCallback(requestCode, resultCode, data,
|
||||
new KeyFileHelper.KeyFileCallback() {
|
||||
@Override
|
||||
public void onKeyFileResultCallback(Uri uri) {
|
||||
if (uri != null) {
|
||||
populateKeyFileTextView(uri.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!keyFileResult) {
|
||||
// this block if not a key file response
|
||||
switch (resultCode) {
|
||||
case RESULT_EXIT_LOCK:
|
||||
case Activity.RESULT_CANCELED:
|
||||
setEmptyViews();
|
||||
App.getDB().clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
prefsNoBackup = PreferencesUtil.getNoBackupSharedPreferences(getApplicationContext());
|
||||
|
||||
mRememberKeyfile = prefs.getBoolean(getString(R.string.keyfile_key),
|
||||
getResources().getBoolean(R.bool.keyfile_default));
|
||||
|
||||
setContentView(R.layout.password);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar.setTitle(getString(R.string.app_name));
|
||||
setSupportActionBar(toolbar);
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
confirmButtonView = (Button) findViewById(R.id.pass_ok);
|
||||
filenameView = (TextView) findViewById(R.id.filename);
|
||||
passwordView = (EditText) findViewById(R.id.password);
|
||||
keyFileView = (EditText) findViewById(R.id.pass_keyfile);
|
||||
checkboxPasswordView = (CompoundButton) findViewById(R.id.password_checkbox);
|
||||
checkboxKeyfileView = (CompoundButton) findViewById(R.id.keyfile_checkox);
|
||||
checkboxDefaultDatabaseView = (CompoundButton) findViewById(R.id.default_database);
|
||||
|
||||
View browseView = findViewById(R.id.browse_button);
|
||||
keyFileHelper = new KeyFileHelper(PasswordActivity.this);
|
||||
browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener());
|
||||
|
||||
passwordView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if (!editable.toString().isEmpty() && !checkboxKeyfileView.isChecked())
|
||||
checkboxPasswordView.setChecked(true);
|
||||
}
|
||||
});
|
||||
keyFileView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if (!editable.toString().isEmpty() && !checkboxKeyfileView.isChecked())
|
||||
checkboxKeyfileView.setChecked(true);
|
||||
}
|
||||
});
|
||||
|
||||
defaultCheckChange = new DefaultCheckChange();
|
||||
validateButtonViewClickListener = new ValidateButtonViewClickListener();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerprintContainerView = findViewById(R.id.fingerprint_container);
|
||||
fingerprintTextView = (TextView) findViewById(R.id.fingerprint_label);
|
||||
initForFingerprint();
|
||||
fingerPrintAnimatedVector = new FingerPrintAnimatedVector(this,
|
||||
(ImageView) findViewById(R.id.fingerprint_image));
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
autofillHelper.retrieveAssistStructure(getIntent());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
// If the application was shutdown make sure to clear the password field, if it
|
||||
// was saved in the instance state
|
||||
if (App.isShutdown()) {
|
||||
setEmptyViews();
|
||||
}
|
||||
|
||||
// Clear the shutdown flag
|
||||
App.clearShutdown();
|
||||
|
||||
// For check shutdown
|
||||
super.onResume();
|
||||
|
||||
new UriIntentInitTask(this, mRememberKeyfile)
|
||||
.execute(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPostInitTask(Uri dbUri, Uri keyFileUri, Integer errorStringId) {
|
||||
mDbUri = dbUri;
|
||||
|
||||
if (errorStringId != null) {
|
||||
Toast.makeText(PasswordActivity.this, errorStringId, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify permission to read file
|
||||
if (mDbUri != null
|
||||
&& !dbUri.getScheme().contains("content"))
|
||||
PasswordActivityPermissionsDispatcher
|
||||
.doNothingWithPermissionCheck(this);
|
||||
|
||||
// Define title
|
||||
String dbUriString = (mDbUri == null) ? "" : mDbUri.toString();
|
||||
if (!dbUriString.isEmpty()) {
|
||||
if (PreferencesUtil.isFullFilePathEnable(this))
|
||||
filenameView.setText(dbUriString);
|
||||
else
|
||||
filenameView.setText(new File(mDbUri.getPath()).getName()); // TODO Encapsulate
|
||||
}
|
||||
|
||||
// Define Key File text
|
||||
String keyUriString = (keyFileUri == null) ? "" : keyFileUri.toString();
|
||||
if (!keyUriString.isEmpty() && mRememberKeyfile) { // Bug KeepassDX #18
|
||||
populateKeyFileTextView(keyUriString);
|
||||
}
|
||||
|
||||
// Define listeners for default database checkbox and validate button
|
||||
checkboxDefaultDatabaseView.setOnCheckedChangeListener(defaultCheckChange);
|
||||
confirmButtonView.setOnClickListener(validateButtonViewClickListener);
|
||||
|
||||
// Retrieve settings for default database
|
||||
String defaultFilename = prefs.getString(KEY_DEFAULT_FILENAME, "");
|
||||
if (mDbUri!=null
|
||||
&& !EmptyUtils.isNullOrEmpty(mDbUri.getPath())
|
||||
&& UriUtil.equalsDefaultfile(mDbUri, defaultFilename)) {
|
||||
checkboxDefaultDatabaseView.setChecked(true);
|
||||
}
|
||||
|
||||
// checks if fingerprint is available, will also start listening for fingerprints when available
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
checkFingerprintAvailability();
|
||||
if (fingerPrintAnimatedVector != null) {
|
||||
fingerPrintAnimatedVector.startScan();
|
||||
}
|
||||
}
|
||||
|
||||
// If Activity is launch with a password and want to open directly
|
||||
Intent intent = getIntent();
|
||||
String password = intent.getStringExtra(KEY_PASSWORD);
|
||||
boolean launch_immediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false);
|
||||
if (password != null) {
|
||||
populatePasswordTextView(password);
|
||||
}
|
||||
if (launch_immediately) {
|
||||
verifyCheckboxesAndLoadDatabase(password, keyFileUri);
|
||||
}
|
||||
}
|
||||
|
||||
private void setEmptyViews() {
|
||||
populatePasswordTextView(null);
|
||||
// Bug KeepassDX #18
|
||||
if (!mRememberKeyfile) {
|
||||
populateKeyFileTextView(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void populatePasswordTextView(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
passwordView.setText("");
|
||||
if (checkboxPasswordView.isChecked())
|
||||
checkboxPasswordView.setChecked(false);
|
||||
} else {
|
||||
passwordView.setText(text);
|
||||
if (!checkboxPasswordView.isChecked())
|
||||
checkboxPasswordView.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateKeyFileTextView(String text) {
|
||||
if (text == null || text.isEmpty()) {
|
||||
keyFileView.setText("");
|
||||
if (checkboxKeyfileView.isChecked())
|
||||
checkboxKeyfileView.setChecked(false);
|
||||
} else {
|
||||
keyFileView.setText(text);
|
||||
if (!checkboxKeyfileView.isChecked())
|
||||
checkboxKeyfileView.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
// fingerprint related code here
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void initForFingerprint() {
|
||||
fingerPrintMode = NOT_CONFIGURED_MODE;
|
||||
|
||||
fingerPrintHelper = new FingerPrintHelper(this, this);
|
||||
|
||||
// when text entered we can enable the logon/purchase button and if required update encryption/decryption mode
|
||||
passwordView.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(
|
||||
final CharSequence s,
|
||||
final int start,
|
||||
final int count,
|
||||
final int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(
|
||||
final CharSequence s,
|
||||
final int start,
|
||||
final int before,
|
||||
final int count) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(final Editable s) {
|
||||
if ( !fingerprintMustBeConfigured ) {
|
||||
final boolean validInput = s.length() > 0;
|
||||
// encrypt or decrypt mode based on how much input or not
|
||||
setFingerPrintTextView(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
|
||||
if (validInput)
|
||||
toggleFingerprintMode(STORE_MODE);
|
||||
else
|
||||
toggleFingerprintMode(OPEN_MODE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// callback for fingerprint findings
|
||||
fingerPrintHelper.setAuthenticationCallback(new FingerprintManagerCompat.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(
|
||||
final int errorCode,
|
||||
final CharSequence errString) {
|
||||
Log.i(getClass().getName(), errString.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(
|
||||
final int helpCode,
|
||||
final CharSequence helpString) {
|
||||
showError(helpString);
|
||||
reInitWithSameFingerprintMode();
|
||||
fingerprintTextView.setText(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
showError(R.string.fingerprint_not_recognized);
|
||||
reInitWithSameFingerprintMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(final FingerprintManagerCompat.AuthenticationResult result) {
|
||||
switch (fingerPrintMode) {
|
||||
case STORE_MODE:
|
||||
// newly store the entered password in encrypted way
|
||||
final String password = passwordView.getText().toString();
|
||||
fingerPrintHelper.encryptData(password);
|
||||
break;
|
||||
case OPEN_MODE:
|
||||
// retrieve the encrypted value from preferences
|
||||
final String encryptedValue = prefsNoBackup.getString(getPreferenceKeyValue(), null);
|
||||
if (encryptedValue != null) {
|
||||
fingerPrintHelper.decryptData(encryptedValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getPreferenceKeyValue() {
|
||||
// makes it possible to store passwords uniqly per database
|
||||
return PREF_KEY_VALUE_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||
}
|
||||
|
||||
private String getPreferenceKeyIvSpec() {
|
||||
return PREF_KEY_IV_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void initEncryptData() {
|
||||
setFingerPrintTextView(R.string.store_with_fingerprint);
|
||||
fingerPrintMode = STORE_MODE;
|
||||
if (fingerPrintHelper != null)
|
||||
fingerPrintHelper.initEncryptData();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void initDecryptData() {
|
||||
setFingerPrintTextView(R.string.scanning_fingerprint);
|
||||
fingerPrintMode = OPEN_MODE;
|
||||
if (fingerPrintHelper != null) {
|
||||
final String ivSpecValue = prefsNoBackup.getString(getPreferenceKeyIvSpec(), null);
|
||||
if (ivSpecValue != null)
|
||||
fingerPrintHelper.initDecryptData(ivSpecValue);
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private synchronized void toggleFingerprintMode(final FingerPrintHelper.Mode newMode) {
|
||||
if( !newMode.equals(fingerPrintMode) ) {
|
||||
fingerPrintMode = newMode;
|
||||
reInitWithSameFingerprintMode();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private synchronized void reInitWithSameFingerprintMode() {
|
||||
switch (fingerPrintMode) {
|
||||
case STORE_MODE:
|
||||
initEncryptData();
|
||||
break;
|
||||
case OPEN_MODE:
|
||||
initDecryptData();
|
||||
break;
|
||||
}
|
||||
// Show fingerprint key deletion
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (fingerPrintAnimatedVector != null) {
|
||||
fingerPrintAnimatedVector.stopScan();
|
||||
}
|
||||
// stop listening when we go in background
|
||||
fingerPrintMode = NOT_CONFIGURED_MODE;
|
||||
if (fingerPrintHelper != null) {
|
||||
fingerPrintHelper.stopListening();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setFingerPrintVisibility(final int vis) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fingerprintContainerView.setVisibility(vis);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setFingerPrintTextView(final int textId) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fingerprintTextView.setText(textId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setFingerPrintAlphaImageView(final float alpha) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
fingerprintContainerView.setAlpha(alpha);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private synchronized void checkFingerprintAvailability() {
|
||||
// fingerprint not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
if (!PreferencesUtil .isFingerprintEnable(getApplicationContext())
|
||||
|| !FingerPrintHelper.isFingerprintSupported(FingerprintManagerCompat.from(this))) {
|
||||
setFingerPrintVisibility(View.GONE);
|
||||
}
|
||||
// fingerprint is available but not configured show icon but in disabled state with some information
|
||||
else {
|
||||
// show explanations
|
||||
fingerprintContainerView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
FingerPrintDialog fingerPrintDialog = new FingerPrintDialog();
|
||||
fingerPrintDialog.show(getSupportFragmentManager(), "fingerprintDialog");
|
||||
}
|
||||
});
|
||||
setFingerPrintVisibility(View.VISIBLE);
|
||||
|
||||
if (!fingerPrintHelper.hasEnrolledFingerprints()) {
|
||||
setFingerPrintAlphaImageView(0.6f);
|
||||
// This happens when no fingerprints are registered. Listening won't start
|
||||
setFingerPrintTextView(R.string.configure_fingerprint);
|
||||
}
|
||||
// finally fingerprint available and configured so we can use it
|
||||
else {
|
||||
fingerprintMustBeConfigured = false;
|
||||
setFingerPrintAlphaImageView(1f);
|
||||
|
||||
// fingerprint available but no stored password found yet for this DB so show info don't listen
|
||||
if (!prefsNoBackup.contains(getPreferenceKeyValue())) {
|
||||
setFingerPrintTextView(R.string.no_password_stored);
|
||||
// listen for encryption
|
||||
initEncryptData();
|
||||
}
|
||||
// all is set here so we can confirm to user and start listening for fingerprints
|
||||
else {
|
||||
// listen for decryption
|
||||
initDecryptData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show fingerprint key deletion
|
||||
invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
private void removePrefsNoBackupKeys() {
|
||||
prefsNoBackup.edit()
|
||||
.remove(getPreferenceKeyValue())
|
||||
.remove(getPreferenceKeyIvSpec())
|
||||
.apply();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEncryptedResult(
|
||||
final String value,
|
||||
final String ivSpec) {
|
||||
prefsNoBackup.edit()
|
||||
.putString(getPreferenceKeyValue(), value)
|
||||
.putString(getPreferenceKeyIvSpec(), ivSpec)
|
||||
.apply();
|
||||
verifyAllViewsAndLoadDatabase();
|
||||
setFingerPrintTextView(R.string.encrypted_value_stored);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDecryptedResult(final String passwordValue) {
|
||||
// Load database directly
|
||||
verifyKeyFileViewsAndLoadDatabase(passwordValue);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onInvalidKeyException(Exception e) {
|
||||
showError(R.string.fingerprint_invalid_key);
|
||||
removePrefsNoBackupKeys();
|
||||
e.printStackTrace();
|
||||
reInitWithSameFingerprintMode(); // restarts listening
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onFingerPrintException(Exception e) {
|
||||
showError(R.string.fingerprint_error);
|
||||
e.printStackTrace();
|
||||
reInitWithSameFingerprintMode();
|
||||
}
|
||||
|
||||
private void showError(final int messageId) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), messageId, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showError(final CharSequence message) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
|
||||
@Override
|
||||
public void onCheckedChanged(
|
||||
CompoundButton buttonView,
|
||||
boolean isChecked) {
|
||||
|
||||
String newDefaultFileName = "";
|
||||
if (isChecked) {
|
||||
newDefaultFileName = mDbUri.toString();
|
||||
}
|
||||
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString(KEY_DEFAULT_FILENAME, newDefaultFileName);
|
||||
EditorCompat.apply(editor);
|
||||
|
||||
BackupManagerCompat backupManager = new BackupManagerCompat(PasswordActivity.this);
|
||||
backupManager.dataChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private class ValidateButtonViewClickListener implements View.OnClickListener {
|
||||
public void onClick(View view) {
|
||||
verifyAllViewsAndLoadDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyAllViewsAndLoadDatabase() {
|
||||
String pass = passwordView.getText().toString();
|
||||
String keyfile = keyFileView.getText().toString();
|
||||
verifyCheckboxesAndLoadDatabase(pass, UriUtil.parseDefaultFile(keyfile));
|
||||
}
|
||||
|
||||
private void verifyCheckboxesAndLoadDatabase(String pass, Uri keyfile) {
|
||||
if (!checkboxPasswordView.isChecked()) {
|
||||
pass = "";
|
||||
}
|
||||
if (!checkboxKeyfileView.isChecked()) {
|
||||
keyfile = null;
|
||||
}
|
||||
loadDatabase(pass, keyfile);
|
||||
}
|
||||
|
||||
private void verifyKeyFileViewsAndLoadDatabase(String password) {
|
||||
String key = keyFileView.getText().toString();
|
||||
Uri keyUri = UriUtil.parseDefaultFile(key);
|
||||
if (!checkboxKeyfileView.isChecked()) {
|
||||
keyUri = null;
|
||||
}
|
||||
loadDatabase(password, keyUri);
|
||||
}
|
||||
|
||||
private void loadDatabase(String pass, Uri keyfile) {
|
||||
// Clear before we load
|
||||
Database db = App.getDB();
|
||||
db.clear();
|
||||
|
||||
// Clear the shutdown flag
|
||||
App.clearShutdown();
|
||||
|
||||
Handler handler = new Handler();
|
||||
AfterLoad afterLoad = new AfterLoad(handler, db);
|
||||
|
||||
LoadDB task = new LoadDB(db, PasswordActivity.this, mDbUri, pass, keyfile, afterLoad);
|
||||
ProgressTask pt = new ProgressTask(PasswordActivity.this, task, R.string.loading_database);
|
||||
pt.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.defaultMenuInflater(inflater, menu);
|
||||
if (!fingerprintMustBeConfigured
|
||||
&& prefsNoBackup.contains(getPreferenceKeyValue()) )
|
||||
inflater.inflate(R.menu.fingerprint, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
case R.id.menu_fingerprint_remove_key:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
fingerPrintHelper.deleteEntryKey();
|
||||
removePrefsNoBackupKeys();
|
||||
fingerPrintMode = NOT_CONFIGURED_MODE;
|
||||
checkFingerprintAvailability();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return MenuUtil.onDefaultMenuOptionsItemSelected(this, item);
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
// NOTE: delegate the permission handling to generated method
|
||||
PasswordActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after verify and try to opening the database
|
||||
*/
|
||||
private final class AfterLoad extends OnFinish {
|
||||
|
||||
protected Database db;
|
||||
|
||||
AfterLoad(
|
||||
Handler handler,
|
||||
Database db) {
|
||||
super(handler);
|
||||
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// Recheck fingerprint if error
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
//setEmptyViews();
|
||||
//mMessage = getString(R.string.fingerprint_error) + " : " + mMessage;
|
||||
// TODO Change fingerprint message
|
||||
// Stay with the same mode
|
||||
reInitWithSameFingerprintMode();
|
||||
}
|
||||
|
||||
if (db.passwordEncodingError) {
|
||||
PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper();
|
||||
dialog.show(PasswordActivity.this, new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(
|
||||
DialogInterface dialog,
|
||||
int which) {
|
||||
launchGroupActivity();
|
||||
}
|
||||
|
||||
});
|
||||
} else if (mSuccess) {
|
||||
launchGroupActivity();
|
||||
} else {
|
||||
displayMessage(PasswordActivity.this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void launchGroupActivity() {
|
||||
AssistStructure assistStructure = null;
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
GroupActivity.launch(PasswordActivity.this, assistStructure);
|
||||
}
|
||||
}
|
||||
if (assistStructure == null) {
|
||||
GroupActivity.launch(PasswordActivity.this);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UriIntentInitTask extends AsyncTask<Intent, Void, Integer> {
|
||||
|
||||
static final String KEY_FILENAME = "fileName";
|
||||
static final String KEY_KEYFILE = "keyFile";
|
||||
private static final String VIEW_INTENT = "android.intent.action.VIEW";
|
||||
|
||||
private UriIntentInitTaskCallback uriIntentInitTaskCallback;
|
||||
private boolean isKeyFileNeeded;
|
||||
private Uri databaseUri;
|
||||
private Uri keyFileUri;
|
||||
|
||||
UriIntentInitTask(UriIntentInitTaskCallback uriIntentInitTaskCallback, boolean isKeyFileNeeded) {
|
||||
this.uriIntentInitTaskCallback = uriIntentInitTaskCallback;
|
||||
this.isKeyFileNeeded = isKeyFileNeeded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer doInBackground(Intent... args) {
|
||||
Intent intent = args[0];
|
||||
String action = intent.getAction();
|
||||
if (action != null && action.equals(VIEW_INTENT)) {
|
||||
Uri incoming = intent.getData();
|
||||
databaseUri = incoming;
|
||||
keyFileUri = ClipDataCompat.getUriFromIntent(intent, KEY_KEYFILE);
|
||||
|
||||
if (incoming == null) {
|
||||
return R.string.error_can_not_handle_uri;
|
||||
} else if (incoming.getScheme().equals("file")) {
|
||||
String fileName = incoming.getPath();
|
||||
|
||||
if (fileName.length() == 0) {
|
||||
// No file name
|
||||
return R.string.file_not_found;
|
||||
}
|
||||
|
||||
File dbFile = new File(fileName);
|
||||
if (!dbFile.exists()) {
|
||||
// File does not exist
|
||||
return R.string.file_not_found;
|
||||
}
|
||||
|
||||
if (keyFileUri == null) {
|
||||
keyFileUri = getKeyFile(databaseUri);
|
||||
}
|
||||
} else if (incoming.getScheme().equals("content")) {
|
||||
if (keyFileUri == null) {
|
||||
keyFileUri = getKeyFile(databaseUri);
|
||||
}
|
||||
} else {
|
||||
return R.string.error_can_not_handle_uri;
|
||||
}
|
||||
|
||||
} else {
|
||||
databaseUri = UriUtil.parseDefaultFile(intent.getStringExtra(KEY_FILENAME));
|
||||
keyFileUri = UriUtil.parseDefaultFile(intent.getStringExtra(KEY_KEYFILE));
|
||||
|
||||
if (keyFileUri == null || keyFileUri.toString().length() == 0) {
|
||||
keyFileUri = getKeyFile(databaseUri);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void onPostExecute(Integer result) {
|
||||
uriIntentInitTaskCallback.onPostInitTask(databaseUri, keyFileUri, result);
|
||||
}
|
||||
|
||||
private Uri getKeyFile(Uri dbUri) {
|
||||
if (isKeyFileNeeded) {
|
||||
return App.getFileHistory().getFileByName(dbUri);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
public void doNothing() {}
|
||||
|
||||
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
void showRationaleForExternalStorage(final PermissionRequest request) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.permission_external_storage_rationale_read_database)
|
||||
.setPositiveButton(R.string.allow, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.proceed();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
request.cancel();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
void showDeniedForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_denied, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
void showNeverAskForExternalStorage() {
|
||||
Toast.makeText(this, R.string.permission_external_storage_never_ask, Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.keepassdroid.password;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
interface UriIntentInitTaskCallback {
|
||||
void onPostInitTask(Uri dbUri, Uri keyFileUri, Integer errorStringId);
|
||||
}
|
||||
@@ -19,20 +19,13 @@
|
||||
*/
|
||||
package com.keepassdroid.search;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwDatabase;
|
||||
import com.keepassdroid.database.PwDatabaseV3;
|
||||
import com.keepassdroid.database.PwDatabaseV4;
|
||||
@@ -41,6 +34,13 @@ import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.database.PwGroupV3;
|
||||
import com.keepassdroid.database.PwGroupV4;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
|
||||
public class SearchDbHelper {
|
||||
private final Context mCtx;
|
||||
|
||||
|
||||
@@ -22,44 +22,28 @@ package com.keepassdroid.search;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
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.KeePass;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.GroupBaseActivity;
|
||||
import com.keepassdroid.PwGroupListAdapter;
|
||||
import com.keepassdroid.activities.ListNodesActivity;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.database.PwGroup;
|
||||
import com.keepassdroid.utils.MenuUtil;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class SearchResultsActivity extends GroupBaseActivity {
|
||||
public class SearchResultsActivity extends ListNodesActivity {
|
||||
|
||||
private Database mDb;
|
||||
|
||||
private View listView;
|
||||
private View imageNotFoundView;
|
||||
private RecyclerView listView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if ( isFinishing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setResult(KeePass.EXIT_NORMAL);
|
||||
|
||||
mDb = App.getDB();
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! mDb.Loaded() ) {
|
||||
finish();
|
||||
}
|
||||
|
||||
setContentView(getLayoutInflater().inflate(R.layout.search_results, null));
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
@@ -69,11 +53,33 @@ public class SearchResultsActivity extends GroupBaseActivity {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
listView = findViewById(R.id.group_list);
|
||||
imageNotFoundView = findViewById(R.id.img_not_found);
|
||||
listView = (RecyclerView) findViewById(R.id.nodes_list);
|
||||
View notFoundView = findViewById(R.id.not_found_container);
|
||||
|
||||
performSearch(getSearchStr(getIntent()));
|
||||
if ( mCurrentGroup == null || mCurrentGroup.numbersOfChildEntries() < 1 ) {
|
||||
listView.setVisibility(View.GONE);
|
||||
notFoundView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
listView.setVisibility(View.VISIBLE);
|
||||
notFoundView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setGroupTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PwGroup initCurrentGroup() {
|
||||
Database mDb = App.getDB();
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! mDb.Loaded() ) {
|
||||
finish();
|
||||
}
|
||||
return mDb.Search(getSearchStr(getIntent()).trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecyclerView defineNodeList() {
|
||||
return listView;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,26 +98,9 @@ public class SearchResultsActivity extends GroupBaseActivity {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void performSearch(String query) {
|
||||
mGroup = mDb.Search(query.trim());
|
||||
|
||||
if ( mGroup == null || mGroup.childEntries.size() < 1 ) {
|
||||
listView.setVisibility(View.GONE);
|
||||
imageNotFoundView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
listView.setVisibility(View.VISIBLE);
|
||||
imageNotFoundView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setGroupTitle();
|
||||
|
||||
setListAdapter(new PwGroupListAdapter(this, mGroup));
|
||||
}
|
||||
|
||||
private String getSearchStr(Intent queryIntent) {
|
||||
// get and process search query here
|
||||
final String queryAction = queryIntent.getAction();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.keepassdroid.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class MainPreferenceFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener {
|
||||
@@ -34,9 +34,11 @@ public class MainPreferenceFragment extends PreferenceFragmentCompat implements
|
||||
Preference preference = findPreference(getString(R.string.app_key));
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
|
||||
preference = findPreference(getString(R.string.db_key));
|
||||
preference = findPreference(getString(R.string.settings_form_filling_key));
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
|
||||
preference = findPreference(getString(R.string.db_key));
|
||||
preference.setOnPreferenceClickListener(this);
|
||||
Database db = App.getDB();
|
||||
if (!(db.Loaded())) {
|
||||
preference.setEnabled(false);
|
||||
@@ -64,6 +66,10 @@ public class MainPreferenceFragment extends PreferenceFragmentCompat implements
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.NESTED_SCREEN_APP_KEY);
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(getString(R.string.settings_form_filling_key))) {
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.NESTED_SCREEN_FORM_FILLING_KEY);
|
||||
}
|
||||
|
||||
if (preference.getKey().equals(getString(R.string.db_key))) {
|
||||
mCallback.onNestedPreferenceSelected(NestedSettingsFragment.NESTED_SCREEN_DB_KEY);
|
||||
}
|
||||
|
||||
@@ -19,30 +19,44 @@
|
||||
*/
|
||||
package com.keepassdroid.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.util.Log;
|
||||
import android.view.autofill.AutofillManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keepassdroid.UnavailableFeatureDialog;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.keepassdroid.Database;
|
||||
import com.keepassdroid.database.Database;
|
||||
import com.keepassdroid.fragments.UnavailableFeatureDialogFragment;
|
||||
import com.keepassdroid.app.App;
|
||||
import com.keepassdroid.database.PwEncryptionAlgorithm;
|
||||
import com.keepassdroid.fingerprint.FingerPrintHelper;
|
||||
import com.keepassdroid.stylish.Stylish;
|
||||
import com.kunzisoft.keepass.R;
|
||||
|
||||
public class NestedSettingsFragment extends PreferenceFragmentCompat {
|
||||
public class NestedSettingsFragment extends PreferenceFragmentCompat
|
||||
implements Preference.OnPreferenceClickListener {
|
||||
|
||||
public static final int NESTED_SCREEN_APP_KEY = 1;
|
||||
public static final int NESTED_SCREEN_DB_KEY = 2;
|
||||
public static final int NESTED_SCREEN_FORM_FILLING_KEY = 2;
|
||||
public static final int NESTED_SCREEN_DB_KEY = 3;
|
||||
|
||||
private static final String TAG_KEY = "NESTED_KEY";
|
||||
|
||||
private static final int REQUEST_CODE_AUTOFILL = 5201;
|
||||
|
||||
public static NestedSettingsFragment newInstance(int key) {
|
||||
NestedSettingsFragment fragment = new NestedSettingsFragment();
|
||||
// supply arguments to bundle.
|
||||
@@ -52,6 +66,23 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
SwitchPreference autoFillEnablePreference =
|
||||
(SwitchPreference) findPreference(getString(R.string.settings_autofill_enable_key));
|
||||
if (autoFillEnablePreference != null) {
|
||||
AutofillManager autofillManager = getActivity().getSystemService(AutofillManager.class);
|
||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices())
|
||||
autoFillEnablePreference.setChecked(true);
|
||||
else
|
||||
autoFillEnablePreference.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
int key = getArguments().getInt(TAG_KEY);
|
||||
@@ -61,78 +92,172 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat {
|
||||
setPreferencesFromResource(R.xml.app_preferences, rootKey);
|
||||
|
||||
Preference keyFile = findPreference(getString(R.string.keyfile_key));
|
||||
keyFile.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
keyFile.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
Boolean value = (Boolean) newValue;
|
||||
|
||||
if (!value) {
|
||||
App.getFileHistory().deleteAllKeys();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Preference recentHistory = findPreference(getString(R.string.recentfile_key));
|
||||
recentHistory.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
recentHistory.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
Boolean value = (Boolean) newValue;
|
||||
|
||||
if (value == null) {
|
||||
value = true;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
App.getFileHistory().deleteAll();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Preference stylePreference = findPreference(getString(R.string.setting_style_key));
|
||||
stylePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
stylePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String styleString = (String) newValue;
|
||||
Stylish.assignStyle(getActivity(), styleString);
|
||||
getActivity().recreate();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
SwitchPreference fingerprintEnablePreference =
|
||||
(SwitchPreference) findPreference(getString(R.string.fingerprint_enable_key));
|
||||
// < M solve verifyError exception
|
||||
boolean fingerprintSupported = false;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
|
||||
fingerprintSupported = FingerPrintHelper.isFingerprintSupported(
|
||||
FingerprintManagerCompat.from(getContext()));
|
||||
if (!fingerprintSupported) {
|
||||
// False if under Marshmallow
|
||||
SwitchPreference preference = (SwitchPreference) findPreference(getString(R.string.fingerprint_enable_key));
|
||||
preference.setDefaultValue(false);
|
||||
preference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
fingerprintEnablePreference.setChecked(false);
|
||||
fingerprintEnablePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
assert fragmentManager != null;
|
||||
((SwitchPreference) preference).setChecked(false);
|
||||
UnavailableFeatureDialog.getInstance(Build.VERSION_CODES.M)
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M)
|
||||
.show(getFragmentManager(), "unavailableFeatureDialog");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Preference deleteKeysFingerprints = findPreference(getString(R.string.fingerprint_delete_all_key));
|
||||
if (!fingerprintSupported) {
|
||||
deleteKeysFingerprints.setEnabled(false);
|
||||
} else {
|
||||
deleteKeysFingerprints.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(getResources().getString(R.string.fingerprint_delete_all_warning))
|
||||
.setIcon(getResources().getDrawable(
|
||||
android.R.drawable.ic_dialog_alert))
|
||||
.setPositiveButton(
|
||||
getResources().getString(android.R.string.yes),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
FingerPrintHelper.deleteEntryKeyInKeystoreForFingerprints(
|
||||
getContext(),
|
||||
new FingerPrintHelper.FingerPrintErrorCallback() {
|
||||
@Override
|
||||
public void onInvalidKeyException(Exception e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFingerPrintException(Exception e) {
|
||||
Toast.makeText(getContext(), R.string.fingerprint_error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
PreferencesUtil.deleteAllValuesFromNoBackupPreferences(getContext());
|
||||
}
|
||||
})
|
||||
.setNegativeButton(
|
||||
getResources().getString(android.R.string.no),
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
}
|
||||
}).show();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case NESTED_SCREEN_FORM_FILLING_KEY:
|
||||
setPreferencesFromResource(R.xml.form_filling_preferences, rootKey);
|
||||
|
||||
SwitchPreference autoFillEnablePreference =
|
||||
(SwitchPreference) findPreference(getString(R.string.settings_autofill_enable_key));
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillManager autofillManager = getActivity().getSystemService(AutofillManager.class);
|
||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices())
|
||||
autoFillEnablePreference.setChecked(autofillManager.hasEnabledAutofillServices());
|
||||
autoFillEnablePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (((SwitchPreference) preference).isChecked()) {
|
||||
startEnableService();
|
||||
} else {
|
||||
disableService();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void disableService() {
|
||||
if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) {
|
||||
autofillManager.disableAutofillServices();
|
||||
} else {
|
||||
Log.d(getClass().getName(), "Sample service already disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void startEnableService() {
|
||||
if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE);
|
||||
intent.setData(Uri.parse("package:com.example.android.autofill.service"));
|
||||
Log.d(getClass().getName(), "enableService(): intent="+ intent);
|
||||
startActivityForResult(intent, REQUEST_CODE_AUTOFILL);
|
||||
} else {
|
||||
Log.d(getClass().getName(), "Sample service already enabled.");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
autoFillEnablePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
((SwitchPreference) preference).setChecked(false);
|
||||
FragmentManager fragmentManager = getFragmentManager();
|
||||
assert fragmentManager != null;
|
||||
UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.O)
|
||||
.show(fragmentManager, "unavailableFeatureDialog");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case NESTED_SCREEN_DB_KEY:
|
||||
setPreferencesFromResource(R.xml.db_preferences, rootKey);
|
||||
|
||||
Database db = App.getDB();
|
||||
Preference algorithmPref = findPreference(getString(R.string.algorithm_key));
|
||||
if (db.Loaded()) {
|
||||
if (db.pm.algorithmSettingsEnabled()) {
|
||||
|
||||
Preference roundPref = findPreference(getString(R.string.rounds_key));
|
||||
|
||||
if (!(db.Loaded() && db.pm.appSettingsEnabled())) {
|
||||
algorithmPref.setEnabled(false);
|
||||
roundPref.setEnabled(false);
|
||||
}
|
||||
|
||||
if (db.Loaded() && db.pm.appSettingsEnabled()) {
|
||||
roundPref.setEnabled(true);
|
||||
roundPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
setRounds(App.getDB(), preference);
|
||||
@@ -140,7 +265,20 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
});
|
||||
setRounds(db, roundPref);
|
||||
|
||||
// TODO Algo
|
||||
Preference algorithmPref = findPreference(getString(R.string.algorithm_key));
|
||||
// algorithmPref.setEnabled(true);
|
||||
setAlgorithm(db, algorithmPref);
|
||||
}
|
||||
|
||||
if (db.pm.isRecycleBinAvailable()) {
|
||||
SwitchPreference recycleBinPref = (SwitchPreference) findPreference(getString(R.string.recycle_bin_key));
|
||||
// TODO Recycle
|
||||
//recycleBinPref.setEnabled(true);
|
||||
recycleBinPref.setChecked(db.pm.isRecycleBinEnable());
|
||||
}
|
||||
|
||||
} else {
|
||||
Log.e(getClass().getName(), "Database isn't ready");
|
||||
}
|
||||
@@ -185,10 +323,19 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat {
|
||||
switch (key) {
|
||||
case NESTED_SCREEN_APP_KEY:
|
||||
return resources.getString(R.string.menu_app_settings);
|
||||
case NESTED_SCREEN_FORM_FILLING_KEY:
|
||||
return resources.getString(R.string.menu_form_filling_settings);
|
||||
case NESTED_SCREEN_DB_KEY:
|
||||
return resources.getString(R.string.menu_db_settings);
|
||||
default:
|
||||
return resources.getString(R.string.settings);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
// TODO encapsulate
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user