Compare commits

...

74 Commits

Author SHA1 Message Date
J-Jamet
edc7324c1d Merge branch 'release/2.5.0.0beta16' 2018-08-02 11:19:41 +02:00
J-Jamet
b9190e8254 Fix translations 2018-08-02 11:18:59 +02:00
J-Jamet
534878ae99 Merge branch 'translations' into develop 2018-08-02 11:04:39 +02:00
J-Jamet
99bfd21ecc Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2018-08-02 11:03:37 +02:00
J-Jamet
cb5c324c9d Fix search / Fix icon - back #138 / Upgrade changelogs 2018-08-02 10:34:04 +02:00
J-Jamet
55dc504f26 Fix visual elements 2018-08-01 15:45:49 +02:00
J-Jamet
ecb0138c90 Add display username settings and rearrange form filling 2018-08-01 12:00:15 +02:00
J-Jamet
0860eeb87f Show website domains if entries given no title #124 and add username 2018-07-31 22:04:41 +02:00
J-Jamet
f08bff61cf Update changelogs 2018-07-31 08:52:58 +02:00
J-Jamet
432aca6465 Fix transparent toolbar 2018-07-30 22:17:23 +02:00
J-Jamet
7cdb8db146 New error message #154 2018-07-30 21:10:11 +02:00
random r
6ba9dedcb8 Translated using Weblate (Italian)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-07-30 11:59:13 +02:00
random r
6d603608f4 Translated using Weblate (Italian)
Currently translated at 99.3% (319 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2018-07-30 11:58:11 +02:00
Kunzisoft
64f4d9fb84 Translated using Weblate (French)
Currently translated at 99.6% (320 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-07-29 14:37:40 +02:00
J-Jamet
60ccc450ae Fix read-only education screen 2018-07-29 09:44:01 +02:00
J-Jamet
ddb5f327a3 Add warning dialog on password copy button and sort settings 2018-07-28 18:00:57 +02:00
J-Jamet
df90ea42eb Deleting points in parameter explanations 2018-07-28 16:18:36 +02:00
Jérémy JAMET
a062d648b3 Merge pull request #158 from comradekingu/patch-1
Spelling: Language rework
2018-07-28 15:56:37 +02:00
J-Jamet
a59ae820b5 Add gray copy icon when copy not available 2018-07-28 15:18:34 +02:00
J-Jamet
228831acdd Upgrade version and add changelogs 2018-07-28 14:29:03 +02:00
J-Jamet
bf15ee43da Populate custom fields in search entry 2018-07-28 14:00:49 +02:00
J-Jamet
608f45677c Fix quick search with magikeyboard 2018-07-28 09:37:26 +02:00
Daniel
1cb15f214b Translated using Weblate (German)
Currently translated at 100.0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-28 00:37:41 +02:00
Claus Rüdinger
cd4a9e9b03 Translated using Weblate (German)
Currently translated at 100.0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-28 00:37:40 +02:00
J-Jamet
d6eae56d4f Merge branch 'feature/Search' into develop #26 #147 #153 2018-07-27 22:19:43 +02:00
J-Jamet
b5a87a63dc Fix search fragment 2018-07-27 21:39:26 +02:00
Allan Nordhøy
1eb17c4f34 Language rework 2018-07-27 15:11:56 +02:00
Hadrián Candela
8a6ce1f711 Translated using Weblate (Galician)
Currently translated at 7.4% (24 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/gl/
2018-07-27 13:38:02 +02:00
J-Jamet
0f22f8af45 Add search title 2018-07-27 10:54:58 +02:00
Tobirium
b2e81e6fd9 Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-26 23:52:55 +02:00
Daniel
f82eab942d Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-26 23:52:55 +02:00
J-Jamet
aa29aec40f Add search suggestions 2018-07-26 14:08:48 +02:00
Hadrián Candela
181def52ab Added translation using Weblate (Galician) 2018-07-26 13:01:37 +02:00
HybridGlucose
96d2bd63cc Translated using Weblate (Chinese (Traditional))
Currently translated at 44.5% (143 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hant/
2018-07-25 20:36:13 +02:00
Claus Rüdinger
194021a957 Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-25 18:38:46 +02:00
Daniel
9e307f94ea Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-25 18:38:45 +02:00
Tobirium
155b2de138 Translated using Weblate (German)
Currently translated at 100,0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-25 18:38:45 +02:00
J-Jamet
c76c3fd2be Fix fragment duplication 2018-07-25 11:50:18 +02:00
Matheus Gritz
01c5554944 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (321 of 321 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2018-07-24 19:11:13 +02:00
J-Jamet
542cf65b41 Fix timeout and new animation 2018-07-24 18:32:19 +02:00
J-Jamet
f037561a67 Search in fragment 2018-07-24 18:02:08 +02:00
J-Jamet
379263a6d3 Search and Group in one activity 2018-07-24 14:48:03 +02:00
J-Jamet
c420ca01f6 Update Readme 2018-07-23 14:21:46 +02:00
J-Jamet
a1f9db6eee Try to solve the date crash 2018-07-23 14:12:07 +02:00
J-Jamet
c9212174c4 Change text by reset 2018-07-22 12:13:02 +02:00
J-Jamet
0065336377 Merge tag '2.5.0.0beta15' into develop
2.5.0.0beta15
2018-07-19 11:27:47 +02:00
J-Jamet
4f9625a3e1 Merge branch 'release/2.5.0.0beta15' 2018-07-19 11:27:18 +02:00
J-Jamet
7688ebd29b Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2018-07-19 11:15:53 +02:00
Claus Rüdinger
3f2a7f1eb3 Translated using Weblate (German)
Currently translated at 99.3% (313 of 315 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2018-07-19 11:08:28 +02:00
Veronica Small
b3d067d0c8 Translated using Weblate (Chinese (Simplified))
Currently translated at 41.9% (132 of 315 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2018-07-19 11:08:28 +02:00
Kunzisoft
a3b4ad5ac1 Translated using Weblate (French)
Currently translated at 100.0% (315 of 315 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2018-07-19 11:08:28 +02:00
J-Jamet
e3b329d27f Update version and changelog 2018-07-19 11:01:32 +02:00
J-Jamet
7d10c43822 Fix orientation change 2018-07-19 10:40:36 +02:00
J-Jamet
fcb0d45d39 Fix Magikeyboard string 2018-07-19 08:58:20 +02:00
J-Jamet
5492db0223 Merge branch 'feature/ReadOnlyMode' into develop 2018-07-18 18:51:51 +02:00
J-Jamet
2207b05f5f Fix PwDate 2018-07-18 18:12:43 +02:00
J-Jamet
f15a0c2591 Optimize memory 2018-07-18 17:56:32 +02:00
J-Jamet
92fb22129c #77 Add read-only mode to the floating menu 2018-07-18 16:29:18 +02:00
J-Jamet
ccca9c4400 #77 Add read-only mode 2018-07-18 14:37:39 +02:00
J-Jamet
0597cb4416 #148 fix second element to copy 2018-07-16 22:02:24 +02:00
J-Jamet
0602174e50 Merge tag '2.5.0.0beta14' into develop
2.5.0.0beta14
2018-07-15 10:59:01 +02:00
J-Jamet
6fddc92ce7 Merge branch 'release/2.5.0.0beta14' 2018-07-15 10:58:48 +02:00
J-Jamet
aa30df6454 Update version / changelogs 2018-07-15 10:49:45 +02:00
J-Jamet
f6c61ab407 Show error message when sort can't be done 2018-07-15 10:33:54 +02:00
J-Jamet
2ab81ed77c Add parcelables 2018-07-15 10:24:30 +02:00
J-Jamet
0ade035f43 Catch construct element exception 2018-07-14 18:11:20 +02:00
J-Jamet
73b62035d8 Fix MagikIME nullpointer 2018-07-14 16:07:38 +02:00
J-Jamet
28837db308 Fix search and search for keyboard 2018-07-14 14:42:51 +02:00
J-Jamet
85befef260 Merge tag '2.5.0.0beta13' into develop
2.5.0.0beta13
2018-07-14 11:11:49 +02:00
J-Jamet
e7bbb47422 Merge branch 'release/2.5.0.0beta13' 2018-07-14 11:11:36 +02:00
J-Jamet
846bc7edb1 Update Changelogs 2018-07-14 11:01:34 +02:00
J-Jamet
99a9842a1f Fix null pointer on nodeButtonView 2018-07-14 10:47:52 +02:00
J-Jamet
33009138c3 Update version and fix memory crash 2018-07-14 10:23:04 +02:00
J-Jamet
c74b82ebd8 Fix changelogs 2018-07-13 17:06:20 +02:00
124 changed files with 3817 additions and 1486 deletions

View File

@@ -1,3 +1,24 @@
KeepassDX (2.5.0.0beta16)
* New search in a single fragment
* Search suggestions
* Added the display of usernames
* Added translations
* Fix read-only mode
* Fix parcelable / toolbar / back
KeepassDX (2.5.0.0beta15)
* Read only mode
* Best group recovery for the navigation fragment
* Fix copies in notifications
* Fix orientation
* Added translations
KeepassDX (2.5.0.0beta14)
* Optimize all the memory with parcelables / fix search
KeepassDX (2.5.0.0beta13)
* Fix memory issue with parcelable (crash in beta12 version)
KeepassDX (2.5.0.0beta12)
* Added the Magikeyboard to fill the forms (settings still in development)
* Added move and copy for groups and entries

View File

@@ -28,7 +28,7 @@ KeePass DX is a **free open source password manager for Android**, which helps y
Yes, KeePass DX is under **free license (OSI certified)** and **without advertising**. You can have a look at its full source and check whether the encryption algorithms are implemented correctly.
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free.*
*Note : If you access the application from a store, visual features may not be available to incentivize the contribution to the work of open source projects. These optional visuals are accessible after a donation (and a small congratulation message :) or the purchase of an extended version, but do not worry, the main features remain completely free. If you contribute to the project and you do not have access to the themes, do not hesitate to contact me at [contact@kunzisoft.com](contact@kunzisoft.com), I will give you the procedure.*
## Contributions

View File

@@ -8,8 +8,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 27
versionCode = 12
versionName = "2.5.0.0beta12"
versionCode = 16
versionName = "2.5.0.0beta16"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"
@@ -94,7 +94,7 @@ dependencies {
implementation 'joda-time:joda-time:2.9.9'
implementation 'org.sufficientlysecure:html-textview:3.5'
implementation 'com.nononsenseapps:filepicker:4.1.0'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.11.0'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.12.0'
// Permissions
implementation("com.github.hotchemi:permissionsdispatcher:$permissionDispatcherVersion") {
// if you don't use android.app.Fragment you can exclude support for them

View File

@@ -45,10 +45,10 @@ public class PwEntryTestV4 extends TestCase {
entry.setBackgroupColor("blue");
entry.putProtectedBinary("key1", new ProtectedBinary(false, new byte[] {0,1}));
entry.setCustomIcon(new PwIconCustom(UUID.randomUUID(), new byte[0]));
entry.setIconCustom(new PwIconCustom(UUID.randomUUID(), new byte[0]));
entry.setForegroundColor("red");
entry.addToHistory(new PwEntryV4());
entry.setIcon(new PwIconStandard(5));
entry.setIconStandard(new PwIconStandard(5));
entry.setOverrideURL("override");
entry.setParent(new PwGroupV4());
entry.addExtraField("key2", new ProtectedString(false, "value2"));

View File

@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
import com.kunzisoft.keepass.search.SearchDbHelper;
import com.kunzisoft.keepass.database.search.SearchDbHelper;
import java.util.List;
@@ -72,8 +72,8 @@ public class DeleteEntry extends AndroidTestCase {
// Verify the entries were removed from the search index
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME);
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME);
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());

View File

@@ -19,11 +19,11 @@
*/
package com.kunzisoft.keepass.tests.database;
import junit.framework.TestCase;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwEntryV4;
import junit.framework.TestCase;
public class EntryV4 extends TestCase {
public void testBackup() {
@@ -46,7 +46,7 @@ public class EntryV4 extends TestCase {
entry.createBackup(db);
PwEntryV4 backup = entry.getHistory().get(0);
entry.endToManageFieldReferences();
entry.stopToManageFieldReferences();
assertEquals("Title2", backup.getTitle());
assertEquals("User2", backup.getUsername());
}

View File

@@ -104,11 +104,19 @@
<activity
android:name="com.kunzisoft.keepass.activities.GroupActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustPan">
android:windowSoftInputMode="adjustPan"
android:launchMode="singleTop">
<meta-data
android:name="android.app.default_searchable"
android:value="com.kunzisoft.keepass.search.SearchResults"
android:exported="false"/>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.EntryActivity"
@@ -118,17 +126,6 @@
android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="com.kunzisoft.keepass.search.SearchResultsActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity"
android:configChanges="orientation|keyboardHidden" />

View File

@@ -28,6 +28,7 @@ import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
@@ -46,7 +47,6 @@ import com.kunzisoft.keepass.database.ExtraFields;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.lock.LockingHideActivity;
import com.kunzisoft.keepass.notifications.NotificationCopyingService;
@@ -65,6 +65,7 @@ import java.util.Date;
import java.util.UUID;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isClipboardNotificationsEnable;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields;
public class EntryActivity extends LockingHideActivity {
private final static String TAG = EntryActivity.class.getName();
@@ -78,15 +79,17 @@ public class EntryActivity extends LockingHideActivity {
protected PwEntry mEntry;
private boolean mShowPassword;
protected boolean readOnly = false;
private ClipboardHelper clipboardHelper;
private boolean firstLaunchOfActivity;
public static void launch(Activity act, PwEntry pw) {
private int iconColor;
public static void launch(Activity act, PwEntry pw, boolean readOnly) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, EntryActivity.class);
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
}
@@ -110,7 +113,7 @@ public class EntryActivity extends LockingHideActivity {
finish();
return;
}
readOnly = db.isReadOnly();
readOnly = db.isReadOnly() || readOnly;
mShowPassword = !PreferencesUtil.isPasswordMask(this);
@@ -123,6 +126,11 @@ public class EntryActivity extends LockingHideActivity {
finish();
return;
}
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
invalidateOptionsMenu();
@@ -225,7 +233,7 @@ public class EntryActivity extends LockingHideActivity {
startService(intent);
}
mEntry.endToManageFieldReferences();
mEntry.stopToManageFieldReferences();
}
firstLaunchOfActivity = false;
}
@@ -310,18 +318,10 @@ public class EntryActivity extends LockingHideActivity {
mEntry.startToManageFieldReferences(pm);
// Assign title icon
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon());
}
db.getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), iconColor);
// Assign title text
titleView.setText(mEntry.getTitle());
titleView.setText(mEntry.getVisualTitle());
// Assign basic fields
entryContentsView.assignUserName(mEntry.getUsername());
@@ -330,12 +330,39 @@ public class EntryActivity extends LockingHideActivity {
getString(R.string.copy_field, getString(R.string.entry_user_name)))
);
entryContentsView.assignPassword(mEntry.getPassword());
if (PreferencesUtil.allowCopyPasswordAndProtectedFields(this)) {
boolean allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this);
entryContentsView.assignPassword(mEntry.getPassword(), allowCopyPassword);
if (allowCopyPassword) {
entryContentsView.assignPasswordCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
getString(R.string.copy_field, getString(R.string.entry_password)))
);
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
entryContentsView.assignPasswordCopyListener(v -> {
String message = getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning);
AlertDialog warningDialog = new AlertDialog.Builder(EntryActivity.this)
.setMessage(message).create();
warningDialog.setButton(AlertDialog.BUTTON1, getText(android.R.string.ok),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, true);
dialog.dismiss();
fillData();
});
warningDialog.setButton(AlertDialog.BUTTON2, getText(android.R.string.cancel),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, false);
dialog.dismiss();
fillData();
});
warningDialog.show();
});
} else {
entryContentsView.assignPasswordCopyListener(null);
}
}
entryContentsView.assignURL(mEntry.getUrl());
@@ -369,7 +396,7 @@ public class EntryActivity extends LockingHideActivity {
entryContentsView.assignExpiresDate(getString(R.string.never));
}
mEntry.endToManageFieldReferences();
mEntry.stopToManageFieldReferences();
}
@Override

View File

@@ -56,7 +56,6 @@ import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.lock.LockingHideActivity;
import com.kunzisoft.keepass.settings.PreferencesUtil;
@@ -71,7 +70,7 @@ import java.util.UUID;
import javax.annotation.Nullable;
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.UNDEFINED_ICON_ID;
import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD;
public class EntryEditActivity extends LockingHideActivity
implements IconPickerDialogFragment.IconPickerListener,
@@ -89,10 +88,12 @@ public class EntryEditActivity extends LockingHideActivity
public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129;
public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY";
private Database database;
protected PwEntry mEntry;
protected PwEntry mCallbackNewEntry;
protected boolean mIsNew;
protected int mSelectedIconID = UNDEFINED_ICON_ID;
protected PwIconStandard mSelectedIconStandard;
// Views
private ScrollView scrollView;
@@ -143,7 +144,6 @@ public class EntryEditActivity extends LockingHideActivity
setContentView(R.layout.entry_edit);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.app_name));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -162,8 +162,8 @@ public class EntryEditActivity extends LockingHideActivity
entryExtraFieldsContainer = findViewById(R.id.advanced_container);
// Likely the app has been killed exit the activity
Database db = App.getDB();
if ( ! db.getLoaded() ) {
database = App.getDB();
if ( ! database.getLoaded() ) {
finish();
return;
}
@@ -176,18 +176,16 @@ public class EntryEditActivity extends LockingHideActivity
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
PwDatabase pm = db.getPwDatabase();
mSelectedIconStandard = database.getPwDatabase().getIconFactory().getUnknownIcon();
PwDatabase pm = database.getPwDatabase();
if ( uuidBytes == null ) {
PwGroupId parentId = (PwGroupId) intent.getSerializableExtra(KEY_PARENT);
PwGroupId parentId = intent.getParcelableExtra(KEY_PARENT);
PwGroup parent = pm.getGroupByGroupId(parentId);
mEntry = db.createEntry(parent);
mEntry = database.createEntry(parent);
mIsNew = true;
// Add the default icon
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, true, iconColor);
} else {
App.getDB().getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView);
}
database.getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, iconColor);
} else {
UUID uuid = Types.bytestoUUID(uuidBytes);
mEntry = pm.getEntryByUUIDId(uuid);
@@ -195,8 +193,12 @@ public class EntryEditActivity extends LockingHideActivity
fillData();
}
// Assign title
setTitle((mIsNew) ? getString(R.string.add_entry) : getString(R.string.edit_entry));
// Retrieve the icon after an orientation change
if (savedInstanceState != null && savedInstanceState.containsKey(IconPickerDialogFragment.KEY_ICON_ID)) {
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ICON_STANDARD)) {
iconPicked(savedInstanceState);
}
@@ -259,9 +261,9 @@ public class EntryEditActivity extends LockingHideActivity
EntryEditActivity act = EntryEditActivity.this;
RunnableOnFinish task;
if ( mIsNew ) {
task = new AddEntryRunnable(act, App.getDB(), mCallbackNewEntry, onFinish);
task = new AddEntryRunnable(act, database, mCallbackNewEntry, onFinish);
} else {
task = new UpdateEntryRunnable(act, App.getDB(), mEntry, mCallbackNewEntry, onFinish);
task = new UpdateEntryRunnable(act, database, mEntry, mCallbackNewEntry, onFinish);
}
task.setUpdateProgressTaskStatus(
new UpdateProgressTaskStatus(this,
@@ -409,7 +411,7 @@ public class EntryEditActivity extends LockingHideActivity
newEntry.setLastModificationTime(new PwDate());
newEntry.setTitle(entryTitleView.getText().toString());
newEntry.setIcon(retrieveIcon());
newEntry.setIconStandard(retrieveIcon());
newEntry.setUrl(entryUrlView.getText().toString());
newEntry.setUsername(entryUserNameView.getText().toString());
@@ -429,21 +431,21 @@ public class EntryEditActivity extends LockingHideActivity
}
}
newEntry.endToManageFieldReferences();
newEntry.stopToManageFieldReferences();
return newEntry;
}
/**
* Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one
* @return
*/
private PwIconStandard retrieveIcon() {
if(mSelectedIconID != UNDEFINED_ICON_ID)
return App.getDB().getPwDatabase().getIconFactory().getIcon(mSelectedIconID);
if (!mSelectedIconStandard.isUnknown())
return mSelectedIconStandard;
else {
if (mIsNew) {
return App.getDB().getPwDatabase().getIconFactory().getKeyIcon();
return database.getPwDatabase().getIconFactory().getKeyIcon();
}
else {
// Keep previous icon, if no new one was selected
@@ -475,16 +477,21 @@ public class EntryEditActivity extends LockingHideActivity
return super.onOptionsItemSelected(item);
}
private void assignIconView() {
database.getDrawFactory()
.assignDatabaseIconTo(
this,
entryIconView,
mEntry.getIcon(),
iconColor);
}
protected void fillData() {
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, entryIconView, mEntry.getIcon());
}
assignIconView();
// Don't start the field reference manager, we want to see the raw ref
mEntry.endToManageFieldReferences();
mEntry.stopToManageFieldReferences();
entryTitleView.setText(mEntry.getTitle());
entryUserNameView.setText(mEntry.getUsername());
@@ -515,14 +522,15 @@ public class EntryEditActivity extends LockingHideActivity
@Override
public void iconPicked(Bundle bundle) {
mSelectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
entryIconView.setImageResource(IconPackChooser.getSelectedIconPack(this).iconToResId(mSelectedIconID));
mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD);
mEntry.setIconStandard(mSelectedIconStandard);
assignIconView();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
if (mSelectedIconID != UNDEFINED_ICON_ID) {
outState.putInt(IconPickerDialogFragment.KEY_ICON_ID, mSelectedIconID);
if (!mSelectedIconStandard.isUnknown()) {
outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard);
super.onSaveInstanceState(outState);
}
}
@@ -548,7 +556,7 @@ public class EntryEditActivity extends LockingHideActivity
if (mCallbackNewEntry != null) {
Bundle bundle = new Bundle();
Intent intentEntry = new Intent();
bundle.putSerializable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mCallbackNewEntry);
intentEntry.putExtras(bundle);
if (mIsNew) {
setResult(ADD_ENTRY_RESULT_CODE, intentEntry);

View File

@@ -19,6 +19,7 @@
*/
package com.kunzisoft.keepass.activities;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.SearchManager;
@@ -29,24 +30,31 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.Database;
@@ -54,9 +62,11 @@ import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.database.action.node.AddGroupRunnable;
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable;
@@ -69,108 +79,178 @@ import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
import com.kunzisoft.keepass.dialogs.ReadOnlyDialog;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.password.AssignPasswordHelper;
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.search.SearchResultsActivity;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
import com.kunzisoft.keepass.tasks.UIToastTask;
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
import com.kunzisoft.keepass.utils.MenuUtil;
import com.kunzisoft.keepass.view.AddNodeButtonView;
import net.cachapa.expandablelayout.ExpandableLayout;
public class GroupActivity extends ListNodesActivity
import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
public class GroupActivity extends LockingActivity
implements GroupEditDialogFragment.EditGroupListener,
IconPickerDialogFragment.IconPickerListener,
NodeAdapter.NodeMenuListener,
ListNodesFragment.OnScrollListener {
ListNodesFragment.OnScrollListener,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
private static final String TAG = GroupActivity.class.getName();
private Toolbar toolbar;
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
private static final String SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG";
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY";
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
private Toolbar toolbar;
private View searchTitleView;
private ExpandableLayout toolbarPasteExpandableLayout;
private Toolbar toolbarPaste;
private ImageView iconView;
private AddNodeButtonView addNodeButtonView;
private TextView groupNameView;
protected boolean addGroupEnabled = false;
protected boolean addEntryEnabled = false;
protected boolean isRoot = false;
protected boolean readOnly = false;
private Database database;
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY";
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
private ListNodesFragment listNodesFragment;
private boolean currentGroupIsASearch;
private PwGroup rootGroup;
private PwGroup mCurrentGroup;
private PwGroup oldGroupToUpdate;
private PwNode nodeToCopy;
private PwNode nodeToMove;
public static void launch(Activity act) {
private boolean entrySelectionMode;
private AutofillHelper autofillHelper;
private SearchEntryCursorAdapter searchSuggestionAdapter;
private int iconColor;
// After a database creation
public static void launch(Activity act) {
launch(act, READ_ONLY_DEFAULT);
}
public static void launch(Activity act, boolean readOnly) {
startRecordTime(act);
launch(act, null);
launch(act, null, readOnly);
}
public static void launch(Activity act, PwGroup group) {
if (checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, GroupActivity.class);
private static void buildAndLaunchIntent(Activity activity, PwGroup group, boolean readOnly,
IntentBuildLauncher intentBuildLauncher) {
if (checkTimeIsAllowedOrFinish(activity)) {
Intent intent = new Intent(activity, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
act.startActivityForResult(intent, 0);
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
intentBuildLauncher.startActivityForResult(intent);
}
}
public static void launchForKeyboardResult(Activity act) {
public static void launch(Activity activity, PwGroup group, boolean readOnly) {
buildAndLaunchIntent(activity, group, readOnly,
(intent) -> activity.startActivityForResult(intent, 0));
}
public static void launchForKeyboardResult(Activity act, boolean readOnly) {
startRecordTime(act);
launchForKeyboardResult(act, null);
launchForKeyboardResult(act, null, readOnly);
}
public static void launchForKeyboardResult(Activity act, PwGroup group) {
// TODO remove
if (checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
public static void launchForKeyboardResult(Activity activity, PwGroup group, boolean readOnly) {
// TODO implement pre search to directly open the direct group
buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
}
activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
});
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launchForAutofillResult(Activity act, AssistStructure assistStructure) {
public static void launchForAutofillResult(Activity act, AssistStructure assistStructure, boolean readOnly) {
if ( assistStructure != null ) {
startRecordTime(act);
launchForAutofillResult(act, null, assistStructure);
launchForAutofillResult(act, null, assistStructure, readOnly);
} else {
launch(act);
launch(act, readOnly);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launchForAutofillResult(Activity act, PwGroup group, AssistStructure assistStructure) {
// TODO remove
public static void launchForAutofillResult(Activity activity, PwGroup group, AssistStructure assistStructure, boolean readOnly) {
// TODO implement pre search to directly open the direct group
if ( assistStructure != null ) {
if (checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
}
activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
});
} else {
launch(act, group);
launch(activity, group, readOnly);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
if ( isFinishing() ) {
return;
}
database = App.getDB();
// Likely the app has been killed exit the activity
if ( ! database.getLoaded() ) {
finish();
return;
}
// Construct main view
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
// Initialize views
iconView = findViewById(R.id.icon);
addNodeButtonView = findViewById(R.id.add_node_button);
toolbar = findViewById(R.id.toolbar);
searchTitleView = findViewById(R.id.search_title);
groupNameView = findViewById(R.id.group_name);
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
toolbarPaste = findViewById(R.id.toolbar_paste);
invalidateOptionsMenu();
// Get arg from intent or instance state
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
// Retrieve elements after an orientation change
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
oldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY);
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) {
nodeToCopy = savedInstanceState.getParcelable(NODE_TO_COPY_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener());
}
else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
nodeToMove = savedInstanceState.getParcelable(NODE_TO_MOVE_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
}
}
rootGroup = database.getPwDatabase().getRootGroup();
mCurrentGroup = retrieveCurrentGroup(getIntent(), savedInstanceState);
currentGroupIsASearch = Intent.ACTION_SEARCH.equals(getIntent().getAction());
Log.i(TAG, "Started creating tree");
if ( mCurrentGroup == null ) {
@@ -178,23 +258,9 @@ public class GroupActivity extends ListNodesActivity
return;
}
// Construct main view
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
attachFragmentToContentView();
iconView = findViewById(R.id.icon);
addNodeButtonView = findViewById(R.id.add_node_button);
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("");
setSupportActionBar(toolbar);
groupNameView = findViewById(R.id.group_name);
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
toolbarPaste = findViewById(R.id.toolbar_paste);
toolbarPaste.inflateMenu(R.menu.node_paste_menu);
toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
toolbarPaste.setNavigationOnClickListener(view -> {
@@ -203,104 +269,215 @@ public class GroupActivity extends ListNodesActivity
nodeToMove = null;
});
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
oldGroupToUpdate = (PwGroup) savedInstanceState.getSerializable(OLD_GROUP_TO_UPDATE_KEY);
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
if (savedInstanceState.containsKey(NODE_TO_COPY_KEY)) {
nodeToCopy = (PwNode) savedInstanceState.getSerializable(NODE_TO_COPY_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnCopyMenuItemClickListener());
}
else if (savedInstanceState.containsKey(NODE_TO_MOVE_KEY)) {
nodeToMove = (PwNode) savedInstanceState.getSerializable(NODE_TO_MOVE_KEY);
toolbarPaste.setOnMenuItemClickListener(new OnMoveMenuItemClickListener());
}
}
String fragmentTag = LIST_NODES_FRAGMENT_TAG;
if (currentGroupIsASearch)
fragmentTag = SEARCH_FRAGMENT_TAG;
addNodeButtonView.setAddGroupClickListener(v -> {
GroupEditDialogFragment.build()
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
});
// Initialize the fragment with the list
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.findFragmentByTag(fragmentTag);
if (listNodesFragment == null)
listNodesFragment = ListNodesFragment.newInstance(mCurrentGroup, readOnly, currentGroupIsASearch);
// Attach fragment to content view
getSupportFragmentManager().beginTransaction().replace(
R.id.nodes_list_fragment_container,
listNodesFragment,
fragmentTag)
.commit();
// Add listeners to the add buttons
addNodeButtonView.setAddGroupClickListener(v -> GroupEditDialogFragment.build()
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP));
addNodeButtonView.setAddEntryClickListener(v ->
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
Log.i(TAG, "Finished creating tree");
if (isRoot) {
showWarnings();
// To init autofill
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
// Search suggestion
searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database);
Log.i(TAG, "Finished creating tree");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putSerializable(GROUP_ID_KEY, mCurrentGroup.getId());
outState.putSerializable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
if (nodeToCopy != null)
outState.putSerializable(NODE_TO_COPY_KEY, nodeToCopy);
if (nodeToMove != null)
outState.putSerializable(NODE_TO_MOVE_KEY, nodeToMove);
super.onSaveInstanceState(outState);
protected void onNewIntent(Intent intent) {
setIntent(intent);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
// only one instance of search in backstack
openSearchGroup(retrieveCurrentGroup(intent, null));
currentGroupIsASearch = true;
} else {
currentGroupIsASearch = false;
}
}
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
PwGroupId pwGroupId = null; // TODO Parcelable
if (savedInstanceState != null
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = (PwGroupId) savedInstanceState.getSerializable(GROUP_ID_KEY);
} else {
if (getIntent() != null)
pwGroupId = (PwGroupId) getIntent().getSerializableExtra(GROUP_ID_KEY);
private void openSearchGroup(PwGroup group) {
// Delete the previous search fragment
Fragment searchFragment = getSupportFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG);
if (searchFragment != null) {
if ( getSupportFragmentManager()
.popBackStackImmediate(SEARCH_FRAGMENT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) )
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
}
Database db = App.getDB();
readOnly = db.isReadOnly();
PwGroup root = db.getPwDatabase().getRootGroup();
openGroup(group, true);
}
Log.w(TAG, "Creating tree view");
PwGroup currentGroup;
if ( pwGroupId == null ) {
currentGroup = root;
} else {
currentGroup = db.getPwDatabase().getGroupByGroupId(pwGroupId);
private void openChildGroup(PwGroup group) {
openGroup(group, false);
}
private void openGroup(PwGroup group, boolean isASearch) {
// Check Timeout
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
// Open a group in a new fragment
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group, readOnly, isASearch);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// Different animation
String fragmentTag;
if (isASearch) {
fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_out_bottom,
R.anim.slide_in_bottom, R.anim.slide_out_top);
fragmentTag = SEARCH_FRAGMENT_TAG;
} else {
fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right);
fragmentTag = LIST_NODES_FRAGMENT_TAG;
}
fragmentTransaction.replace(R.id.nodes_list_fragment_container,
newListNodeFragment,
fragmentTag);
fragmentTransaction.addToBackStack(fragmentTag);
fragmentTransaction.commit();
listNodesFragment = newListNodeFragment;
mCurrentGroup = group;
assignGroupViewElements();
}
if (currentGroup != null) {
addGroupEnabled = !readOnly;
addEntryEnabled = !readOnly; // TODO consultation mode
isRoot = (currentGroup == root);
if (!currentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled;
}
return currentGroup;
}
@Override
public void assignToolbarElements() {
super.assignToolbarElements();
protected void onSaveInstanceState(Bundle outState) {
outState.putParcelable(GROUP_ID_KEY, mCurrentGroup.getId());
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
if (nodeToCopy != null)
outState.putParcelable(NODE_TO_COPY_KEY, nodeToCopy);
if (nodeToMove != null)
outState.putParcelable(NODE_TO_MOVE_KEY, nodeToMove);
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
// Assign the group icon depending of IconPack or custom icon
if ( mCurrentGroup != null ) {
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor);
protected PwGroup retrieveCurrentGroup(Intent intent, @Nullable Bundle savedInstanceState) {
// If it's a search
if ( Intent.ACTION_SEARCH.equals(intent.getAction()) ) {
return database.search(intent.getStringExtra(SearchManager.QUERY).trim());
}
// else a real group
else {
PwGroupId pwGroupId = null;
if (savedInstanceState != null
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon());
if (getIntent() != null)
pwGroupId = intent.getParcelableExtra(GROUP_ID_KEY);
}
if (toolbar != null) {
if ( mCurrentGroup.containsParent() )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
else {
toolbar.setNavigationIcon(null);
readOnly = database.isReadOnly() || readOnly; // Force read only if the database is like that
Log.w(TAG, "Creating tree view");
PwGroup currentGroup;
if (pwGroupId == null) {
currentGroup = rootGroup;
} else {
currentGroup = database.getPwDatabase().getGroupByGroupId(pwGroupId);
}
return currentGroup;
}
}
public void assignGroupViewElements() {
// Assign title
if (mCurrentGroup != null) {
String title = mCurrentGroup.getName();
if (title != null && title.length() > 0) {
if (groupNameView != null) {
groupNameView.setText(title);
groupNameView.invalidate();
}
} else {
if (groupNameView != null) {
groupNameView.setText(getText(R.string.root));
groupNameView.invalidate();
}
}
}
if (currentGroupIsASearch) {
searchTitleView.setVisibility(View.VISIBLE);
} else {
searchTitleView.setVisibility(View.GONE);
}
// Assign icon
if (currentGroupIsASearch) {
if (toolbar != null) {
toolbar.setNavigationIcon(null);
}
iconView.setVisibility(View.GONE);
} else {
// Assign the group icon depending of IconPack or custom icon
iconView.setVisibility(View.VISIBLE);
if (mCurrentGroup != null) {
database.getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), iconColor);
if (toolbar != null) {
if (mCurrentGroup.containsParent())
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
else {
toolbar.setNavigationIcon(null);
}
}
}
}
// Show button if allowed
if (addNodeButtonView != null) {
// To enable add button
boolean addGroupEnabled = !readOnly && !currentGroupIsASearch;
boolean addEntryEnabled = !readOnly && !currentGroupIsASearch;
if (mCurrentGroup != null) {
boolean isRoot = (mCurrentGroup == rootGroup);
if (!mCurrentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled;
if (isRoot) {
showWarnings();
}
}
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
if (addNodeButtonView.isEnable())
addNodeButtonView.showButton();
}
}
@Override
@@ -309,6 +486,50 @@ public class GroupActivity extends ListNodesActivity
addNodeButtonView.hideButtonOnScrollListener(dy);
}
@Override
public void onNodeClick(PwNode node) {
// Add event when we have Autofill
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
// Build response with the entry selected
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
}
}
if ( assistStructure == null ){
if (entrySelectionMode) {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
} else {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node, readOnly);
break;
}
}
}
}
@Override
public boolean onOpenMenuClick(PwNode node) {
onNodeClick(node);
@@ -320,7 +541,7 @@ public class GroupActivity extends ListNodesActivity
switch (node.getType()) {
case GROUP:
oldGroupToUpdate = (PwGroup) node;
GroupEditDialogFragment.build(node)
GroupEditDialogFragment.build(oldGroupToUpdate)
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
break;
@@ -481,9 +702,11 @@ public class GroupActivity extends ListNodesActivity
@Override
protected void onResume() {
super.onResume();
// Show button on resume
if (addNodeButtonView != null)
addNodeButtonView.showButton();
// Refresh the elements
assignGroupViewElements();
// Refresh suggestions to change preferences
if (searchSuggestionAdapter != null)
searchSuggestionAdapter.reInit(this);
}
/**
@@ -496,7 +719,8 @@ public class GroupActivity extends ListNodesActivity
// If no node, show education to add new one
if (listNodesFragment != null
&& listNodesFragment.isEmpty()) {
if (!PreferencesUtil.isEducationNewNodePerformed(this)) {
if (!PreferencesUtil.isEducationNewNodePerformed(this)
&& addNodeButtonView.isEnable()) {
TapTargetView.showFor(this,
TapTarget.forView(findViewById(R.id.add_button),
@@ -633,7 +857,8 @@ public class GroupActivity extends ListNodesActivity
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search, menu);
inflater.inflate(R.menu.database_master_key, menu);
if (!readOnly)
inflater.inflate(R.menu.database_master_key, menu);
inflater.inflate(R.menu.database_lock, menu);
// Get the SearchView and set the searchable configuration
@@ -646,11 +871,26 @@ public class GroupActivity extends ListNodesActivity
searchView = (SearchView) searchItem.getActionView();
}
if (searchView != null) {
// TODO Flickering when locking, will be better with content provider
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, GroupActivity.class)));
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
searchView.setSuggestionsAdapter(searchSuggestionAdapter);
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
@Override
public boolean onSuggestionClick(int position) {
onNodeClick(searchSuggestionAdapter.getEntryFromPosition(position));
return true;
}
@Override
public boolean onSuggestionSelect(int position) {
return true;
}
});
}
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
super.onCreateOptionsMenu(menu);
// Launch education screen
@@ -667,16 +907,23 @@ public class GroupActivity extends ListNodesActivity
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
// manually launch the real search activity
final Intent searchIntent = new Intent(getApplicationContext(), SearchResultsActivity.class);
final Intent searchIntent = new Intent(getApplicationContext(), GroupActivity.class);
// add query to the Intent Extras
searchIntent.setAction(Intent.ACTION_SEARCH);
searchIntent.putExtra(SearchManager.QUERY, query);
if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& autofillHelper.getAssistStructure() != null ) {
AutofillHelper.addAssistStructureExtraInIntent(searchIntent, autofillHelper.getAssistStructure());
startActivityForResult(searchIntent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
customSearchQueryExecuted = true;
}
// To get the keyboard response, verify if the current intent contains the EntrySelection key
else if (EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent())){
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(searchIntent);
startActivityForResult(searchIntent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
customSearchQueryExecuted = true;
}
}
if (!customSearchQueryExecuted) {
@@ -693,7 +940,7 @@ public class GroupActivity extends ListNodesActivity
return true;
case R.id.menu_search:
onSearchRequested();
//onSearchRequested();
return true;
case R.id.menu_lock:
@@ -703,8 +950,11 @@ public class GroupActivity extends ListNodesActivity
case R.id.menu_change_master_key:
setPassword();
return true;
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
return super.onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
private void setPassword() {
@@ -728,7 +978,7 @@ public class GroupActivity extends ListNodesActivity
try {
iconStandard = (PwIconStandard) icon;
} catch (Exception ignored) {} // TODO custom icon
newGroup.setIcon(iconStandard);
newGroup.setIconStandard(iconStandard);
// If group created save it in the database
AddGroupRunnable addGroupRunnable = new AddGroupRunnable(this,
@@ -750,8 +1000,11 @@ public class GroupActivity extends ListNodesActivity
updateGroup.setName(name);
try {
iconStandard = (PwIconStandard) icon;
} catch (Exception ignored) {} // TODO custom icon
updateGroup.setIcon(iconStandard);
updateGroup = ((PwGroupV4) oldGroupToUpdate).clone(); // TODO generalize
} catch (Exception e) {
e.printStackTrace();
} // TODO custom icon
updateGroup.setIconStandard(iconStandard);
if (listNodesFragment != null)
listNodesFragment.removeNode(oldGroupToUpdate);
@@ -776,6 +1029,7 @@ public class GroupActivity extends ListNodesActivity
class AfterAddNode extends AfterActionNodeOnFinish {
@Override
public void run(PwNode oldNode, PwNode newNode) {
super.run();
@@ -794,6 +1048,7 @@ public class GroupActivity extends ListNodesActivity
class AfterUpdateNode extends AfterActionNodeOnFinish {
@Override
public void run(PwNode oldNode, PwNode newNode) {
super.run();
@@ -879,14 +1134,79 @@ public class GroupActivity extends ListNodesActivity
}
@Override
protected void openGroup(PwGroup group) {
super.openGroup(group);
addNodeButtonView.showButton();
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
AssignPasswordHelper assignPasswordHelper =
new AssignPasswordHelper(this,
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
assignPasswordHelper.assignPasswordInDatabase(null);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
if (listNodesFragment != null)
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}
@SuppressLint("RestrictedApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.startActivityForResult(intent, requestCode, options);
}
}
private void removeSearchInIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
currentGroupIsASearch = false;
intent.setAction(Intent.ACTION_DEFAULT);
intent.removeExtra(SearchManager.QUERY);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
addNodeButtonView.showButton();
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
super.onBackPressed();
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// to refresh fragment
listNodesFragment.rebuildList();
mCurrentGroup = listNodesFragment.getMainGroup();
removeSearchInIntent(getIntent());
assignGroupViewElements();
}
}
}

View File

@@ -0,0 +1,7 @@
package com.kunzisoft.keepass.activities;
import android.content.Intent;
public interface IntentBuildLauncher {
void startActivityForResult(Intent intent);
}

View File

@@ -1,290 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities;
import android.annotation.SuppressLint;
import android.app.assist.AssistStructure;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.password.AssignPasswordHelper;
import com.kunzisoft.keepass.utils.MenuUtil;
public abstract class ListNodesActivity extends LockingActivity
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
protected ListNodesFragment listNodesFragment;
protected PwGroup mCurrentGroup;
protected TextView groupNameView;
protected boolean entrySelectionMode;
protected AutofillHelper autofillHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( isFinishing() ) {
return;
}
// Likely the app has been killed exit the activity
if ( ! App.getDB().getLoaded() ) {
finish();
return;
}
invalidateOptionsMenu();
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
initializeListNodesFragment(mCurrentGroup);
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
}
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
@Override
protected void onResume() {
super.onResume();
// Refresh the title
assignToolbarElements();
}
protected void initializeListNodesFragment(PwGroup currentGroup) {
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
if (listNodesFragment == null)
listNodesFragment = ListNodesFragment.newInstance(currentGroup);
}
/**
* Attach the fragment's list of node.
* <br />
* <strong>R.id.nodes_list_fragment_container</strong> must be the id of the container
*/
protected void attachFragmentToContentView() {
getSupportFragmentManager().beginTransaction().replace(
R.id.nodes_list_fragment_container,
listNodesFragment,
LIST_NODES_FRAGMENT_TAG)
.commit();
}
public void assignToolbarElements() {
if (mCurrentGroup != null) {
String title = mCurrentGroup.getName();
if (title != null && title.length() > 0) {
if (groupNameView != null) {
groupNameView.setText(title);
groupNameView.invalidate();
}
} else {
if (groupNameView != null) {
groupNameView.setText(getText(R.string.root));
groupNameView.invalidate();
}
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
return super.onOptionsItemSelected(item);
}
}
@Override
public void onNodeClick(PwNode node) {
// Add event when we have Autofill
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
// Build response with the entry selected
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
}
}
if ( assistStructure == null ){
if (entrySelectionMode) {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
} else {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node);
break;
}
}
}
}
protected void openGroup(PwGroup group) {
// Check Timeout
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group.getId());
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.nodes_list_fragment_container,
newListNodeFragment,
LIST_NODES_FRAGMENT_TAG)
.addToBackStack(LIST_NODES_FRAGMENT_TAG)
.commit();
listNodesFragment = newListNodeFragment;
mCurrentGroup = group;
assignToolbarElements();
}
}
@Override
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
AssignPasswordHelper assignPasswordHelper =
new AssignPasswordHelper(this,
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
assignPasswordHelper.assignPasswordInDatabase(null);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
if (listNodesFragment != null)
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}
@SuppressLint("RestrictedApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.startActivityForResult(intent, requestCode, options);
}
}
@Override
public void onBackPressed() {
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
super.onBackPressed();
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// to refresh fragment
listNodesFragment.rebuildList();
mCurrentGroup = listNodesFragment.getMainGroup();
assignToolbarElements();
}
}
}

View File

@@ -20,10 +20,8 @@ import android.view.ViewGroup;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
@@ -36,34 +34,31 @@ public class ListNodesFragment extends StylishFragment implements
private static final String TAG = ListNodesFragment.class.getName();
private static final String GROUP_KEY = "GROUP_KEY";
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private static final String IS_SEARCH = "IS_SEARCH";
private NodeAdapter.NodeClickCallback nodeClickCallback;
private NodeAdapter.NodeMenuListener nodeMenuListener;
private OnScrollListener onScrollListener;
private RecyclerView listView;
protected PwGroup mCurrentGroup;
protected NodeAdapter mAdapter;
private PwGroup currentGroup;
private NodeAdapter mAdapter;
private View notFoundView;
private boolean isASearchResult;
// Preferences for sorting
private SharedPreferences prefs;
public static ListNodesFragment newInstance(PwGroup group) {
private boolean readOnly;
public static ListNodesFragment newInstance(PwGroup group, boolean readOnly, boolean isASearch) {
Bundle bundle = new Bundle();
if (group != null) {
bundle.putSerializable(GROUP_KEY, group);
}
ListNodesFragment listNodesFragment = new ListNodesFragment();
listNodesFragment.setArguments(bundle);
return listNodesFragment;
}
public static ListNodesFragment newInstance(PwGroupId groupId) {
Bundle bundle=new Bundle();
if (groupId != null) {
bundle.putSerializable(GROUP_ID_KEY, groupId);
bundle.putParcelable(GROUP_KEY, group);
}
bundle.putBoolean(IS_SEARCH, isASearch);
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly);
ListNodesFragment listNodesFragment = new ListNodesFragment();
listNodesFragment.setArguments(bundle);
return listNodesFragment;
@@ -101,46 +96,39 @@ public class ListNodesFragment extends StylishFragment implements
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
if ( getActivity() != null ) {
setHasOptionsMenu(true);
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
if (getArguments() != null) {
// Contains all the group in element
if (getArguments().containsKey(GROUP_KEY)) {
currentGroup = getArguments().getParcelable(GROUP_KEY);
}
if (getArguments().containsKey(IS_SEARCH)) {
isASearchResult = getArguments().getBoolean(IS_SEARCH);
}
}
mCurrentGroup = initCurrentGroup();
if (getActivity() != null) {
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
mAdapter.setReadOnly(readOnly);
mAdapter.setIsASearchResult(isASearchResult);
mAdapter.setOnNodeClickListener(nodeClickCallback);
if (nodeMenuListener != null) {
mAdapter.setActivateContextMenu(true);
mAdapter.setNodeMenuListener(nodeMenuListener);
}
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
}
prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
}
protected PwGroup initCurrentGroup() { // TODO Change by parcelable
Database db = App.getDB();
PwGroup root = db.getPwDatabase().getRootGroup();
PwGroup currentGroup = null;
if (getArguments() != null) {
// Contains all the group in element
if (getArguments().containsKey(GROUP_KEY)) {
currentGroup = (PwGroup) getArguments().getSerializable(GROUP_KEY);
}
// Contains only the group id, so the group must be retrieve
if (getArguments().containsKey(GROUP_ID_KEY)) {
PwGroupId pwGroupId = (PwGroupId) getArguments().getSerializable(GROUP_ID_KEY);
if ( pwGroupId != null )
currentGroup = db.getPwDatabase().getGroupByGroupId(pwGroupId);
}
}
if ( currentGroup == null ) {
currentGroup = root;
}
return currentGroup;
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
@Nullable
@@ -152,6 +140,7 @@ public class ListNodesFragment extends StylishFragment implements
View rootView = inflater.cloneInContext(getContextThemed())
.inflate(R.layout.list_nodes_fragment, container, false);
listView = rootView.findViewById(R.id.nodes_list);
notFoundView = rootView.findViewById(R.id.not_found_container);
if (onScrollListener != null) {
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -171,11 +160,20 @@ public class ListNodesFragment extends StylishFragment implements
super.onResume();
rebuildList();
if (isASearchResult && mAdapter.isEmpty()) {
// To show the " no search entry found "
listView.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listView.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
}
public void rebuildList() {
// Add elements to the list
mAdapter.rebuildList(mCurrentGroup);
mAdapter.rebuildList(currentGroup);
assignListToNodeAdapter(listView);
}
@@ -197,7 +195,7 @@ public class ListNodesFragment extends StylishFragment implements
// Tell the adapter to refresh it's list
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
mAdapter.rebuildList(mCurrentGroup);
mAdapter.rebuildList(currentGroup);
}
@Override
@@ -249,13 +247,13 @@ public class ListNodesFragment extends StylishFragment implements
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);
PwNode newNode = data.getParcelableExtra(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);
mAdapter.rebuildList(currentGroup);
}
} else {
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
@@ -282,7 +280,7 @@ public class ListNodesFragment extends StylishFragment implements
}
public PwGroup getMainGroup() {
return mCurrentGroup;
return currentGroup;
}
public interface OnScrollListener {

View File

@@ -0,0 +1,61 @@
package com.kunzisoft.keepass.activities;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.kunzisoft.keepass.settings.PreferencesUtil;
public class ReadOnlyHelper {
public static final String READ_ONLY_KEY = "READ_ONLY_KEY";
public static final boolean READ_ONLY_DEFAULT = false;
public static boolean retrieveReadOnlyFromInstanceStateOrPreference(Context context, Bundle savedInstanceState) {
boolean readOnly;
if (savedInstanceState != null
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
} else {
readOnly = PreferencesUtil.enableReadOnlyDatabase(context);
}
return readOnly;
}
public static boolean retrieveReadOnlyFromInstanceStateOrArguments(Bundle savedInstanceState, Bundle arguments) {
boolean readOnly = READ_ONLY_DEFAULT;
if (savedInstanceState != null
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
} else if (arguments != null
&& arguments.containsKey(READ_ONLY_KEY)) {
readOnly = arguments.getBoolean(READ_ONLY_KEY);
}
return readOnly;
}
public static boolean retrieveReadOnlyFromInstanceStateOrIntent(Bundle savedInstanceState, Intent intent) {
boolean readOnly = READ_ONLY_DEFAULT;
if (savedInstanceState != null
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
} else {
if (intent != null)
readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT);
}
return readOnly;
}
public static void putReadOnlyInIntent(Intent intent, boolean readOnly) {
intent.putExtra(READ_ONLY_KEY, readOnly);
}
public static void putReadOnlyInBundle(Bundle bundle, boolean readOnly) {
bundle.putBoolean(READ_ONLY_KEY, readOnly);
}
public static void onSaveInstanceState(Bundle outState, boolean readOnly) {
outState.putBoolean(READ_ONLY_KEY, readOnly);
}
}

View File

@@ -29,6 +29,7 @@ abstract class BasicViewHolder extends RecyclerView.ViewHolder {
View container;
ImageView icon;
TextView text;
TextView subText;
BasicViewHolder(View itemView) {
super(itemView);

View File

@@ -30,5 +30,6 @@ class EntryViewHolder extends BasicViewHolder {
container = itemView.findViewById(R.id.entry_container);
icon = itemView.findViewById(R.id.entry_icon);
text = itemView.findViewById(R.id.entry_text);
subText = itemView.findViewById(R.id.entry_subtext);
}
}

View File

@@ -30,5 +30,6 @@ class GroupViewHolder extends BasicViewHolder {
container = itemView.findViewById(R.id.group_container);
icon = itemView.findViewById(R.id.group_icon);
text = itemView.findViewById(R.id.group_text);
subText = itemView.findViewById(R.id.group_subtext);
}
}

View File

@@ -26,23 +26,27 @@ import android.support.annotation.NonNull;
import android.support.v7.util.SortedList;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.util.SortedListAdapterCallback;
import android.util.TypedValue;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.utils.Util;
public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private static final String TAG = NodeAdapter.class.getName();
private SortedList<PwNode> nodeSortedList;
@@ -50,14 +54,20 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private LayoutInflater inflater;
private MenuInflater menuInflater;
private float textSize;
private float subtextSize;
private float iconSize;
private SortNodeEnum listSort;
private boolean groupsBeforeSort;
private boolean ascendingSort;
private boolean showUsernames;
private NodeClickCallback nodeClickCallback;
private NodeMenuListener nodeMenuListener;
private boolean activateContextMenu;
private boolean readOnly;
private boolean isASearchResult;
private Database database;
private int iconGroupColor;
private int iconEntryColor;
@@ -72,6 +82,8 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
this.context = context;
assignPreferences();
this.activateContextMenu = false;
this.readOnly = false;
this.isASearchResult = false;
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
@Override public int compare(PwNode item1, PwNode item2) {
@@ -87,6 +99,9 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
}
});
// Database
this.database = App.getDB();
// Retrieve the color to tint the icon
int[] attrTextColorPrimary = {android.R.attr.textColorPrimary};
TypedArray taTextColorPrimary = context.getTheme().obtainStyledAttributes(attrTextColorPrimary);
@@ -98,22 +113,30 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
taTextColor.recycle();
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
public void setIsASearchResult(boolean isASearchResult) {
this.isASearchResult = isASearchResult;
}
public void setActivateContextMenu(boolean activate) {
this.activateContextMenu = activate;
}
private void assignPreferences() {
float textSizeDefault = Util.getListTextDefaultSize(context);
this.textSize = PreferencesUtil.getListTextSize(context);
this.subtextSize = context.getResources().getDimension(R.dimen.list_small_size_default)
* textSize / textSizeDefault;
// Retrieve the icon size
int iconDefaultSize = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
context.getResources().getInteger(R.integer.list_icon_size_default),
context.getResources().getDisplayMetrics()
);
this.iconSize = iconDefaultSize * textSize / Float.parseFloat(context.getString(R.string.list_size_default));
float iconDefaultSize = context.getResources().getDimension(R.dimen.list_icon_size_default);
this.iconSize = iconDefaultSize * textSize / textSizeDefault;
this.listSort = PreferencesUtil.getListSort(context);
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context);
this.ascendingSort = PreferencesUtil.getAscendingSort(context);
this.showUsernames = PreferencesUtil.showUsernamesListEntries(context);
}
/**
@@ -122,11 +145,23 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
public void rebuildList(PwGroup group) {
this.nodeSortedList.clear();
assignPreferences();
if (group != null) {
// TODO verify sort
try {
this.nodeSortedList.addAll(group.getDirectChildren());
} catch (Exception e) {
Log.e(TAG, "Can't add node elements to the list", e);
Toast.makeText(context, "Can't add node elements to the list : " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
/**
* Determine if the adapter contains or not any element
* @return true if the list is empty
*/
public boolean isEmpty() {
return nodeSortedList.size() <= 0;
}
/**
* Add a node to the list
* @param node Node to add
@@ -188,35 +223,51 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
public void onBindViewHolder(@NonNull BasicViewHolder holder, int position) {
PwNode subNode = nodeSortedList.get(position);
// Assign image
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
int iconColor = Color.BLACK;
switch (subNode.getType()) {
case GROUP:
iconColor = iconGroupColor;
break;
case ENTRY:
iconColor = iconEntryColor;
break;
}
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon());
int iconColor = Color.BLACK;
switch (subNode.getType()) {
case GROUP:
iconColor = iconGroupColor;
break;
case ENTRY:
iconColor = iconEntryColor;
break;
}
database.getDrawFactory().assignDatabaseIconTo(context, holder.icon, subNode.getIcon(), iconColor);
// Assign text
holder.text.setText(subNode.getDisplayTitle());
holder.text.setText(subNode.getTitle());
// Assign click
holder.container.setOnClickListener(
new OnNodeClickListener(subNode));
// Context menu
if (activateContextMenu) {
holder.container.setOnCreateContextMenuListener(
new ContextMenuBuilder(subNode, nodeMenuListener));
new ContextMenuBuilder(subNode, nodeMenuListener, readOnly));
}
// Add username
holder.subText.setText("");
holder.subText.setVisibility(View.GONE);
if (subNode.getType().equals(PwNode.Type.ENTRY)) {
PwEntry entry = (PwEntry) subNode;
entry.startToManageFieldReferences(database.getPwDatabase());
holder.text.setText(entry.getVisualTitle());
String username = entry.getUsername();
if (showUsernames && !username.isEmpty()) {
holder.subText.setVisibility(View.VISIBLE);
holder.subText.setText(username);
}
entry.stopToManageFieldReferences();
}
// Assign image and text size
// Relative size of the icon
holder.icon.getLayoutParams().height = ((int) iconSize);
holder.icon.getLayoutParams().width = ((int) iconSize);
holder.text.setTextSize(textSize);
holder.subText.setTextSize(subtextSize);
}
@Override
@@ -280,36 +331,56 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private PwNode node;
private NodeMenuListener menuListener;
private boolean readOnly;
ContextMenuBuilder(PwNode node, NodeMenuListener menuListener) {
ContextMenuBuilder(PwNode node, NodeMenuListener menuListener, boolean readOnly) {
this.menuListener = menuListener;
this.node = node;
this.readOnly = readOnly;
}
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
menuInflater.inflate(R.menu.node_menu, contextMenu);
// TODO COPY For Group
if (node.getType().equals(PwNode.Type.GROUP)) {
contextMenu.removeItem(R.id.menu_copy);
}
// Opening
MenuItem menuItem = contextMenu.findItem(R.id.menu_open);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
if (!App.getDB().isReadOnly() && !node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
// Edition
// Edition
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_edit);
} else {
menuItem = contextMenu.findItem(R.id.menu_edit);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Copy (not for group)
if (node.getType().equals(PwNode.Type.ENTRY)) {
menuItem = contextMenu.findItem(R.id.menu_copy);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}
// Move
}
// Copy (not for group)
if (readOnly
|| isASearchResult
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())
|| node.getType().equals(PwNode.Type.GROUP)) {
// TODO COPY For Group
contextMenu.removeItem(R.id.menu_copy);
} else {
menuItem = contextMenu.findItem(R.id.menu_copy);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}
// Move
if (readOnly
|| isASearchResult
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_move);
} else {
menuItem = contextMenu.findItem(R.id.menu_move);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Deletion
}
// Deletion
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_delete);
} else {
menuItem = contextMenu.findItem(R.id.menu_delete);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwIconFactory;
import com.kunzisoft.keepass.database.cursor.EntryCursor;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import java.util.UUID;
public class SearchEntryCursorAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
private Database database;
private boolean displayUsername;
private int iconColor;
public SearchEntryCursorAdapter(Context context, Database database) {
super(context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
cursorInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
this.database = database;
// Get the icon color
int[] attrTextColor = {R.attr.textColorInverse};
TypedArray taTextColor = context.getTheme().obtainStyledAttributes(attrTextColor);
this.iconColor = taTextColor.getColor(0, Color.WHITE);
taTextColor.recycle();
reInit(context);
}
public void reInit(Context context) {
this.displayUsername = PreferencesUtil.showUsernamesListEntries(context);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = cursorInflater.inflate(R.layout.search_entry, parent ,false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon);
viewHolder.textViewTitle = view.findViewById(R.id.entry_text);
viewHolder.textViewSubTitle = view.findViewById(R.id.entry_subtext);
view.setTag(viewHolder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// Retrieve elements from cursor
UUID uuid = new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)));
PwIconFactory iconFactory = database.getPwDatabase().getIconFactory();
PwIcon icon = iconFactory.getIcon(
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
if (icon.isUnknown()) {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
if (icon.isUnknown())
icon = iconFactory.getKeyIcon();
}
String title = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE) );
String username = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME) );
String url = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL) );
ViewHolder viewHolder = (ViewHolder) view.getTag();
// Assign image
database.getDrawFactory().assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon, iconColor);
// Assign title
String showTitle = PwEntry.getVisualTitle(false, title, username, url, uuid);
viewHolder.textViewTitle.setText(showTitle);
if (displayUsername && !username.isEmpty()) {
viewHolder.textViewSubTitle.setText(String.format("(%s)", username));
} else {
viewHolder.textViewSubTitle.setText("");
}
}
private static class ViewHolder {
ImageView imageViewIcon;
TextView textViewTitle;
TextView textViewSubTitle;
}
@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return database.searchEntry(constraint.toString());
}
public PwEntry getEntryFromPosition(int position) {
PwEntry pwEntry = null;
Cursor cursor = this.getCursor();
if (cursor.moveToFirst()
&&
cursor.move(position)) {
pwEntry = database.createEntry();
database.populateEntry(pwEntry, (EntryCursor) cursor);
}
return pwEntry;
}
}

View File

@@ -19,20 +19,57 @@
*/
package com.kunzisoft.keepass.database;
import java.io.Serializable;
import android.os.Parcel;
import android.os.Parcelable;
import com.kunzisoft.keepass.utils.MemUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class AutoType implements Cloneable, Serializable {
public class AutoType implements Cloneable, Parcelable {
private static final long OBF_OPT_NONE = 0;
public boolean enabled = true;
public long obfuscationOptions = OBF_OPT_NONE;
public String defaultSequence = "";
private HashMap<String, String> windowSeqPairs = new HashMap<>();
public AutoType() {}
public AutoType(Parcel in) {
enabled = in.readByte() != 0;
obfuscationOptions = in.readLong();
defaultSequence = in.readString();
windowSeqPairs = MemUtil.readStringParcelableMap(in);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (enabled ? 1 : 0));
dest.writeLong(obfuscationOptions);
dest.writeString(defaultSequence);
MemUtil.writeStringParcelableMap(dest, windowSeqPairs);
}
public static final Parcelable.Creator<AutoType> CREATOR = new Parcelable.Creator<AutoType>() {
@Override
public AutoType createFromParcel(Parcel in) {
return new AutoType(in);
}
@Override
public AutoType[] newArray(int size) {
return new AutoType[size];
}
};
@SuppressWarnings("unchecked")
public AutoType clone() {
AutoType auto;

View File

@@ -21,19 +21,21 @@ package com.kunzisoft.keepass.database;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
import com.kunzisoft.keepass.database.cursor.EntryCursor;
import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException;
import com.kunzisoft.keepass.database.exception.InvalidDBException;
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
import com.kunzisoft.keepass.database.load.Importer;
import com.kunzisoft.keepass.database.load.ImporterFactory;
import com.kunzisoft.keepass.database.save.PwDbOutput;
import com.kunzisoft.keepass.database.search.SearchDbHelper;
import com.kunzisoft.keepass.icons.IconDrawableFactory;
import com.kunzisoft.keepass.search.SearchDbHelper;
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
import com.kunzisoft.keepass.utils.UriUtil;
@@ -179,13 +181,17 @@ public class Database {
}
public PwGroup search(String str) {
return search(str, Integer.MAX_VALUE);
}
public PwGroup search(String str, int max) {
if (searchHelper == null) { return null; }
try {
switch (pm.getVersion()) {
case V3:
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str);
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str, max);
case V4:
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str);
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str, max);
}
} catch (Exception e) {
Log.e(TAG, "Search can't be performed with this SearchHelper", e);
@@ -193,6 +199,52 @@ public class Database {
return null;
}
public Cursor searchEntry(String query) {
final EntryCursor cursor = new EntryCursor();
// TODO real content provider
if (!query.isEmpty()) {
PwGroup searchResult = search(query, 6);
PwVersion version = getPwDatabase().getVersion();
for (int i=0; i < searchResult.numbersOfChildEntries(); i++) {
PwEntry entry = searchResult.getChildEntryAt(i);
if (!entry.isMetaStream()) { // TODO metastream
try {
switch (version) {
case V3:
cursor.addEntry((PwEntryV3) entry);
continue;
case V4:
cursor.addEntry((PwEntryV4) entry);
}
} catch (Exception e) {
Log.e(TAG, "This version of PwEntry can't be added to the cursor", e);
}
}
}
}
return cursor;
}
public void populateEntry(PwEntry pwEntry, EntryCursor cursor) {
PwIconFactory iconFactory = getPwDatabase().getIconFactory();
try {
switch (getPwDatabase().getVersion()) {
case V3:
cursor.populateEntry((PwEntryV3) pwEntry, iconFactory);
break;
case V4:
// TODO invert field reference manager
pwEntry.startToManageFieldReferences(getPwDatabase());
cursor.populateEntry((PwEntryV4) pwEntry, iconFactory);
pwEntry.stopToManageFieldReferences();
break;
}
} catch (Exception e) {
Log.e(TAG, "This version of PwGroup can't be populated", e);
}
}
public void saveData(Context ctx) throws IOException, PwDbOutputException {
saveData(ctx, mUri);
}
@@ -464,19 +516,22 @@ public class Database {
}
}
public PwEntry createEntry(PwGroup parent) {
PwEntry newPwEntry = null;
public PwEntry createEntry() {
return createEntry(null);
}
public PwEntry createEntry(@Nullable PwGroup parent) {
try {
switch (getPwDatabase().getVersion()) {
case V3:
newPwEntry = new PwEntryV3((PwGroupV3) parent);
return new PwEntryV3((PwGroupV3) parent);
case V4:
newPwEntry = new PwEntryV4((PwGroupV4) parent);
return new PwEntryV4((PwGroupV4) parent);
}
} catch (Exception e) {
Log.e(TAG, "This version of PwEntry can't be created", e);
}
return newPwEntry;
return null;
}
public PwGroup createGroup(PwGroup parent) {

View File

@@ -19,9 +19,12 @@
*/
package com.kunzisoft.keepass.database;
import com.kunzisoft.keepass.database.security.ProtectedString;
import android.os.Parcel;
import android.os.Parcelable;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.utils.MemUtil;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -32,7 +35,7 @@ import static com.kunzisoft.keepass.database.PwEntryV4.STR_TITLE;
import static com.kunzisoft.keepass.database.PwEntryV4.STR_URL;
import static com.kunzisoft.keepass.database.PwEntryV4.STR_USERNAME;
public class ExtraFields implements Serializable, Cloneable {
public class ExtraFields implements Parcelable, Cloneable {
private Map<String, ProtectedString> fields;
@@ -40,6 +43,32 @@ public class ExtraFields implements Serializable, Cloneable {
fields = new HashMap<>();
}
public ExtraFields(Parcel in) {
fields = MemUtil.readStringParcelableMap(in, ProtectedString.class);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
MemUtil.writeStringParcelableMap(dest, flags, fields);
}
public static final Parcelable.Creator<ExtraFields> CREATOR = new Parcelable.Creator<ExtraFields>() {
@Override
public ExtraFields createFromParcel(Parcel in) {
return new ExtraFields(in);
}
@Override
public ExtraFields[] newArray(int size) {
return new ExtraFields[size];
}
};
public boolean containsCustomFields() {
return !getCustomProtectedFields().keySet().isEmpty();
}

View File

@@ -35,7 +35,7 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwGroupDB, PwEntryDB>,
public abstract class PwDatabase<PwGroupDB extends PwGroup<PwGroupDB, PwEntryDB>,
PwEntryDB extends PwEntry<PwGroupDB>> {
public static final UUID UUID_ZERO = new UUID(0,0);

View File

@@ -91,7 +91,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
PwGroupV3 group = createGroup();
group.setId(newGroupId());
group.setName(name);
group.setIcon(iconFactory.getIcon(iconId));
group.setIconStandard(iconFactory.getIcon(iconId));
addGroupTo(group, parent);
}
@@ -176,7 +176,7 @@ public class PwDatabaseV3 extends PwDatabase<PwGroupV3, PwEntryV3> {
*/
for (int i = 0; i < entries.size(); i++) {
PwEntryV3 ent = entries.get(i);
if (ent.getGroupId() == parent.getGroupId())
if (ent.getParent().getGroupId() == parent.getGroupId())
kids.add(ent);
}
return kids;

View File

@@ -19,10 +19,12 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import android.os.Parcelable;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.utils.Types;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -33,15 +35,14 @@ import java.util.Date;
* @author bpellin
*
*/
public class PwDate implements Cloneable, Serializable {
public class PwDate implements Cloneable, Parcelable {
private static final int DATE_SIZE = 5;
private boolean cDateBuilt = false;
private static final int DATE_SIZE = 5;
private Date jDate;
private boolean jDateBuilt = false;
private Date jDate;
private byte[] cDate;
transient private byte[] cDate;
transient private boolean cDateBuilt = false;
public static final Date NEVER_EXPIRE = getNeverExpire();
public static final Date DEFAULT_DATE = getDefaultDate();
@@ -93,6 +94,35 @@ public class PwDate implements Cloneable, Serializable {
jDate = new Date();
jDateBuilt = true;
}
protected PwDate(Parcel in) {
jDate = (Date) in.readSerializable();
jDateBuilt = in.readByte() != 0;
cDateBuilt = false;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(getDate());
dest.writeByte((byte) (jDateBuilt ? 1 : 0));
}
public static final Creator<PwDate> CREATOR = new Creator<PwDate>() {
@Override
public PwDate createFromParcel(Parcel in) {
return new PwDate(in);
}
@Override
public PwDate[] newArray(int size) {
return new PwDate[size];
}
};
@Override
public PwDate clone() {

View File

@@ -19,6 +19,8 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
import com.kunzisoft.keepass.database.security.ProtectedString;
@@ -30,6 +32,19 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
protected UUID uuid = PwDatabase.UUID_ZERO;
public PwEntry() {}
public PwEntry(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeSerializable(uuid);
}
@Override
protected void construct(Parent parent) {
super.construct(parent);
@@ -61,7 +76,7 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
}
public void startToManageFieldReferences(PwDatabase db) {}
public void endToManageFieldReferences() {}
public void stopToManageFieldReferences() {}
public abstract String getTitle();
public abstract void setTitle(String title);
@@ -82,15 +97,31 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
return getTitle().equals(PMS_TAN_ENTRY) && (getUsername().length() > 0);
}
@Override
public String getDisplayTitle() {
if ( isTan() ) {
return PMS_TAN_ENTRY + " " + getUsername();
} else {
return getTitle();
}
/**
* {@inheritDoc}
* Get the display title from an entry, <br />
* {@link #startToManageFieldReferences(PwDatabase)} and {@link #stopToManageFieldReferences()} must be called
* before and after {@link #getVisualTitle()}
*/
public String getVisualTitle() {
// only used to compare, don't car if it's a reference
return getVisualTitle(isTan(), getTitle(), getUsername(), getUrl(), getUUID());
}
public static String getVisualTitle(boolean isTAN, String title, String username, String url, UUID uuid) {
if ( isTAN ) {
return PMS_TAN_ENTRY + " " + username;
} else {
if (title.isEmpty())
if (url.isEmpty())
return uuid.toString();
else
return url;
else
return title;
}
}
// TODO encapsulate extra fields
/**

View File

@@ -42,6 +42,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
@@ -75,15 +77,11 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
private static final String PMS_ID_USER = "SYSTEM";
private static final String PMS_ID_URL = "$";
// TODO Parent ID to remove
private int groupId;
private String title;
private String username;
private byte[] password;
private String url;
private String additional;
/** A string describing what is in pBinaryData */
private String binaryDesc;
private byte[] binaryData;
@@ -94,12 +92,45 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
public PwEntryV3(PwGroupV3 p) {
construct(p);
groupId = ((PwGroupIdV3) this.parent.getId()).getId(); // TODO remove
}
public PwEntryV3(Parcel in) {
super(in);
title = in.readString();
username = in.readString();
in.readByteArray(password);
url = in.readString();
additional = in.readString();
binaryDesc = in.readString();
in.readByteArray(binaryData);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(title);
dest.writeString(username);
dest.writeByteArray(password);
dest.writeString(url);
dest.writeString(additional);
dest.writeString(binaryDesc);
dest.writeByteArray(binaryData);
}
public static final Creator<PwEntryV3> CREATOR = new Creator<PwEntryV3>() {
@Override
public PwEntryV3 createFromParcel(Parcel in) {
return new PwEntryV3(in);
}
@Override
public PwEntryV3[] newArray(int size) {
return new PwEntryV3[size];
}
};
protected void updateWith(PwEntryV3 source) {
super.assign(source);
groupId = source.groupId;
title = source.title;
username = source.username;
@@ -145,12 +176,9 @@ public class PwEntryV3 extends PwEntry<PwGroupV3> {
return newEntry;
}
public int getGroupId() {
return groupId;
}
public void setGroupId(int groupId) {
this.groupId = groupId;
this.parent = new PwGroupV3();
this.parent.setGroupId(groupId);
}
@Override

View File

@@ -19,8 +19,11 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import com.kunzisoft.keepass.database.security.ProtectedBinary;
import com.kunzisoft.keepass.database.security.ProtectedString;
import com.kunzisoft.keepass.utils.MemUtil;
import com.kunzisoft.keepass.utils.SprEngineV4;
import java.util.ArrayList;
@@ -37,7 +40,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
public static final String STR_URL = "URL";
public static final String STR_NOTES = "Notes";
// To decode each field not serializable
// To decode each field not parcelable
private transient PwDatabaseV4 mDatabase = null;
private transient boolean mDecodeRef = false;
@@ -45,7 +48,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
private long usageCount = 0;
private PwDate parentGroupLastMod = new PwDate();
private Map<String, String> customData = new HashMap<>();
private ExtraFields fields = new ExtraFields();
private HashMap<String, ProtectedBinary> binaries = new HashMap<>();
private String foregroundColor = "";
@@ -53,7 +55,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
private String overrideURL = "";
private AutoType autoType = new AutoType();
private ArrayList<PwEntryV4> history = new ArrayList<>();
private String url = "";
private String additional = "";
private String tags = "";
@@ -71,8 +72,8 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
customIcon = source.customIcon;
usageCount = source.usageCount;
parentGroupLastMod = source.parentGroupLastMod;
// TODO customData
customData.clear();
customData.putAll(source.customData); // Add all custom elements in map
fields = source.fields;
binaries = source.binaries;
foregroundColor = source.foregroundColor;
@@ -80,12 +81,61 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
overrideURL = source.overrideURL;
autoType = source.autoType;
history = source.history;
url = source.url;
additional = source.additional;
tags = source.tags;
}
public PwEntryV4(Parcel in) {
super(in);
customIcon = in.readParcelable(PwIconCustom.class.getClassLoader());
usageCount = in.readLong();
parentGroupLastMod = in.readParcelable(PwDate.class.getClassLoader());
customData = MemUtil.readStringParcelableMap(in);
fields = in.readParcelable(ExtraFields.class.getClassLoader());
// TODO binaries takes too much memory for parcelable
// binaries = MemUtil.readStringParcelableMap(in, ProtectedBinary.class);
foregroundColor = in.readString();
backgroupColor = in.readString();
overrideURL = in.readString();
autoType = in.readParcelable(AutoType.class.getClassLoader());
history = in.readArrayList(PwEntryV4.class.getClassLoader()); // TODO verify
url = in.readString();
additional = in.readString();
tags = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(customIcon, flags);
dest.writeLong(usageCount);
dest.writeParcelable(parentGroupLastMod, flags);
MemUtil.writeStringParcelableMap(dest, customData);
dest.writeParcelable(fields, flags);
// TODO MemUtil.writeStringParcelableMap(dest, flags, binaries);
dest.writeString(foregroundColor);
dest.writeString(backgroupColor);
dest.writeString(overrideURL);
dest.writeParcelable(autoType, flags);
dest.writeList(history);
dest.writeString(url);
dest.writeString(additional);
dest.writeString(tags);
}
public static final Creator<PwEntryV4> CREATOR = new Creator<PwEntryV4>() {
@Override
public PwEntryV4 createFromParcel(Parcel in) {
return new PwEntryV4(in);
}
@Override
public PwEntryV4[] newArray(int size) {
return new PwEntryV4[size];
}
};
@SuppressWarnings("unchecked")
@Override
public PwEntryV4 clone() {
@@ -120,7 +170,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
}
@Override
public void endToManageFieldReferences() {
public void stopToManageFieldReferences() {
this.mDatabase = null;
this.mDecodeRef = false;
}
@@ -156,41 +206,31 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
@Override
public void setTitle(String title) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectTitle;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectTitle;
setProtectedString(STR_TITLE, title, protect);
}
@Override
public void setUsername(String user) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectUserName;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUserName;
setProtectedString(STR_USERNAME, user, protect);
}
@Override
public void setPassword(String pass) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectPassword;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectPassword;
setProtectedString(STR_PASSWORD, pass, protect);
}
@Override
public void setUrl(String url) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectUrl;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUrl;
setProtectedString(STR_URL, url, protect);
}
@Override
public void setNotes(String notes) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectNotes;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectNotes;
setProtectedString(STR_NOTES, notes, protect);
}
@@ -202,14 +242,6 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
fields.putProtectedString(key, value, protect);
}
public PwIconCustom getCustomIcon() {
return customIcon;
}
public void setCustomIcon(PwIconCustom icon) {
this.customIcon = icon;
}
public PwDate getLocationChanged() {
return parentGroupLastMod;
}
@@ -236,15 +268,28 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
return decodeRefKey(mDecodeRef, STR_URL);
}
@Override
@Override
public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
if (customIcon == null || customIcon.isUnknown()) {
return super.getIcon();
} else {
return customIcon;
}
}
public void setIconCustom(PwIconCustom icon) {
this.customIcon = icon;
}
public PwIconCustom getIconCustom() {
return customIcon;
}
public void setIconStandard(PwIconStandard icon) {
this.icon = icon;
this.customIcon = PwIconCustom.ZERO;
}
@Override
public boolean allowExtraFields() {
return true;

View File

@@ -19,16 +19,34 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.util.ArrayList;
import java.util.List;
public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup, ChildEntry extends PwEntry>
extends PwNode<Parent> {
public abstract class PwGroup<GroupG extends PwGroup, EntryE extends PwEntry>
extends PwNode<GroupG> {
protected String name = "";
protected List<ChildGroup> childGroups = new ArrayList<>();
protected List<ChildEntry> childEntries = new ArrayList<>();
// TODO verify children not needed
transient protected List<GroupG> childGroups = new ArrayList<>();
transient protected List<EntryE> childEntries = new ArrayList<>();
protected PwGroup() {
super();
}
protected PwGroup(Parcel in) {
super(in);
name = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(name);
}
@Override
public PwGroup clone() {
@@ -36,48 +54,48 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
return (PwGroup) super.clone();
}
protected void assign(PwGroup<Parent, ChildGroup, ChildEntry> source) {
protected void assign(PwGroup<GroupG, EntryE> source) {
super.assign(source);
name = source.name;
}
public List<ChildGroup> getChildGroups() {
public List<GroupG> getChildGroups() {
return childGroups;
}
public List<ChildEntry> getChildEntries() {
public List<EntryE> getChildEntries() {
return childEntries;
}
public void setGroups(List<ChildGroup> groups) {
public void setGroups(List<GroupG> groups) {
childGroups = groups;
}
public void setEntries(List<ChildEntry> entries) {
public void setEntries(List<EntryE> entries) {
childEntries = entries;
}
public void addChildGroup(ChildGroup group) {
public void addChildGroup(GroupG group) {
this.childGroups.add(group);
}
public void addChildEntry(ChildEntry entry) {
public void addChildEntry(EntryE entry) {
this.childEntries.add(entry);
}
public ChildGroup getChildGroupAt(int number) {
public GroupG getChildGroupAt(int number) {
return this.childGroups.get(number);
}
public ChildEntry getChildEntryAt(int number) {
public EntryE getChildEntryAt(int number) {
return this.childEntries.get(number);
}
public void removeChildGroup(ChildGroup group) {
public void removeChildGroup(GroupG group) {
this.childGroups.remove(group);
}
public void removeChildEntry(ChildEntry entry) {
public void removeChildEntry(EntryE entry) {
this.childEntries.remove(entry);
}
@@ -101,7 +119,7 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
public List<PwNode> getDirectChildren() {
List<PwNode> children = new ArrayList<>();
children.addAll(childGroups);
for(ChildEntry child : childEntries) {
for(EntryE child : childEntries) {
if (!child.isMetaStream())
children.add(child);
}
@@ -112,10 +130,18 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
public abstract void setId(PwGroupId id);
@Override
public String getDisplayTitle() {
protected String getVisualTitle() {
return getTitle();
}
@Override
public String getTitle() {
return getName();
}
/**
* The same thing as {@link #getTitle()}
*/
public String getName() {
return name;
}
@@ -128,15 +154,15 @@ public abstract class PwGroup<Parent extends PwGroup, ChildGroup extends PwGroup
return false;
}
public boolean preOrderTraverseTree(GroupHandler<ChildGroup> groupHandler,
EntryHandler<ChildEntry> entryHandler) {
public boolean preOrderTraverseTree(GroupHandler<GroupG> groupHandler,
EntryHandler<EntryE> entryHandler) {
if (entryHandler != null) {
for (ChildEntry entry : childEntries) {
for (EntryE entry : childEntries) {
if (!entryHandler.operate(entry)) return false;
}
}
for (ChildGroup group : childGroups) {
for (GroupG group : childGroups) {
if ((groupHandler != null) && !groupHandler.operate(group)) return false;
group.preOrderTraverseTree(groupHandler, entryHandler);
}

View File

@@ -19,8 +19,20 @@
*/
package com.kunzisoft.keepass.database;
import java.io.Serializable;
import android.os.Parcel;
import android.os.Parcelable;
public abstract class PwGroupId implements Serializable {
public abstract class PwGroupId implements Parcelable {
public PwGroupId() {}
public PwGroupId(Parcel in) {}
@Override
public void writeToParcel(Parcel dest, int flags) {}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -19,13 +19,39 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
public class PwGroupIdV3 extends PwGroupId {
private int id;
public PwGroupIdV3(int i) {
id = i;
public PwGroupIdV3(int groupId) {
super();
this.id = groupId;
}
public PwGroupIdV3(Parcel in) {
super(in);
id = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(id);
}
public static final Creator<PwGroupIdV3> CREATOR = new Creator<PwGroupIdV3>() {
@Override
public PwGroupIdV3 createFromParcel(Parcel in) {
return new PwGroupIdV3(in);
}
@Override
public PwGroupIdV3[] newArray(int size) {
return new PwGroupIdV3[size];
}
};
@Override
public boolean equals(Object compare) {

View File

@@ -19,15 +19,42 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.util.UUID;
public class PwGroupIdV4 extends PwGroupId {
private UUID uuid;
public PwGroupIdV4(UUID u) {
uuid = u;
public PwGroupIdV4(UUID uuid) {
super();
this.uuid = uuid;
}
public PwGroupIdV4(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeSerializable(uuid);
}
public static final Creator<PwGroupIdV4> CREATOR = new Creator<PwGroupIdV4>() {
@Override
public PwGroupIdV4 createFromParcel(Parcel in) {
return new PwGroupIdV4(in);
}
@Override
public PwGroupIdV4[] newArray(int size) {
return new PwGroupIdV4[size];
}
};
@Override
public boolean equals(Object id) {
if ( ! (id instanceof PwGroupIdV4) ) {
@@ -36,12 +63,12 @@ public class PwGroupIdV4 extends PwGroupId {
PwGroupIdV4 v4 = (PwGroupIdV4) id;
return uuid.equals(v4.uuid);
}
@Override
public int hashCode() {
return uuid.hashCode();
}
public UUID getId() {
return uuid;
}

View File

@@ -20,19 +20,13 @@
package com.kunzisoft.keepass.database;
/**
* @author Brian Pellin <bpellin@gmail.com>
* @author Naomaru Itoi <nao@phoneid.org>
* @author Bill Zwicky <wrzwicky@pobox.com>
* @author Dominik Reichl <dominik.reichl@t-online.de>
*/
public class PwGroupV3 extends PwGroup<PwGroupV3, PwGroupV3, PwEntryV3> {
import android.os.Parcel;
public class PwGroupV3 extends PwGroup<PwGroupV3, PwEntryV3> {
// for tree traversing
private int groupId;
private int level = 0; // short
/** Used by KeePass internally, don't use */
private int flags;
@@ -40,6 +34,33 @@ public class PwGroupV3 extends PwGroup<PwGroupV3, PwGroupV3, PwEntryV3> {
super();
}
public PwGroupV3(Parcel in) {
super(in);
groupId = in.readInt();
level = in.readInt();
flags = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(groupId);
dest.writeInt(level);
dest.writeInt(flags);
}
public static final Creator<PwGroupV3> CREATOR = new Creator<PwGroupV3>() {
@Override
public PwGroupV3 createFromParcel(Parcel in) {
return new PwGroupV3(in);
}
@Override
public PwGroupV3[] newArray(int size) {
return new PwGroupV3[size];
}
};
public PwGroupV3(PwGroupV3 p) {
construct(p);
}

View File

@@ -19,11 +19,15 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import com.kunzisoft.keepass.utils.MemUtil;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implements ITimeLogger {
public class PwGroupV4 extends PwGroup<PwGroupV4, PwEntryV4> implements ITimeLogger {
public static final boolean DEFAULT_SEARCHING_ENABLED = true;
@@ -32,9 +36,7 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
private long usageCount = 0;
private PwDate parentGroupLastMod = new PwDate();
private Map<String, String> customData = new HashMap<>();
private boolean expires = false;
private String notes = "";
private boolean isExpanded = true;
private String defaultAutoTypeSequence = "";
@@ -57,6 +59,53 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
this.icon = icon;
}
public PwGroupV4(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
customIcon = in.readParcelable(PwIconCustom.class.getClassLoader());
usageCount = in.readLong();
parentGroupLastMod = in.readParcelable(PwDate.class.getClassLoader());
// TODO customData = MemUtil.readStringParcelableMap(in);
expires = in.readByte() != 0;
notes = in.readString();
isExpanded = in.readByte() != 0;
defaultAutoTypeSequence = in.readString();
byte autoTypeByte = in.readByte();
enableAutoType = (autoTypeByte == -1) ? null : autoTypeByte != 0;
byte enableSearchingByte = in.readByte();
enableSearching = (enableSearchingByte == -1) ? null : enableSearchingByte != 0;
lastTopVisibleEntry = (UUID) in.readSerializable();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeSerializable(uuid);
dest.writeParcelable(customIcon, flags);
dest.writeLong(usageCount);
dest.writeParcelable(parentGroupLastMod, flags);
// TODO MemUtil.writeStringParcelableMap(dest, customData);
dest.writeByte((byte) (expires ? 1 : 0));
dest.writeString(notes);
dest.writeByte((byte) (isExpanded ? 1 : 0));
dest.writeString(defaultAutoTypeSequence);
dest.writeByte((byte) (enableAutoType == null ? -1 : (enableAutoType ? 1 : 0)));
dest.writeByte((byte) (enableAutoType == null ? -1 : (enableAutoType ? 1 : 0)));
dest.writeSerializable(lastTopVisibleEntry);
}
public static final Creator<PwGroupV4> CREATOR = new Creator<PwGroupV4>() {
@Override
public PwGroupV4 createFromParcel(Parcel in) {
return new PwGroupV4(in);
}
@Override
public PwGroupV4[] newArray(int size) {
return new PwGroupV4[size];
}
};
protected void updateWith(PwGroupV4 source) {
super.assign(source);
uuid = source.uuid;
@@ -120,14 +169,6 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
this.uuid = uuid;
}
public PwIconCustom getCustomIcon() {
return customIcon;
}
public void setCustomIcon(PwIconCustom icon) {
this.customIcon = icon;
}
@Override
public PwGroupId getId() {
return new PwGroupIdV4(uuid);
@@ -176,13 +217,26 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
@Override
public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
if (customIcon == null || customIcon.getUUID().equals(PwDatabase.UUID_ZERO)) {
return super.getIcon();
} else {
return customIcon;
}
}
public PwIconCustom getIconCustom() {
return customIcon;
}
public void setIconCustom(PwIconCustom icon) {
this.customIcon = icon;
}
public void setIconStandard(PwIconStandard icon) { // TODO Encapsulate with PwEntryV4
this.icon = icon;
this.customIcon = PwIconCustom.ZERO;
}
public void putCustomData(String key, String value) {
customData.put(key, value);
}

View File

@@ -19,11 +19,23 @@
*/
package com.kunzisoft.keepass.database;
import java.io.Serializable;
import android.os.Parcel;
import android.os.Parcelable;
public abstract class PwIcon implements Serializable {
public abstract class PwIcon implements Parcelable {
public boolean isMetaStreamIcon() {
return false;
}
protected PwIcon() {}
protected PwIcon(Parcel in) {}
public abstract boolean isUnknown();
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -19,25 +19,71 @@
*/
package com.kunzisoft.keepass.database;
import android.os.Parcel;
import java.util.UUID;
public class PwIconCustom extends PwIcon {
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
public final UUID uuid;
public byte[] imageData;
private final UUID uuid;
transient private byte[] imageData;
public PwIconCustom(UUID u, byte[] data) {
uuid = u;
imageData = data;
public PwIconCustom(UUID uuid, byte[] data) {
super();
this.uuid = uuid;
this.imageData = data;
}
public PwIconCustom(PwIconCustom icon) {
super();
uuid = icon.uuid;
imageData = icon.imageData;
}
protected PwIconCustom(Parcel in) {
super(in);
uuid = (UUID) in.readSerializable();
// TODO Take too much memories
// in.readByteArray(imageData);
}
@Override
public boolean isUnknown() {
return uuid == null || this.equals(ZERO);
}
public UUID getUUID() {
return uuid;
}
public byte[] getImageData() {
return imageData;
}
public void setImageData(byte[] imageData) {
this.imageData = imageData;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(uuid);
// Too big for a parcelable dest.writeByteArray(imageData);
}
public static final Creator<PwIconCustom> CREATOR = new Creator<PwIconCustom>() {
@Override
public PwIconCustom createFromParcel(Parcel in) {
return new PwIconCustom(in);
}
@Override
public PwIconCustom[] newArray(int size) {
return new PwIconCustom[size];
}
};
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
@@ -55,10 +101,7 @@ public class PwIconCustom extends PwIcon {
return false;
PwIconCustom other = (PwIconCustom) obj;
if (uuid == null) {
if (other.uuid != null)
return false;
} else if (!uuid.equals(other.uuid))
return false;
return true;
return other.uuid == null;
} else return uuid.equals(other.uuid);
}
}

View File

@@ -37,6 +37,10 @@ public class PwIconFactory {
*/
private ReferenceMap customCache = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
public PwIconStandard getUnknownIcon() {
return getIcon(PwIconStandard.UNKNOWN);
}
public PwIconStandard getKeyIcon() {
return getIcon(PwIconStandard.KEY);
}
@@ -46,8 +50,8 @@ public class PwIconFactory {
}
public PwIconStandard getFolderIcon() {
return getIcon(PwIconStandard.FOLDER);
}
return getIcon(PwIconStandard.FOLDER);
}
public PwIconStandard getIcon(int iconId) {
PwIconStandard icon = (PwIconStandard) cache.get(iconId);
@@ -71,25 +75,8 @@ public class PwIconFactory {
return icon;
}
public PwIconCustom getIcon(UUID iconUuid, byte[] data) {
PwIconCustom icon = (PwIconCustom) customCache.get(iconUuid);
if (icon == null) {
icon = new PwIconCustom(iconUuid, data);
customCache.put(iconUuid, icon);
} else {
icon.imageData = data;
}
return icon;
}
public void setIconData(UUID iconUuid, byte[] data) {
getIcon(iconUuid, data);
}
public void put(PwIconCustom icon) {
customCache.put(icon.uuid, icon);
customCache.put(icon.getUUID(), icon);
}
}

View File

@@ -19,13 +19,20 @@
*/
package com.kunzisoft.keepass.database;
public class PwIconStandard extends PwIcon {
public final int iconId;
import android.os.Parcel;
public class PwIconStandard extends PwIcon {
private final int iconId;
public static final int UNKNOWN = -1;
public static final int KEY = 0;
public static final int TRASH = 43;
public static final int FOLDER = 48;
public PwIconStandard() {
this.iconId = KEY;
}
public PwIconStandard(int iconId) {
this.iconId = iconId;
}
@@ -34,6 +41,37 @@ public class PwIconStandard extends PwIcon {
this.iconId = icon.iconId;
}
protected PwIconStandard(Parcel in) {
super(in);
iconId = in.readInt();
}
@Override
public boolean isUnknown() {
return iconId == UNKNOWN;
}
public int getIconId() {
return iconId;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(iconId);
}
public static final Creator<PwIconStandard> CREATOR = new Creator<PwIconStandard>() {
@Override
public PwIconStandard createFromParcel(Parcel in) {
return new PwIconStandard(in);
}
@Override
public PwIconStandard[] newArray(int size) {
return new PwIconStandard[size];
}
};
@Override
public boolean isMetaStreamIcon() {
return iconId == 0;

View File

@@ -20,33 +20,69 @@
*/
package com.kunzisoft.keepass.database;
import org.joda.time.LocalDate;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
import com.kunzisoft.keepass.app.App;
import org.joda.time.LocalDate;
/**
* Abstract class who manage Groups and Entries
*/
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Serializable, Cloneable {
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Parcelable, Cloneable {
protected Parent parent = null;
protected PwIconStandard icon = new PwIconStandard(0);
protected PwIconStandard icon = new PwIconStandard();
protected PwDate creation = new PwDate();
protected PwDate lastMod = new PwDate();
protected PwDate lastAccess = new PwDate();
protected PwDate expireDate = PwDate.PW_NEVER_EXPIRE;
protected PwNode() {}
protected PwNode(Parcel in) {
// TODO better technique ?
try {
PwGroupId pwGroupId = in.readParcelable(PwGroupId.class.getClassLoader());
parent = (Parent) App.getDB().getPwDatabase().getGroupByGroupId(pwGroupId);
} catch (Exception e) {
e.printStackTrace();
}
icon = in.readParcelable(PwIconStandard.class.getClassLoader());
creation = in.readParcelable(PwDate.class.getClassLoader());
lastMod = in.readParcelable(PwDate.class.getClassLoader());
lastAccess = in.readParcelable(PwDate.class.getClassLoader());
expireDate = in.readParcelable(PwDate.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
PwGroupId parentId = null;
if (parent != null)
parentId = parent.getId();
dest.writeParcelable(parentId, flags);
dest.writeParcelable(icon, flags);
dest.writeParcelable(creation, flags);
dest.writeParcelable(lastMod, flags);
dest.writeParcelable(lastAccess, flags);
dest.writeParcelable(expireDate, flags);
}
@Override
public int describeContents() {
return 0;
}
protected void construct(Parent parent) {
this.parent = parent;
}
protected void assign(PwNode<Parent> source) {
this.parent = source.parent;
this.icon = source.icon;
this.creation = source.creation;
this.lastMod = source.lastMod;
this.lastAccess = source.lastAccess;
@@ -59,9 +95,7 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
try {
newNode = (PwNode) super.clone();
// newNode.parent stay the same in copy
newNode.icon = new PwIconStandard(this.icon);
newNode.creation = creation.clone();
newNode.lastMod = lastMod.clone();
newNode.lastAccess = lastAccess.clone();
@@ -85,22 +119,27 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
public abstract Type getType();
/**
* @return Title to display as view
* @return Title
*/
public abstract String getDisplayTitle();
public abstract String getTitle();
/**
* @return Title to display, typically return alternative title if {@link #getTitle()} is empty
*/
protected abstract String getVisualTitle();
/**
* @return Visual icon
*/
public PwIcon getIcon() {
return icon;
return getIconStandard();
}
public PwIconStandard getIconStandard() {
return icon;
}
public void setIcon(PwIconStandard icon) {
public void setIconStandard(PwIconStandard icon) {
this.icon = icon;
}
@@ -176,7 +215,7 @@ public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger
*/
public boolean isContentVisuallyTheSame(PwNode o) {
return getType().equals(o.getType())
&& getDisplayTitle().equals(o.getDisplayTitle())
&& getVisualTitle().equals(o.getVisualTitle())
&& getIcon().equals(o.getIcon());
}

View File

@@ -109,8 +109,8 @@ public enum SortNodeEnum {
new EntryNameComparator(ascending),
object1,
object2,
object1.getDisplayTitle()
.compareToIgnoreCase(object2.getDisplayTitle()));
object1.getTitle()
.compareToIgnoreCase(object2.getTitle()));
}
}

View File

@@ -69,9 +69,7 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
public void run() {
try {
mDatabase.loadData(mContext, mUri, mPass, mKey, mStatus);
saveFileData(mUri, mKey);
} catch (ArcFourException e) {
catchError(e, R.string.error_arc4);
return;
@@ -85,8 +83,10 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
catchError(e, R.string.file_not_found);
return;
} catch (IOException e) {
Log.e(TAG, "Database can't be read", e);
finish(false, e.getMessage());
if (e.getMessage().contains("Hash failed with code"))
catchError(e, R.string.error_load_database_KDF_memory, true);
else
catchError(e, R.string.error_load_database, true);
return;
} catch (KeyFileEmptyException e) {
catchError(e, R.string.keyfile_is_empty);
@@ -107,22 +107,24 @@ public class LoadDatabaseRunnable extends RunnableOnFinish {
catchError(e, R.string.error_invalid_db);
return;
} catch (OutOfMemoryError e) {
String errorMessage = mContext.getString(R.string.error_out_of_memory);
Log.e(TAG, errorMessage, e);
finish(false, errorMessage);
catchError(e, R.string.error_out_of_memory);
return;
} catch (Exception e) {
Log.e(TAG, "Database can't be load", e);
finish(false, e.getMessage());
catchError(e, R.string.error_load_database, true);
return;
}
finish(true);
}
private void catchError(Exception e, @StringRes int messageId) {
private void catchError(Throwable e, @StringRes int messageId) {
catchError(e, messageId, false);
}
private void catchError(Throwable e, @StringRes int messageId, boolean addThrowableMessage) {
String errorMessage = mContext.getString(messageId);
Log.e(TAG, errorMessage, e);
if (addThrowableMessage)
errorMessage = errorMessage + " " + e.getLocalizedMessage();
finish(false, errorMessage);
}

View File

@@ -31,15 +31,15 @@ import java.util.List;
public class DeleteGroupRunnable extends ActionNodeDatabaseRunnable {
private PwGroup<PwGroup, PwGroup, PwEntry> mGroupToDelete;
private PwGroup<PwGroup, PwEntry> mGroupToDelete;
private PwGroup mParent;
private boolean mRecycle;
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish) {
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwEntry> group, AfterActionNodeOnFinish finish) {
this(ctx, db, group, finish, false);
}
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwGroup, PwEntry> group, AfterActionNodeOnFinish finish, boolean dontSave) {
public DeleteGroupRunnable(Context ctx, Database db, PwGroup<PwGroup, PwEntry> group, AfterActionNodeOnFinish finish, boolean dontSave) {
super(ctx, db, finish, dontSave);
mGroupToDelete = group;
}

View File

@@ -0,0 +1,125 @@
package com.kunzisoft.keepass.database.cursor;
import android.database.MatrixCursor;
import android.provider.BaseColumns;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwIconCustom;
import com.kunzisoft.keepass.database.PwIconFactory;
import com.kunzisoft.keepass.database.PwIconStandard;
import java.util.UUID;
public class EntryCursor extends MatrixCursor {
private long entryId;
public static final String _ID = BaseColumns._ID;
public static final String COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUID_most_significant_bits";
public static final String COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUID_least_significant_bits";
public static final String COLUMN_INDEX_TITLE = "title";
public static final String COLUMN_INDEX_ICON_STANDARD = "icon_standard";
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS = "icon_custom_UUID_most_significant_bits";
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS = "icon_custom_UUID_least_significant_bits";
public static final String COLUMN_INDEX_USERNAME = "username";
public static final String COLUMN_INDEX_PASSWORD = "password";
public static final String COLUMN_INDEX_URL = "URL";
public static final String COLUMN_INDEX_NOTES = "notes";
private ExtraFieldCursor extraFieldCursor;
public EntryCursor() {
super(new String[]{ _ID,
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
COLUMN_INDEX_TITLE,
COLUMN_INDEX_ICON_STANDARD,
COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS,
COLUMN_INDEX_USERNAME,
COLUMN_INDEX_PASSWORD,
COLUMN_INDEX_URL,
COLUMN_INDEX_NOTES});
entryId = 0;
extraFieldCursor = new ExtraFieldCursor();
}
public void addEntry(PwEntryV3 entry) {
addRow(new Object[] {entryId,
entry.getUUID().getMostSignificantBits(),
entry.getUUID().getLeastSignificantBits(),
entry.getTitle(),
entry.getIconStandard().getIconId(),
PwDatabase.UUID_ZERO.getMostSignificantBits(),
PwDatabase.UUID_ZERO.getLeastSignificantBits(),
entry.getUsername(),
entry.getPassword(),
entry.getUrl(),
entry.getNotes()});
entryId++;
}
public void addEntry(PwEntryV4 entry) {
addRow(new Object[] {entryId,
entry.getUUID().getMostSignificantBits(),
entry.getUUID().getLeastSignificantBits(),
entry.getTitle(),
entry.getIconStandard().getIconId(),
entry.getIconCustom().getUUID().getMostSignificantBits(),
entry.getIconCustom().getUUID().getLeastSignificantBits(),
entry.getUsername(),
entry.getPassword(),
entry.getUrl(),
entry.getNotes()});
entry.getFields().doActionToAllCustomProtectedField((key, value) -> {
extraFieldCursor.addExtraField(entryId, key, value);
});
entryId++;
}
private void populateEntryBaseVersion(PwEntry pwEntry, PwIconFactory iconFactory) {
pwEntry.setUUID(
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))));
pwEntry.setTitle(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE)));
PwIconStandard iconStandard = iconFactory.getIcon(getInt(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
pwEntry.setIconStandard(iconStandard);
pwEntry.setUsername(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME)));
pwEntry.setPassword(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_PASSWORD)));
pwEntry.setUrl(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_URL)));
pwEntry.setNotes(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_NOTES)));
}
public void populateEntry(PwEntryV3 pwEntry, PwIconFactory iconFactory) {
populateEntryBaseVersion(pwEntry, iconFactory);
}
public void populateEntry(PwEntryV4 pwEntry, PwIconFactory iconFactory) {
populateEntryBaseVersion(pwEntry, iconFactory);
// Retrieve custom icon
PwIconCustom iconCustom = iconFactory.getIcon(
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
pwEntry.setIconCustom(iconCustom);
// Retrieve extra fields
if (extraFieldCursor.moveToFirst()) {
while (!extraFieldCursor.isAfterLast()) {
// Add a new extra field only if entryId is the one we want
if (extraFieldCursor.getLong(extraFieldCursor.getColumnIndex(ExtraFieldCursor.FOREIGN_KEY_ENTRY_ID))
== getLong(getColumnIndex(EntryCursor._ID))) {
extraFieldCursor.populateExtraFieldInEntry(pwEntry);
}
extraFieldCursor.moveToNext();
}
}
}
}

View File

@@ -0,0 +1,43 @@
package com.kunzisoft.keepass.database.cursor;
import android.database.MatrixCursor;
import android.provider.BaseColumns;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.security.ProtectedString;
public class ExtraFieldCursor extends MatrixCursor {
private long fieldId;
public static final String _ID = BaseColumns._ID;
public static final String FOREIGN_KEY_ENTRY_ID = "entry_id";
public static final String COLUMN_LABEL = "label";
public static final String COLUMN_PROTECTION = "protection";
public static final String COLUMN_VALUE = "value";
public ExtraFieldCursor() {
super(new String[]{ _ID,
FOREIGN_KEY_ENTRY_ID,
COLUMN_LABEL,
COLUMN_PROTECTION,
COLUMN_VALUE});
fieldId = 0;
}
public synchronized void addExtraField(long entryId, String label, ProtectedString value) {
addRow(new Object[] {fieldId,
entryId,
label,
(value.isProtected()) ? 1 : 0,
value.toString()});
fieldId++;
}
public void populateExtraFieldInEntry(PwEntryV4 pwEntry) {
pwEntry.addExtraField(getString(getColumnIndex(ExtraFieldCursor.COLUMN_LABEL)),
new ProtectedString((getInt(getColumnIndex(ExtraFieldCursor.COLUMN_PROTECTION)) > 0),
getString(getColumnIndex(ExtraFieldCursor.COLUMN_VALUE))));
}
}

View File

@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.SearchParameters;
import com.kunzisoft.keepass.database.SearchParametersV4;
import com.kunzisoft.keepass.database.search.SearchParameters;
import com.kunzisoft.keepass.database.search.SearchParametersV4;
import java.util.Iterator;

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.SearchParameters;
import com.kunzisoft.keepass.database.search.SearchParameters;
import java.util.NoSuchElementException;

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.SearchParametersV4;
import com.kunzisoft.keepass.database.search.SearchParametersV4;
import com.kunzisoft.keepass.database.security.ProtectedString;
import java.util.Iterator;
@@ -78,18 +78,19 @@ public class EntrySearchStringIteratorV4 extends EntrySearchStringIterator {
}
private boolean searchInField(String key) {
if (key.equals(PwEntryV4.STR_TITLE)) {
return sp.searchInTitles;
} else if (key.equals(PwEntryV4.STR_USERNAME)) {
return sp.searchInUserNames;
} else if (key.equals(PwEntryV4.STR_PASSWORD)) {
return sp.searchInPasswords;
} else if (key.equals(PwEntryV4.STR_URL)) {
return sp.searchInUrls;
} else if (key.equals(PwEntryV4.STR_NOTES)) {
return sp.searchInNotes;
} else {
return sp.searchInOther;
switch (key) {
case PwEntryV4.STR_TITLE:
return sp.searchInTitles;
case PwEntryV4.STR_USERNAME:
return sp.searchInUserNames;
case PwEntryV4.STR_PASSWORD:
return sp.searchInPasswords;
case PwEntryV4.STR_URL:
return sp.searchInUrls;
case PwEntryV4.STR_NOTES:
return sp.searchInNotes;
default:
return sp.searchInOther;
}
}

View File

@@ -316,7 +316,7 @@ public class ImporterV3 extends Importer {
grp.setExpiryTime(new PwDate(buf, offset));
break;
case 0x0007 :
grp.setIcon(db.getIconFactory().getIcon(LEDataInputStream.readInt(buf, offset)));
grp.setIconStandard(db.getIconFactory().getIcon(LEDataInputStream.readInt(buf, offset)));
break;
case 0x0008 :
grp.setLevel(LEDataInputStream.readUShort(buf, offset));
@@ -353,7 +353,7 @@ public class ImporterV3 extends Importer {
iconId = 0;
}
ent.setIcon(db.getIconFactory().getIcon(iconId));
ent.setIconStandard(db.getIconFactory().getIcon(iconId));
break;
case 0x0004 :
ent.setTitle(Types.readCString(buf, offset));

View File

@@ -556,9 +556,9 @@ public class ImporterV4 extends Importer {
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemNotes) ) {
ctxGroup.setNotes(ReadString(xpp));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
ctxGroup.setIcon(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
ctxGroup.setIconStandard(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
ctxGroup.setCustomIcon(db.getIconFactory().getIcon(ReadUuid(xpp)));
ctxGroup.setIconCustom(db.getIconFactory().getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemTimes) ) {
return SwitchContext(ctx, KdbContext.GroupTimes, xpp);
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIsExpanded) ) {
@@ -611,9 +611,9 @@ public class ImporterV4 extends Importer {
if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemUuid) ) {
ctxEntry.setUUID(ReadUuid(xpp));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemIcon) ) {
ctxEntry.setIcon(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
ctxEntry.setIconStandard(db.getIconFactory().getIcon((int)ReadUInt(xpp, 0)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemCustomIconID) ) {
ctxEntry.setCustomIcon(db.getIconFactory().getIcon(ReadUuid(xpp)));
ctxEntry.setIconCustom(db.getIconFactory().getIcon(ReadUuid(xpp)));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemFgColor) ) {
ctxEntry.setForegroundColor(ReadString(xpp));
} else if ( name.equalsIgnoreCase(PwDatabaseV4XML.ElemBgColor) ) {

View File

@@ -350,10 +350,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
writeObject(PwDatabaseV4XML.ElemUuid, group.getUUID());
writeObject(PwDatabaseV4XML.ElemName, group.getName());
writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes());
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().iconId);
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().getIconId());
if (!group.getCustomIcon().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getCustomIcon().uuid);
if (!group.getIconCustom().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getIconCustom().getUUID());
}
writeList(PwDatabaseV4XML.ElemTimes, group);
@@ -375,10 +375,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
xml.startTag(null, PwDatabaseV4XML.ElemEntry);
writeObject(PwDatabaseV4XML.ElemUuid, entry.getUUID());
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().iconId);
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().getIconId());
if (!entry.getCustomIcon().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getCustomIcon().uuid);
if (!entry.getIconCustom().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getIconCustom().getUUID());
}
writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor());
@@ -701,8 +701,8 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
for (PwIconCustom icon : customIcons) {
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem);
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid);
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.imageData)));
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.getUUID());
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.getImageData())));
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem);
}

View File

@@ -79,12 +79,12 @@ public class PwEntryOutputV3 {
// Group ID
mOS.write(GROUPID_FIELD_TYPE);
mOS.write(LONG_FOUR);
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getGroupId()));
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getParent().getGroupId()));
// Image ID
mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(LONG_FOUR);
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().iconId));
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().getIconId()));
// Title
//byte[] title = mPE.title.getBytes("UTF-8");

View File

@@ -93,7 +93,7 @@ public class PwGroupOutputV3 {
// Image ID
mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(IMAGEID_FIELD_SIZE);
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().iconId));
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().getIconId()));
// Level
mOS.write(LEVEL_FIELD_TYPE);

View File

@@ -17,8 +17,10 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
import java.util.Date;

View File

@@ -17,7 +17,10 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntry;
import java.util.Date;
import java.util.List;

View File

@@ -17,8 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.utils.StrUtil;
import com.kunzisoft.keepass.utils.UuidUtil;

View File

@@ -17,8 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.utils.StrUtil;
import java.util.ArrayList;

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.search;
package com.kunzisoft.keepass.database.search;
import android.content.Context;
import android.content.SharedPreferences;
@@ -42,7 +42,7 @@ import java.util.Locale;
import java.util.Queue;
public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch, PwEntrySearch>,
PwGroupSearch extends PwGroup<PwGroupSearch, PwGroupSearch, PwEntrySearch>,
PwGroupSearch extends PwGroup<PwGroupSearch, PwEntrySearch>,
PwEntrySearch extends PwEntry<PwGroupSearch>> {
private final Context mCtx;
@@ -54,31 +54,33 @@ public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch,
private boolean omitBackup() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mCtx);
return prefs.getBoolean(mCtx.getString(R.string.omitbackup_key), mCtx.getResources().getBoolean(R.bool.omitbackup_default));
}
public PwGroupSearch search(PwDatabaseVersion pm, String qStr) {
public PwGroupSearch search(PwDatabaseVersion pm, String qStr, int max) {
PwGroupSearch group = pm.createGroup();
group.setName(mCtx.getString(R.string.search_results));
group.setName("\"" + qStr + "\"");
group.setEntries(new ArrayList<>());
// Search all entries
Locale loc = Locale.getDefault();
qStr = qStr.toLowerCase(loc);
boolean isOmitBackup = omitBackup();
// TODO Search from the current group
Queue<PwGroupSearch> worklist = new LinkedList<>();
if (pm.getRootGroup() != null) {
worklist.add(pm.getRootGroup());
}
while (worklist.size() != 0) {
PwGroupSearch top = worklist.remove();
if (pm.isGroupSearchable(top, isOmitBackup)) {
for (PwEntrySearch entry : top.getChildEntries()) {
processEntries(entry, group.getChildEntries(), qStr, loc);
if (group.numbersOfChildEntries() >= max)
return group;
}
for (PwGroupSearch childGroup : top.getChildGroups()) {

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
/**
* @author bpellin

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
public class SearchParametersV4 extends SearchParameters implements Cloneable {
public static SearchParametersV4 DEFAULT = new SearchParametersV4();

View File

@@ -19,15 +19,17 @@
*/
package com.kunzisoft.keepass.database.security;
import java.io.Serializable;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Arrays;
public class ProtectedBinary implements Serializable {
public class ProtectedBinary implements Parcelable {
public final static ProtectedBinary EMPTY = new ProtectedBinary();
private byte[] data;
private boolean protect;
private byte[] data;
public boolean isProtected() {
return protect;
@@ -37,21 +39,22 @@ public class ProtectedBinary implements Serializable {
if (data == null) {
return 0;
}
return data.length;
}
public ProtectedBinary() {
this(false, new byte[0]);
}
public ProtectedBinary(boolean enableProtection, byte[] data) {
protect = enableProtection;
this.protect = enableProtection;
this.data = data;
}
public ProtectedBinary(Parcel in) {
protect = in.readByte() != 0;
in.readByteArray(data);
}
// TODO: replace the byte[] with something like ByteBuffer to make the return
// value immutable, so we don't have to worry about making deep copies
@@ -63,4 +66,27 @@ public class ProtectedBinary implements Serializable {
return (protect == rhs.protect) && Arrays.equals(data, rhs.data);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (protect ? 1 : 0));
dest.writeByteArray(data);
}
public static final Creator<ProtectedBinary> CREATOR = new Creator<ProtectedBinary>() {
@Override
public ProtectedBinary createFromParcel(Parcel in) {
return new ProtectedBinary(in);
}
@Override
public ProtectedBinary[] newArray(int size) {
return new ProtectedBinary[size];
}
};
}

View File

@@ -19,38 +19,66 @@
*/
package com.kunzisoft.keepass.database.security;
import java.io.Serializable;
import android.os.Parcel;
import android.os.Parcelable;
public class ProtectedString implements Parcelable {
public class ProtectedString implements Serializable {
private String string;
private boolean protect;
public boolean isProtected() {
return protect;
}
public int length() {
if (string == null) {
return 0;
}
return string.length();
}
private String string;
public ProtectedString() {
this(false, "");
}
public ProtectedString(ProtectedString toCopy) {
this.string = toCopy.string;
this.protect = toCopy.protect;
this.string = toCopy.string;
}
public ProtectedString(boolean enableProtection, String string) {
protect = enableProtection;
this.protect = enableProtection;
this.string = string;
}
public ProtectedString(Parcel in) {
protect = in.readByte() != 0;
string = in.readString();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte((byte) (protect ? 1 : 0));
dest.writeString(string);
}
public static final Parcelable.Creator<ProtectedString> CREATOR = new Parcelable.Creator<ProtectedString>() {
@Override
public ProtectedString createFromParcel(Parcel in) {
return new ProtectedString(in);
}
@Override
public ProtectedString[] newArray(int size) {
return new ProtectedString[size];
}
};
public boolean isProtected() {
return protect;
}
public int length() {
if (string == null) {
return 0;
}
return string.length();
}
@Override

View File

@@ -35,9 +35,9 @@ import android.widget.Toast;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.icons.IconPackChooser;
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION;
import static com.kunzisoft.keepass.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE;
@@ -49,9 +49,11 @@ public class GroupEditDialogFragment extends DialogFragment
public static final String TAG_CREATE_GROUP = "TAG_CREATE_GROUP";
public static final String KEY_NAME = "KEY_NAME";
public static final String KEY_ICON_ID = "KEY_ICON_ID";
public static final String KEY_ICON = "KEY_ICON";
public static final String KEY_ACTION_ID = "KEY_ACTION_ID";
private Database database;
private EditGroupListener editGroupListener;
private EditGroupDialogAction editGroupDialogAction;
@@ -59,6 +61,7 @@ public class GroupEditDialogFragment extends DialogFragment
private PwIcon iconGroup;
private ImageView iconButton;
private int iconColor;
public enum EditGroupDialogAction {
CREATION, UPDATE, NONE;
@@ -76,10 +79,10 @@ public class GroupEditDialogFragment extends DialogFragment
return fragment;
}
public static GroupEditDialogFragment build(PwNode group) {
public static GroupEditDialogFragment build(PwGroup group) {
Bundle bundle = new Bundle();
bundle.putString(KEY_NAME, group.getDisplayTitle());
bundle.putSerializable(KEY_ICON_ID, group.getIcon());
bundle.putString(KEY_NAME, group.getName());
bundle.putParcelable(KEY_ICON, group.getIcon());
bundle.putInt(KEY_ACTION_ID, UPDATE.ordinal());
GroupEditDialogFragment fragment = new GroupEditDialogFragment();
fragment.setArguments(bundle);
@@ -112,20 +115,21 @@ public class GroupEditDialogFragment extends DialogFragment
// Retrieve the textColor to tint the icon
int[] attrs = {android.R.attr.textColorPrimary};
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
iconColor = ta.getColor(0, Color.WHITE);
// Init elements
database = App.getDB();
editGroupDialogAction = EditGroupDialogAction.NONE;
nameGroup = "";
iconGroup = App.getDB().getPwDatabase().getIconFactory().getFolderIcon();
iconGroup = database.getPwDatabase().getIconFactory().getFolderIcon();
if (savedInstanceState != null
&& savedInstanceState.containsKey(KEY_ACTION_ID)
&& savedInstanceState.containsKey(KEY_NAME)
&& savedInstanceState.containsKey(KEY_ICON_ID)) {
&& savedInstanceState.containsKey(KEY_ICON)) {
editGroupDialogAction = getActionFromOrdinal(savedInstanceState.getInt(KEY_ACTION_ID));
nameGroup = savedInstanceState.getString(KEY_NAME);
iconGroup = (PwIcon) savedInstanceState.getSerializable(KEY_ICON_ID);
iconGroup = savedInstanceState.getParcelable(KEY_ICON);
} else {
@@ -135,30 +139,16 @@ public class GroupEditDialogFragment extends DialogFragment
if (getArguments() != null
&& getArguments().containsKey(KEY_NAME)
&& getArguments().containsKey(KEY_ICON_ID)) {
&& getArguments().containsKey(KEY_ICON)) {
nameGroup = getArguments().getString(KEY_NAME);
iconGroup = (PwIcon) getArguments().getSerializable(KEY_ICON_ID);
iconGroup = getArguments().getParcelable(KEY_ICON);
}
}
// populate the name
nameField.setText(nameGroup);
// populate the icon
if (IconPackChooser.getSelectedIconPack(getContext()).tintable()) {
App.getDB().getDrawFactory()
.assignDatabaseIconTo(
getContext(),
iconButton,
iconGroup,
true,
iconColor);
} else {
App.getDB().getDrawFactory()
.assignDatabaseIconTo(
getContext(),
iconButton,
iconGroup);
}
assignIconView();
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(root)
@@ -195,18 +185,26 @@ public class GroupEditDialogFragment extends DialogFragment
return builder.create();
}
private void assignIconView() {
database.getDrawFactory()
.assignDatabaseIconTo(
getContext(),
iconButton,
iconGroup,
iconColor);
}
@Override
public void iconPicked(Bundle bundle) {
int selectedIconID = bundle.getInt(IconPickerDialogFragment.KEY_ICON_ID);
iconButton.setImageResource(IconPackChooser.getSelectedIconPack(getContext()).iconToResId(selectedIconID));
iconGroup = App.getDB().getPwDatabase().getIconFactory().getIcon(selectedIconID);
iconGroup = bundle.getParcelable(IconPickerDialogFragment.KEY_ICON_STANDARD);
assignIconView();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(KEY_ACTION_ID, editGroupDialogAction.ordinal());
outState.putString(KEY_NAME, nameGroup);
outState.putSerializable(KEY_ICON_ID, iconGroup);
outState.putParcelable(KEY_ICON, iconGroup);
super.onSaveInstanceState(outState);
}

View File

@@ -37,14 +37,16 @@ import android.widget.GridView;
import android.widget.ImageView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.icons.IconPack;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.stylish.StylishActivity;
public class IconPickerDialogFragment extends DialogFragment {
public static final String KEY_ICON_ID = "icon_id";
public static final int UNDEFINED_ICON_ID = -1;
public static final String KEY_ICON_STANDARD = "KEY_ICON_STANDARD";
private IconPickerListener iconPickerListener;
private IconPack iconPack;
@@ -85,7 +87,7 @@ public class IconPickerDialogFragment extends DialogFragment {
currIconGridView.setOnItemClickListener((parent, v, position, id) -> {
Bundle bundle = new Bundle();
bundle.putInt(KEY_ICON_ID, position);
bundle.putParcelable(KEY_ICON_STANDARD, new PwIconStandard(position));
iconPickerListener.iconPicked(bundle);
dismiss();
});

View File

@@ -65,52 +65,50 @@ public class IconDrawableFactory {
private ReferenceMap standardIconMap = new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
/**
* Assign a default database icon to an ImageView
* Assign a default database icon to an ImageView and tint it if needed
*
* @param context Context to build the drawable
* @param iv ImageView that will host the drawable
* @param iconView ImageView that will host the drawable
* @param tintColor Use this color to tint tintable icon
*/
public void assignDefaultDatabaseIconTo(Context context, ImageView iv) {
assignDefaultDatabaseIconTo(context, iv, false, Color.WHITE);
public void assignDefaultDatabaseIconTo(Context context, ImageView iconView, int tintColor) {
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
assignDrawableTo(context,
iconView,
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
true,
tintColor);
} else {
assignDrawableTo(context,
iconView,
IconPackChooser.getSelectedIconPack(context).getDefaultIconId(),
false,
Color.WHITE);
}
}
/**
* Assign a default database icon to an ImageView and tint it
* Assign a database icon to an ImageView and tint it if needed
*
* @param context Context to build the drawable
* @param iv ImageView that will host the drawable
*/
public void assignDefaultDatabaseIconTo(Context context, ImageView iv, boolean tint, int tintColor) {
assignDrawableTo(context, iv, IconPackChooser.getSelectedIconPack(context).getDefaultIconId(), tint, tintColor);
}
/**
* Assign a database icon to an ImageView
*
* @param context Context to build the drawable
* @param iv ImageView that will host the drawable
* @param iconView ImageView that will host the drawable
* @param icon The icon from the database
* @param tintColor Use this color to tint tintable icon
*/
public void assignDatabaseIconTo(Context context, ImageView iv, PwIcon icon) {
assignDatabaseIconTo(context, iv, icon, false, Color.WHITE);
public void assignDatabaseIconTo(Context context, ImageView iconView, PwIcon icon, int tintColor) {
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
iconView,
true,
tintColor);
} else {
assignDrawableToImageView(getIconDrawable(context, icon, true, tintColor),
iconView,
false,
Color.WHITE);
}
}
/**
* Assign a database icon to an ImageView and tint it
*
* @param context Context to build the drawable
* @param imageView ImageView that will host the drawable
* @param icon The icon from the database
* @param tint true will tint the drawable with tintColor
* @param tintColor Use this color if tint is true
*/
public void assignDatabaseIconTo(Context context, ImageView imageView, PwIcon icon, boolean tint, int tintColor) {
assignDrawableToImageView(getIconDrawable(context, icon, tint, tintColor),
imageView,
tint,
tintColor);
}
/**
* Assign an image by its resourceId to an ImageView and tint it
*
@@ -221,7 +219,7 @@ public class IconDrawableFactory {
* @return The drawable
*/
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.iconId);
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
return getIconDrawable(context, resId, isTint, tintColor);
}
@@ -289,14 +287,14 @@ public class IconDrawableFactory {
return blank;
}
Drawable draw = (Drawable) customIconMap.get(icon.uuid);
Drawable draw = (Drawable) customIconMap.get(icon.getUUID());
if (draw == null) {
if (icon.imageData == null) {
if (icon.getImageData() == null) {
return blank;
}
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.length);
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
// Could not understand custom icon
if (bitmap == null) {
@@ -306,7 +304,7 @@ public class IconDrawableFactory {
bitmap = resize(bitmap);
draw = new BitmapDrawable(context.getResources(), bitmap);
customIconMap.put(icon.uuid, draw);
customIconMap.put(icon.getUUID(), draw);
}
return draw;

View File

@@ -29,6 +29,7 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.stylish.StylishActivity;
@@ -45,6 +46,7 @@ public abstract class LockingActivity extends StylishActivity {
private LockReceiver lockReceiver;
private boolean exitLock;
protected boolean readOnly;
/**
* Called to start a record time,
@@ -72,6 +74,9 @@ public abstract class LockingActivity extends StylishActivity {
lockReceiver = null;
exitLock = false;
readOnly = false;
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
}
public static void checkShutdown(Activity activity) {
@@ -116,6 +121,12 @@ public abstract class LockingActivity extends StylishActivity {
TimeoutHelper.recordTime(this);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
@Override
protected void onPause() {
super.onPause();

View File

@@ -239,11 +239,13 @@ public class NotificationCopyingService extends Service {
}
countingDownTask = null;
notificationManager.cancel(myNotificationId);
try {
clipboardHelper.cleanClipboard();
} catch (SamsungClipboardException e) {
Log.e(TAG, "Clipboard can't be cleaned", e);
}
// Clean password only if no next field
if (nextFields.size() <= 0)
try {
clipboardHelper.cleanClipboard();
} catch (SamsungClipboardException e) {
Log.e(TAG, "Clipboard can't be cleaned", e);
}
});
countingDownTask.start();

View File

@@ -56,8 +56,8 @@ import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.GroupActivity;
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.activities.IntentBuildLauncher;
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.compat.ClipDataCompat;
@@ -69,6 +69,8 @@ import com.kunzisoft.keepass.fileselect.KeyFileHelper;
import com.kunzisoft.keepass.fingerprint.FingerPrintAnimatedVector;
import com.kunzisoft.keepass.fingerprint.FingerPrintExplanationDialog;
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.stylish.StylishActivity;
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment;
@@ -124,6 +126,8 @@ public class PasswordActivity extends StylishActivity
private CompoundButton checkboxDefaultDatabaseView;
private CompoundButton.OnCheckedChangeListener enableButtonOncheckedChangeListener;
private boolean readOnly;
private DefaultCheckChange defaultCheckChange;
private ValidateButtonViewClickListener validateButtonViewClickListener;
@@ -139,16 +143,23 @@ public class PasswordActivity extends StylishActivity
}
public static void launch(
Activity act,
Activity activity,
String fileName,
String keyFile) throws FileNotFoundException {
verifyFileNameUriFromLaunch(fileName);
Intent intent = new Intent(act, PasswordActivity.class);
buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
// only to avoid visible flickering when redirecting
activity.startActivityForResult(intent, RESULT_CANCELED);
});
}
private static void buildAndLaunchIntent(Activity activity, String fileName, String keyFile,
IntentBuildLauncher intentBuildLauncher) {
Intent intent = new Intent(activity, PasswordActivity.class);
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
// only to avoid visible flickering when redirecting
act.startActivityForResult(intent, RESULT_CANCELED);
intentBuildLauncher.startActivityForResult(intent);
}
public static void launchForKeyboardResult(
@@ -158,17 +169,16 @@ public class PasswordActivity extends StylishActivity
}
public static void launchForKeyboardResult(
Activity act,
Activity activity,
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);
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
// only to avoid visible flickering when redirecting
act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
// only to avoid visible flickering when redirecting
activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
});
}
@RequiresApi(api = Build.VERSION_CODES.O)
@@ -181,20 +191,19 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launchForAutofillResult(
Activity act,
Activity activity,
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);
buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
});
} else {
launch(act, fileName, keyFile);
launch(activity, fileName, keyFile);
}
}
@@ -276,6 +285,8 @@ public class PasswordActivity extends StylishActivity
checkboxKeyfileView = findViewById(R.id.keyfile_checkox);
checkboxDefaultDatabaseView = findViewById(R.id.default_database);
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState);
View browseView = findViewById(R.id.browse_button);
keyFileHelper = new KeyFileHelper(PasswordActivity.this);
browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener());
@@ -326,8 +337,6 @@ public class PasswordActivity extends StylishActivity
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
checkAndPerformedEducation();
}
@Override
@@ -380,11 +389,17 @@ public class PasswordActivity extends StylishActivity
.execute(getIntent());
}
@Override
protected void onSaveInstanceState(Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
/**
* Check and display learning views
* Displays the explanation for a database opening with fingerprints if available
*/
private void checkAndPerformedEducation() {
private void checkAndPerformedEducation(Menu menu) {
if (PreferencesUtil.isEducationScreensEnabled(this)) {
if (!PreferencesUtil.isEducationUnlockPerformed(this)) {
@@ -394,7 +409,7 @@ public class PasswordActivity extends StylishActivity
getString(R.string.education_unlock_title),
getString(R.string.education_unlock_summary))
.dimColor(R.color.green)
.icon(ContextCompat.getDrawable(this, R.mipmap.ic_launcher_round))
.icon(ContextCompat.getDrawable(getApplicationContext(), R.mipmap.ic_launcher_round))
.textColorInt(Color.WHITE)
.tintTarget(false)
.cancelable(true),
@@ -402,6 +417,44 @@ public class PasswordActivity extends StylishActivity
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view);
performedReadOnlyEducation(menu);
}
@Override
public void onOuterCircleClick(TapTargetView view) {
super.onOuterCircleClick(view);
view.dismiss(false);
performedReadOnlyEducation(menu);
}
});
// TODO make a period for donation
PreferencesUtil.saveEducationPreference(PasswordActivity.this,
R.string.education_unlock_key);
}
}
}
/**
* Check and display learning views
* Displays read-only if available
*/
private void performedReadOnlyEducation(Menu menu) {
if (!PreferencesUtil.isEducationReadOnlyPerformed(this)) {
try {
TapTargetView.showFor(this,
TapTarget.forToolbarMenuItem(toolbar, R.id.menu_open_file_read_mode_key,
getString(R.string.education_read_only_title),
getString(R.string.education_read_only_summary))
.textColorInt(Color.WHITE)
.tintTarget(true)
.cancelable(true),
new TapTargetView.Listener() {
@Override
public void onTargetClick(TapTargetView view) {
super.onTargetClick(view);
MenuItem editItem = menu.findItem(R.id.menu_open_file_read_mode_key);
onOptionsItemSelected(editItem);
checkAndPerformedEducationForFingerprint();
}
@@ -410,11 +463,13 @@ public class PasswordActivity extends StylishActivity
super.onOuterCircleClick(view);
view.dismiss(false);
checkAndPerformedEducationForFingerprint();
}
});
// TODO make a period for donation
PreferencesUtil.saveEducationPreference(PasswordActivity.this, R.string.education_unlock_key);
PreferencesUtil.saveEducationPreference(this,
R.string.education_read_only_key);
} catch (Exception e) {
// If icon not visible
Log.w(TAG, "Can't performed education for entry's edition");
}
}
}
@@ -953,14 +1008,14 @@ public class PasswordActivity extends StylishActivity
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure);
GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure, readOnly);
}
}
if (assistStructure == null) {
if (entrySelectionMode) {
GroupActivity.launchForKeyboardResult(PasswordActivity.this);
GroupActivity.launchForKeyboardResult(PasswordActivity.this, readOnly);
} else {
GroupActivity.launch(PasswordActivity.this);
GroupActivity.launch(PasswordActivity.this, readOnly);
}
}
}
@@ -968,16 +1023,35 @@ public class PasswordActivity extends StylishActivity
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
// Read menu
inflater.inflate(R.menu.open_file, menu);
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key));
MenuUtil.defaultMenuInflater(inflater, menu);
// Fingerprint menu
if (!fingerprintMustBeConfigured
&& prefsNoBackup.contains(getPreferenceKeyValue()) )
inflater.inflate(R.menu.fingerprint, menu);
super.onCreateOptionsMenu(menu);
// Show education views
new Handler().post(() -> checkAndPerformedEducation(menu));
return true;
}
private void changeOpenFileReadIcon(MenuItem togglePassword) {
if ( readOnly ) {
togglePassword.setTitle(R.string.menu_file_selection_read_only);
togglePassword.setIcon(R.drawable.ic_read_only_white_24dp);
} else {
togglePassword.setTitle(R.string.menu_open_file_read_and_write);
togglePassword.setIcon(R.drawable.ic_read_write_white_24dp);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@@ -985,6 +1059,10 @@ public class PasswordActivity extends StylishActivity
case android.R.id.home:
finish();
break;
case R.id.menu_open_file_read_mode_key:
readOnly = !readOnly;
changeOpenFileReadIcon(item);
break;
case R.id.menu_fingerprint_remove_key:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEntryKey();

View File

@@ -1,107 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.search;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.ListNodesActivity;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.utils.MenuUtil;
import javax.annotation.Nullable;
public class SearchResultsActivity extends ListNodesActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutInflater().inflate(R.layout.search_results, null));
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.search_label));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
groupNameView = findViewById(R.id.group_name);
attachFragmentToContentView();
View notFoundView = findViewById(R.id.not_found_container);
View listContainer = findViewById(R.id.nodes_list_fragment_container);
if ( mCurrentGroup == null || mCurrentGroup.numbersOfChildEntries() < 1 ) {
listContainer.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listContainer.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
}
@Override
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
Database mDb = App.getDB();
// Likely the app has been killed exit the activity
if ( ! mDb.getLoaded() ) {
finish();
}
return mDb.search(getSearchStr(getIntent()).trim());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case android.R.id.home:
finish();
}
return super.onOptionsItemSelected(item);
}
private String getSearchStr(Intent queryIntent) {
// get and process search query here
final String queryAction = queryIntent.getAction();
if ( Intent.ACTION_SEARCH.equals(queryAction) ) {
return queryIntent.getStringExtra(SearchManager.QUERY);
}
return "";
}
}

View File

@@ -43,6 +43,7 @@ import android.widget.Toast;
import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.dialogs.ProFeatureDialogFragment;
@@ -72,6 +73,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
private static final int REQUEST_CODE_AUTOFILL = 5201;
private Database database;
private boolean databaseReadOnly;
private int count = 0;
private Preference roundPref;
@@ -79,14 +83,24 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
private Preference parallelismPref;
public static NestedSettingsFragment newInstance(Screen key) {
return newInstance(key, ReadOnlyHelper.READ_ONLY_DEFAULT);
}
public static NestedSettingsFragment newInstance(Screen key, boolean databaseReadOnly) {
NestedSettingsFragment fragment = new NestedSettingsFragment();
// supply arguments to bundle.
Bundle args = new Bundle();
args.putInt(TAG_KEY, key.ordinal());
ReadOnlyHelper.putReadOnlyInBundle(args, databaseReadOnly);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public void onResume() {
super.onResume();
@@ -110,6 +124,10 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (getArguments() != null)
key = getArguments().getInt(TAG_KEY);
database = App.getDB();
databaseReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
databaseReadOnly = database.isReadOnly() || databaseReadOnly;
// Load the preferences from an XML resource
switch (Screen.values()[key]) {
case APPLICATION:
@@ -306,23 +324,22 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
case DATABASE:
setPreferencesFromResource(R.xml.database_preferences, rootKey);
Database db = App.getDB();
if (db.getLoaded()) {
if (database.getLoaded()) {
PreferenceCategory dbGeneralPrefCategory = (PreferenceCategory) findPreference(getString(R.string.database_general_key));
// Db name
Preference dbNamePref = findPreference(getString(R.string.database_name_key));
if ( db.containsName() ) {
dbNamePref.setSummary(db.getName());
if ( database.containsName() ) {
dbNamePref.setSummary(database.getName());
} else {
dbGeneralPrefCategory.removePreference(dbNamePref);
}
// Db description
Preference dbDescriptionPref = findPreference(getString(R.string.database_description_key));
if ( db.containsDescription() ) {
dbDescriptionPref.setSummary(db.getDescription());
if ( database.containsDescription() ) {
dbDescriptionPref.setSummary(database.getDescription());
} else {
dbGeneralPrefCategory.removePreference(dbDescriptionPref);
}
@@ -331,9 +348,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
SwitchPreference recycleBinPref = (SwitchPreference) findPreference(getString(R.string.recycle_bin_key));
// TODO Recycle
dbGeneralPrefCategory.removePreference(recycleBinPref); // To delete
if (db.isRecycleBinAvailable()) {
if (database.isRecycleBinAvailable()) {
recycleBinPref.setChecked(db.isRecycleBinEnabled());
recycleBinPref.setChecked(database.isRecycleBinEnabled());
recycleBinPref.setEnabled(false);
} else {
dbGeneralPrefCategory.removePreference(recycleBinPref);
@@ -341,27 +358,27 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
// Version
Preference dbVersionPref = findPreference(getString(R.string.database_version_key));
dbVersionPref.setSummary(db.getVersion());
dbVersionPref.setSummary(database.getVersion());
// Encryption Algorithm
Preference algorithmPref = findPreference(getString(R.string.encryption_algorithm_key));
algorithmPref.setSummary(db.getEncryptionAlgorithmName(getResources()));
algorithmPref.setSummary(database.getEncryptionAlgorithmName(getResources()));
// Key derivation function
Preference kdfPref = findPreference(getString(R.string.key_derivation_function_key));
kdfPref.setSummary(db.getKeyDerivationName(getResources()));
kdfPref.setSummary(database.getKeyDerivationName(getResources()));
// Round encryption
roundPref = findPreference(getString(R.string.transform_rounds_key));
roundPref.setSummary(db.getNumberKeyEncryptionRoundsAsString());
roundPref.setSummary(database.getNumberKeyEncryptionRoundsAsString());
// Memory Usage
memoryPref = findPreference(getString(R.string.memory_usage_key));
memoryPref.setSummary(db.getMemoryUsageAsString());
memoryPref.setSummary(database.getMemoryUsageAsString());
// Parallelism
parallelismPref = findPreference(getString(R.string.parallelism_key));
parallelismPref.setSummary(db.getParallelismAsString());
parallelismPref.setSummary(database.getParallelismAsString());
} else {
Log.e(getClass().getName(), "Database isn't ready");
@@ -480,18 +497,16 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
assert getFragmentManager() != null;
DialogFragment dialogFragment = null;
boolean otherDialogFragment = false;
DialogFragment dialogFragment = null;
if (preference.getKey().equals(getString(R.string.database_name_key))) {
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.getKey());
}
else if (preference.getKey().equals(getString(R.string.database_description_key))) {
} else if (preference.getKey().equals(getString(R.string.database_description_key))) {
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.getKey());
}
else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
} else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.getKey());
}
else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
} else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
DatabaseKeyDerivationPreferenceDialogFragmentCompat keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.getKey());
// Add other prefs to manage
if (roundPref != null)
@@ -501,24 +516,23 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (parallelismPref != null)
keyDerivationDialogFragment.setParallelismPreference(parallelismPref);
dialogFragment = keyDerivationDialogFragment;
}
else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
} else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.getKey());
}
else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
} else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.getKey());
}
else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
} else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.getKey());
} else {
otherDialogFragment = true;
}
if (dialogFragment != null) {
if (dialogFragment != null && !databaseReadOnly) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getFragmentManager(), null);
}
// Could not be handled here. Try with the super method.
else {
else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference);
}
}
@@ -538,6 +552,12 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
ReadOnlyHelper.onSaveInstanceState(outState, databaseReadOnly);
super.onSaveInstanceState(outState);
}
@Override
public boolean onPreferenceClick(Preference preference) {
// TODO encapsulate

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.settings;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.TypedValue;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.SortNodeEnum;
@@ -54,9 +55,25 @@ public class PreferencesUtil {
sharedPreferencesEditor.apply();
}
public static boolean showUsernamesListEntries(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.list_entries_show_username_key),
context.getResources().getBoolean(R.bool.list_entries_show_username_default));
}
/**
* Retrieve the text size in SP, verify the integrity of the size stored in preference
*/
public static float getListTextSize(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return Float.parseFloat(prefs.getString(ctx.getString(R.string.list_size_key), ctx.getString(R.string.list_size_default)));
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
String defaultSizeString = ctx.getString(R.string.list_size_default);
String listSize = prefs.getString(ctx.getString(R.string.list_size_key), defaultSizeString);
if (!Arrays.asList(ctx.getResources().getStringArray(R.array.list_size_values)).contains(listSize))
listSize = defaultSizeString;
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
Float.parseFloat(listSize),
ctx.getResources().getDisplayMetrics());
}
public static int getDefaultPasswordLength(Context ctx) {
@@ -139,12 +156,26 @@ public class PreferencesUtil {
ctx.getResources().getBoolean(R.bool.auto_open_file_uri_default));
}
public static boolean isFirstTimeAskAllowCopyPasswordAndProtectedFields(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_first_time_key),
ctx.getResources().getBoolean(R.bool.allow_copy_password_first_time_default));
}
public static boolean allowCopyPasswordAndProtectedFields(Context ctx) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
return prefs.getBoolean(ctx.getString(R.string.allow_copy_password_key),
ctx.getResources().getBoolean(R.bool.allow_copy_password_default));
}
public static void setAllowCopyPasswordAndProtectedFields(Context ctx, boolean allowCopy) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
prefs.edit()
.putBoolean(ctx.getString(R.string.allow_copy_password_first_time_key), false)
.putBoolean(ctx.getString(R.string.allow_copy_password_key), allowCopy)
.apply();
}
public static String getIconPackSelectedId(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getString(
@@ -158,6 +189,12 @@ public class PreferencesUtil {
context.getResources().getBoolean(R.bool.allow_no_password_default));
}
public static boolean enableReadOnlyDatabase(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.enable_read_only_key),
context.getResources().getBoolean(R.bool.enable_read_only_default));
}
/**
* All preference keys associated with education
*/
@@ -166,6 +203,7 @@ public class PreferencesUtil {
R.string.education_select_db_key,
R.string.education_open_link_db_key,
R.string.education_unlock_key,
R.string.education_read_only_key,
R.string.education_search_key,
R.string.education_new_node_key,
R.string.education_sort_key,
@@ -245,6 +283,18 @@ public class PreferencesUtil {
context.getResources().getBoolean(R.bool.education_unlock_default));
}
/**
* Determines whether the explanatory view of the database read-only has already been displayed.
*
* @param context The context to open the SharedPreferences
* @return boolean value of education_read_only_key key
*/
public static boolean isEducationReadOnlyPerformed(Context context) {
SharedPreferences prefs = getEducationSharedPreferences(context);
return prefs.getBoolean(context.getString(R.string.education_read_only_key),
context.getResources().getBoolean(R.bool.education_read_only_default));
}
/**
* Determines whether the explanatory view of search has already been displayed.
*

View File

@@ -28,6 +28,7 @@ import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
@@ -39,17 +40,18 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
private Toolbar toolbar;
public static void launch(Activity activity) {
Intent i = new Intent(activity, SettingsActivity.class);
activity.startActivity(i);
public static void launch(Activity activity, boolean readOnly) {
Intent intent = new Intent(activity, SettingsActivity.class);
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
activity.startActivity(intent);
}
public static void launch(Activity activity, boolean checkLock) {
public static void launch(Activity activity, boolean readOnly, boolean checkLock) {
// To avoid flickering when launch settings in a LockingActivity
if (!checkLock)
launch(activity);
launch(activity, readOnly);
else if (LockingActivity.checkTimeIsAllowedOrFinish(activity)) {
launch(activity);
launch(activity, readOnly);
}
}
@@ -114,7 +116,7 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED)
.replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, readOnly), TAG_NESTED)
.addToBackStack(TAG_NESTED)
.commit();

View File

@@ -88,8 +88,8 @@ public class ProgressTaskDialogFragment extends DialogFragment implements Progre
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
Util.unlockScreenOrientation(getActivity());
super.onDismiss(dialog);
}
public static void stop(AppCompatActivity activity) {

View File

@@ -19,9 +19,14 @@
*/
package com.kunzisoft.keepass.utils;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -47,4 +52,68 @@ public class MemUtil {
return baos.toByteArray();
}
// For writing to a Parcel
public static <K extends Parcelable,V extends Parcelable> void writeParcelableMap(
Parcel parcel, int flags, Map<K, V > map) {
parcel.writeInt(map.size());
for(Map.Entry<K, V> e : map.entrySet()){
parcel.writeParcelable(e.getKey(), flags);
parcel.writeParcelable(e.getValue(), flags);
}
}
// For reading from a Parcel
public static <K extends Parcelable,V extends Parcelable> Map<K,V> readParcelableMap(
Parcel parcel, Class<K> kClass, Class<V> vClass) {
int size = parcel.readInt();
Map<K, V> map = new HashMap<K, V>(size);
for(int i = 0; i < size; i++){
map.put(kClass.cast(parcel.readParcelable(kClass.getClassLoader())),
vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
}
return map;
}
// For writing map with string key to a Parcel
public static <V extends Parcelable> void writeStringParcelableMap(
Parcel parcel, int flags, Map<String, V> map) {
parcel.writeInt(map.size());
for(Map.Entry<String, V> e : map.entrySet()){
parcel.writeString(e.getKey());
parcel.writeParcelable(e.getValue(), flags);
}
}
// For reading map with string key from a Parcel
public static <V extends Parcelable> HashMap<String,V> readStringParcelableMap(
Parcel parcel, Class<V> vClass) {
int size = parcel.readInt();
HashMap<String, V> map = new HashMap<>(size);
for(int i = 0; i < size; i++){
map.put(parcel.readString(),
vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
}
return map;
}
// For writing map with string key and string value to a Parcel
public static void writeStringParcelableMap(Parcel dest, Map<String, String> map) {
dest.writeInt(map.size());
for(Map.Entry<String, String> e : map.entrySet()){
dest.writeString(e.getKey());
dest.writeString(e.getValue());
}
}
// For reading map with string key and string value from a Parcel
public static HashMap<String, String> readStringParcelableMap(Parcel in) {
int size = in.readInt();
HashMap<String, String> map = new HashMap<>(size);
for(int i = 0; i < size; i++){
map.put(in.readString(),
in.readString());
}
return map;
}
}

View File

@@ -32,6 +32,8 @@ import com.kunzisoft.keepass.activities.AboutActivity;
import com.kunzisoft.keepass.settings.SettingsActivity;
import com.kunzisoft.keepass.stylish.StylishActivity;
import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
public class MenuUtil {
@@ -56,20 +58,20 @@ public class MenuUtil {
}
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) {
return onDefaultMenuOptionsItemSelected(activity, item, false);
return onDefaultMenuOptionsItemSelected(activity, item, READ_ONLY_DEFAULT, false);
}
/*
* @param checkLock Check the time lock before launch settings in LockingActivity
*/
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean checkLock) {
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean readOnly, boolean checkLock) {
switch (item.getItemId()) {
case R.id.menu_contribute:
return onContributionItemSelected(activity);
case R.id.menu_app_settings:
// To avoid flickering when launch settings in a LockingActivity
SettingsActivity.launch(activity, checkLock);
SettingsActivity.launch(activity, readOnly, checkLock);
return true;
case R.id.menu_about:

View File

@@ -19,13 +19,12 @@
*/
package com.kunzisoft.keepass.utils;
import com.kunzisoft.keepass.database.EntrySearchV4;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.database.SearchParametersV4;
import com.kunzisoft.keepass.database.search.EntrySearchV4;
import com.kunzisoft.keepass.database.search.SearchParametersV4;
import java.util.ArrayList;
import java.util.List;
@@ -158,7 +157,7 @@ public class SprEngineV4 {
List<PwEntryV4> list = new ArrayList<>();
// TODO type parameter
EntrySearchV4 entrySearchV4 = new EntrySearchV4((PwGroupV4) ctx.db.getRootGroup());
EntrySearchV4 entrySearchV4 = new EntrySearchV4(ctx.db.getRootGroup());
entrySearchV4.searchEntries(sp, list);
if (list.size() > 0) {

View File

@@ -27,9 +27,12 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.net.Uri;
import android.util.TypedValue;
import android.widget.EditText;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -74,6 +77,13 @@ public class Util {
applyFontVisibilityTo(context, (TextView) editText);
}
public static float getListTextDefaultSize(Context context) {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
(Float.parseFloat(context.getString(R.string.list_size_default))),
context.getResources().getDisplayMetrics());
}
public static void lockScreenOrientation(Activity activity) {
if (activity != null) {
int currentOrientation = activity.getResources().getConfiguration().orientation;
@@ -86,7 +96,8 @@ public class Util {
}
public static void unlockScreenOrientation(Activity activity) {
if (activity != null)
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
if (activity != null) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
}
}

View File

@@ -167,6 +167,7 @@ public class AddNodeButtonView extends RelativeLayout {
this.addEntryEnable = enable;
if (enable && addEntryView != null && addEntryView.getVisibility() != VISIBLE)
addEntryView.setVisibility(INVISIBLE);
disableViewIfNoAddAvailable();
}
/**
@@ -177,6 +178,19 @@ public class AddNodeButtonView extends RelativeLayout {
this.addGroupEnable = enable;
if (enable && addGroupView != null && addGroupView.getVisibility() != VISIBLE)
addGroupView.setVisibility(INVISIBLE);
disableViewIfNoAddAvailable();
}
private void disableViewIfNoAddAvailable() {
if (!addEntryEnable || !addGroupEnable) {
setVisibility(GONE);
} else {
setVisibility(VISIBLE);
}
}
public boolean isEnable() {
return getVisibility() == VISIBLE;
}
public void setAddGroupClickListener(OnClickListener onClickListener) {

View File

@@ -20,6 +20,9 @@
package com.kunzisoft.keepass.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.view.LayoutInflater;
@@ -39,6 +42,7 @@ import java.util.Date;
public class EntryContentsView extends LinearLayout {
private boolean fontInVisibility;
private int colorAccent;
private View userNameContainerView;
private TextView userNameView;
@@ -77,6 +81,11 @@ public class EntryContentsView extends LinearLayout {
timeFormat = android.text.format.DateFormat.getTimeFormat(context);
inflate(context);
int[] attrColorAccent = {R.attr.colorAccentCompat};
TypedArray taColorAccent = context.getTheme().obtainStyledAttributes(attrColorAccent);
this.colorAccent = taColorAccent.getColor(0, Color.BLACK);
taColorAccent.recycle();
}
private void inflate(Context context) {
@@ -129,21 +138,26 @@ public class EntryContentsView extends LinearLayout {
return userNameContainerView.getVisibility() == VISIBLE;
}
public void assignPassword(String password) {
public void assignPassword(String password, boolean allowCopyPassword) {
if (password != null && !password.isEmpty()) {
passwordContainerView.setVisibility(VISIBLE);
passwordView.setText(password);
if (fontInVisibility)
Util.applyFontVisibilityTo(getContext(), passwordView);
passwordActionView.setVisibility(GONE);
if (!allowCopyPassword) {
passwordActionView.setColorFilter(ContextCompat.getColor(getContext(), R.color.grey_dark));
} else {
passwordActionView.setColorFilter(colorAccent);
}
} else {
passwordContainerView.setVisibility(GONE);
}
}
public void assignPasswordCopyListener(OnClickListener onClickListener) {
if (onClickListener == null)
setClickable(false);
passwordActionView.setOnClickListener(onClickListener);
passwordActionView.setVisibility(VISIBLE);
}
public boolean isPasswordPresent() {

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.view;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageView;
@@ -62,10 +63,12 @@ public class EntryCustomField extends LinearLayout {
setLabel(label);
setValue(value);
if (!showAction) {
actionImageView.setVisibility(INVISIBLE);
} else {
if (showAction) {
actionImageView.setEnabled(true);
setAction(onClickActionListener);
} else {
actionImageView.setEnabled(false);
actionImageView.setColorFilter(ContextCompat.getColor(getContext(), R.color.grey_dark));
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App;
public class GroupHeaderView extends RelativeLayout {
public GroupHeaderView(Context context) {
this(context, null);
}
public GroupHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context);
}
private void inflate(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
inflater.inflate(R.layout.group_header, this);
if (App.getDB().isReadOnly()) {
View readOnlyIndicator = findViewById(R.id.read_only);
readOnlyIndicator.setVisibility(VISIBLE);
}
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_right.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="100%p" android:toYDelta="0"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="-100%p" android:toYDelta="0"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_out_right.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="0" android:toYDelta="100%p"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_out_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="0" android:toYDelta="-100%p"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="24"
android:viewportHeight="24"
android:width="24dp"
android:height="24dp">
<group
android:translateY="-8">
<group
android:scaleX="1.777778"
android:scaleY="1.777778"
android:translateX="-205.4844"
android:translateY="-31.99788">
<path
android:pathData="M125.00684 31.305444l0 -6.644528c0 -0.263013 -0.21159 -0.47461 -0.4746 -0.47461l-6.48633 0c-1.04808 0 -1.89843 0.850344 -1.89843 1.898435l0 6.328127c0 1.048093 0.85035 1.898437 1.89843 1.898437l6.48633 0c0.26301 0 0.4746 -0.2116 0.4746 -0.474611l0 -0.316409c0 -0.148311 -0.0692 -0.282785 -0.176 -0.369792 -0.083 -0.304543 -0.083 -1.172685 0 -1.477229 0.10684 -0.08504 0.176 -0.219501 0.176 -0.36782zm-6.32812 -4.469236c0 -0.06529 0.0534 -0.118652 0.11866 -0.118652l4.19238 0c0.0653 0 0.11866 0.05343 0.11866 0.118652l0 0.39551c0 0.06529 -0.0534 0.118653 -0.11866 0.118653l-4.19238 0c-0.0653 0 -0.11866 -0.05343 -0.11866 -0.118653l0 -0.39551zm0 1.265621c0 -0.06529 0.0534 -0.118653 0.11866 -0.118653l4.19238 0c0.0653 0 0.11866 0.05343 0.11866 0.118653l0 0.395509c0 0.06529 -0.0534 0.118653 -0.11866 0.118653l-4.19238 0c-0.0653 0 -0.11866 -0.05342 -0.11866 -0.118653l0 -0.395509zm5.0111 4.943847l-5.64391 0c-0.35003 0 -0.63282 -0.282781 -0.63282 -0.632808 0 -0.348045 0.28476 -0.632813 0.63282 -0.632813l5.64391 0c-0.0375 0.338162 -0.0375 0.927466 0 1.265621z"
android:fillColor="#ffffff" />
</group>
</group>
</vector>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="24"
android:viewportHeight="24"
android:width="24dp"
android:height="24dp">
<group
android:translateY="-8">
<group
android:scaleX="1.777778"
android:scaleY="1.777778"
android:translateX="-205.4844"
android:translateY="-31.99788">
<group
android:scaleX="0.5625"
android:scaleY="0.5625"
android:translateX="115.585"
android:translateY="22.49881">
<path
android:pathData="M4.375 3C2.5117466 3 1 4.5117271 1 6.375l0 11.25C1 19.488276 2.5117466 21 4.375 21l11.53125 0C16.373823 21 16.75 20.623825 16.75 20.15625l0 -0.5625c0 -0.263664 -0.122669 -0.503524 -0.3125 -0.658203 -0.147556 -0.54141 -0.147556 -2.08359 0 -2.625 0.189938 -0.151182 0.3125 -0.390619 0.3125 -0.654297l0 -0.669922 -2.378906 2.378906c-0.0089 0.500068 -0.0036 1.014773 0.03711 1.384766l-1.525391 0 -3.0507808 0.667969C9.333649 19.527124 8.7483479 19.391267 8.3632812 19.005859 8.2870146 18.929486 8.2287106 18.840044 8.171875 18.75L4.375 18.75C3.7527244 18.75 3.25 18.24727 3.25 17.625 3.25 17.006253 3.7562267 16.5 4.375 16.5l3.8027344 0L8.6503906 14.349609 12.125 10.875l-6.4140625 0C5.5948486 10.875 5.5 10.780031 5.5 10.664062l0 -0.7031245C5.5 9.8448664 5.5949375 9.75 5.7109375 9.75l7.4531245 0c0.02365 0 0.03921 0.018137 0.06055 0.025391L16.550781 6.4492188 16.75 6.25l0 -2.40625C16.75 3.3761713 16.373823 3 15.90625 3L4.375 3Zm16.015625 1.5898438c-0.30546 0.033979 -0.623906 0.1844704 -0.882813 0.4433593l-1.183593 1.1835938 2.828125 2.828125 1.183594 -1.1835938c0.517848 -0.5180889 0.601704 -1.2752558 0.1875 -1.6894531L21.195312 4.8457031C20.988219 4.6386018 20.696085 4.5558644 20.390625 4.5898438ZM17.320312 7.21875L9.6464844 14.894531 9.015625 17.767578c-0.08448 0.384462 0.1995577 0.670133 0.5839844 0.585938L12.472656 17.724609 20.148438 10.046875 17.320312 7.21875ZM5.7109375 7.5L13.164062 7.5C13.280151 7.5 13.375 7.594987 13.375 7.7109375l0 0.703125C13.375 8.5301336 13.28008 8.625 13.164062 8.625l-7.4531245 0C5.5948486 8.625 5.5 8.5300127 5.5 8.4140625l0 -0.703125C5.5 7.5948664 5.5949375 7.5 5.7109375 7.5Z"
android:fillColor="#ffffff" />
</group>
</group>
</group>
</vector>

View File

@@ -32,6 +32,6 @@
android:layout_marginStart="24dp"
android:layout_marginEnd="18dp"
android:layout_toStartOf="@+id/fingerprint_image"
style="@style/KeepassDXStyle.TextAppearance.DefaultTextOnPrimary"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary"
android:gravity="center_vertical|start" />
</RelativeLayout>

View File

@@ -53,7 +53,7 @@
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:layout_gravity="start|center_vertical"
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary" />
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
</LinearLayout>
</android.support.v7.widget.Toolbar>

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePass DX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/read_only"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/read_only"
android:visibility="gone"
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="@dimen/list_margin"
android:layout_marginEnd="@dimen/list_margin">
<android.support.v7.widget.AppCompatImageView android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:scaleType="fitXY" />
<android.support.v7.widget.AppCompatTextView android:id="@+id/group_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="@string/root"
android:maxLines="1"
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary" />
</LinearLayout>
</LinearLayout>

View File

@@ -36,14 +36,32 @@
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:id="@+id/entry_text"
android:layout_height="wrap_content"
<LinearLayout
android:layout_width="wrap_content"
style="@style/KeepassDXStyle.TextAppearance.Default"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="2dp"
android:paddingBottom="4dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_toRightOf="@+id/entry_icon"
android:layout_toEndOf="@+id/entry_icon" />
android:layout_toEndOf="@+id/entry_icon">
<TextView
android:id="@+id/entry_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Default" /> <!-- style override -->
<TextView
android:id="@+id/entry_subtext"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_marginTop="-4dp"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Secondary" /> <!-- style override -->
</LinearLayout>
</RelativeLayout>

View File

@@ -1,6 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nodes_list"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground" />
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/not_found_container"
android:layout_gravity="center"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/not_found_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/img_not_found"/>
<TextView
android:id="@+id/not_found_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_results"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nodes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground" />
</FrameLayout>

View File

@@ -50,14 +50,29 @@
android:layout_toRightOf="@+id/group_arrow"
android:layout_toEndOf="@+id/group_arrow" />
<TextView android:id="@+id/group_text"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingTop="2dp"
android:paddingBottom="2dp"
style="@style/KeepassDXStyle.TextAppearance.FolderTitle"
android:orientation="vertical"
android:layout_centerVertical="true"
android:paddingTop="2dp"
android:paddingBottom="4dp"
android:layout_toRightOf="@+id/group_icon"
android:layout_toEndOf="@+id/group_icon" />
android:layout_toEndOf="@+id/group_icon">
<TextView android:id="@+id/group_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Title" /> <!-- style override -->
<TextView android:id="@+id/group_subtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginTop="-4dp"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Secondary" /> <!-- style override -->
</LinearLayout>
</RelativeLayout>

View File

@@ -56,13 +56,43 @@
app:popupTheme="?attr/toolbarPopupAppearance"
android:elevation="4dp"
tools:targetApi="lollipop">
<com.kunzisoft.keepass.view.GroupHeaderView
<LinearLayout
android:id="@+id/group_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/toolbar" />
android:layout_below="@+id/toolbar"
android:orientation="vertical">
<TextView android:id="@+id/search_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/search_results"
android:visibility="gone"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="@dimen/list_margin"
android:layout_marginEnd="@dimen/list_margin">
<android.support.v7.widget.AppCompatImageView android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:scaleType="fitXY" />
<android.support.v7.widget.AppCompatTextView android:id="@+id/group_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="@string/root"
android:maxLines="1"
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
@@ -70,10 +100,11 @@
<FrameLayout
android:id="@+id/nodes_list_fragment_container"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar" />
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_below="@+id/toolbar"
android:background="?android:attr/windowBackground" />
<com.kunzisoft.keepass.view.AddNodeButtonView
android:id="@+id/add_node_button"

View File

@@ -75,7 +75,7 @@
tools:targetApi="lollipop" >
<TextView
android:id="@+id/filename"
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary"
style="@style/KeepassDXStyle.TextAppearance.Title.TextOnPrimary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePass DX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/entry_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:minHeight="32dp"
android:background="?attr/colorPrimary" >
<android.support.v7.widget.AppCompatImageView android:id="@+id/entry_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin"
android:scaleType="fitXY"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:id="@+id/entry_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:lines="1"
android:singleLine="true"
style="@style/KeepassDXStyle.TextAppearance.Default.TextOnPrimary"
android:textStyle="bold"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/entry_icon"
android:layout_toEndOf="@+id/entry_icon" />
<TextView
android:id="@+id/entry_subtext"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:lines="1"
android:singleLine="true"
android:layout_marginLeft="6dp"
android:layout_marginStart="6dp"
style="@style/KeepassDXStyle.TextAppearance.Secondary.TextOnPrimary"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_toRightOf="@+id/entry_text"
android:layout_toEndOf="@+id/entry_text" />
</RelativeLayout>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePass DX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_open_file_read_mode_key"
android:icon="@drawable/ic_read_write_white_24dp"
android:title="@string/menu_open_file_read_and_write"
android:orderInCategory="85"
app:showAsAction="ifRoom" />
</menu>

View File

@@ -88,7 +88,7 @@
<string name="field_name">Feldname</string>
<string name="field_value">Feldwert</string>
<string name="file_not_found">Datei nicht gefunden.</string>
<string name="file_not_found_content">Datei nicht gefunden. Versuche es von ihrem Dienstanbieter erneut zu öffnen.</string>
<string name="file_not_found_content">Datei nicht gefunden. Versuchen Sie, es von Ihrem Dienstanbieter erneut zu öffnen.</string>
<string name="file_browser">Dateimanager</string>
<string name="generate_password">Passwort generieren</string>
<string name="hint_conf_pass">Passwort wiederholen</string>
@@ -129,7 +129,7 @@
<string name="never">Nie</string>
<string name="no_results">Keine Suchergebnisse</string>
<string name="no_url_handler">Kein Handler zum Öffnen der URL vorhanden.</string>
<string name="open_recent">Zuletzt geöffnete Datenbank :</string>
<string name="open_recent">Zuletzt geöffnete Datenbanken :</string>
<string name="omitbackup_title">Papierkorb/Sicherungen nicht durchsuchen</string>
<string name="omitbackup_summary">Papierkorb und Sicherungseinträge werden bei der Suche nicht berücksichtigt (nur bei .kdb Dateien)</string>
<string name="progress_create">Neue Datenbank anlegen\u2026</string>
@@ -162,7 +162,7 @@
<string name="use_saf_summary">Storage Access Framework als Dateimanager verwenden (Android KitKat und später)</string>
<string name="use_saf_title">Speicherzugriff-Framework</string>
<string name="warning">Warnung</string>
<string name="warning_password_encoding">Das .kdb Format unterstützt nur den Latin1 Zeichensatz. Ihr Passwort enthält andere Zeichen. Diese Zeichen werden umgewandelt. Dies reduziert die Sicherheit des Passwortes. Es wird empfohlen ihr Passwort zu ändern.</string>
<string name="warning_password_encoding">Das .kdb Format unterstützt nur den Latin1-Zeichensatz. Enthält Ihr Passwort Zeichen, die in diesem Zeichensatz nicht vorkommen, werden diese alle in in ein und dasselbe Zeichen umgewandelt. Dies reduziert die Sicherheit des Passwortes. Es wird empfohlen, dass Sie Ihr Passwort ändern.</string>
<string name="warning_read_only">Die SD-Karte ist schreibgeschützt. Etwaige Änderungen in den Datensätzen können daher nicht in der Datenbank gespeichert werden.</string>
<string name="warning_unmounted">Keine SD-Karte vorhanden oder derzeit nicht im Gerät eingebunden. Daher kann weder eine Datenbank geöffnet, noch erstellt werden.</string>
<string name="version_label">Version:</string>
@@ -186,13 +186,13 @@
<item>Groß</item>
</string-array>
<string name="warning_empty_password">Sind Sie sicher, dass sie ein leeres Passwort verwenden wollen ?</string>
<string name="warning_no_encryption_key">Sind Sie sicher, dass sie keinen Verschlüsselungsschlüssel verwenden wollen ?</string>
<string name="warning_empty_password">Sind Sie sicher, dass Sie ein leeres Passwort verwenden wollen ?</string>
<string name="warning_no_encryption_key">Sind Sie sicher, dass Sie keinen Verschlüsselungsschlüssel verwenden wollen ?</string>
<string name="appearance">Aussehen</string>
<string name="password_size_title">Passwortlänge</string>
<string name="password_size_summary">Standardlänge des generierten Passworts ändern</string>
<string name="clipboard_notifications_title">Zwischenablagenbenachrichtigungen</string>
<string name="clipboard_notifications_summary">Zwischenablagenbenachrichtigungen zum Kopieren von Benutzername und Passwort einschalten</string>
<string name="clipboard_notifications_summary">Zwischenablagenbenachrichtigungen einschalten um Eingabefelder zu aktivieren</string>
<string name="lock_database_screen_off_title">Bildschirmsperre</string>
<string name="lock_database_screen_off_summary">Datenbank sperren, wenn der Bildschirm ausgeschaltet wird</string>
<string name="create_keepass_file">Keepass Datei erstellen</string>
@@ -204,9 +204,9 @@
<string name="fingerprint_enable_summary">Datenbanköffnung mit Fingerabdruck einschalten</string>
<string name="fingerprint">Fingerabdruck</string>
<string name="fingerprint_enable_title">Fingerabdruckscanner</string>
<string name="fingerprint_scan_to_open">Fingerabdruck scannen während Passwortfeld leer ist, um Datenbank zu öffnen</string>
<string name="fingerprint_scan_to_open">Fingerabdruck scannen während das Passwortfeld leer ist, um Datenbank zu öffnen</string>
<string name="fingerprint_scan_to_store">Fingerabdruck scannen, um Masterpasswort zu speichern</string>
<string name="fingerprint_type_password_text">Geben Sie ihr Passwort in Keepass DX ein</string>
<string name="fingerprint_type_password_text">Geben Sie Ihr Passwort in Keepass DX ein</string>
<string name="lock">Sperre</string>
<string name="list_password_generator_options_summary">Standardzeichen für Passwortgenerator setzen</string>
<string name="list_password_generator_options_title">Passwortzeichen</string>
@@ -217,7 +217,7 @@
<string name="fingerprint_error">Probleme mit dem Fingerabdruck : %1$s</string>
<string name="history">Verlauf</string>
<string name="fingerprint_quick_unlock_title">Wie richte ich den Fingerabdruckscanner für schnelles Entsperren ein ?</string>
<string name="fingerprint_setting_text">Speichern sie ihren persönlichen Fingerabdruck in</string>
<string name="fingerprint_setting_text">Speichern Sie Ihren persönlichen Fingerabdruck in</string>
<string name="fingerprint_setting_way_text">Einstellungen -&gt; Sicherheit -&gt; Fingerabdruck</string>
<string name="usage">Verwendung</string>
<string name="general">Allgemein</string>
@@ -238,7 +238,7 @@
<string name="memory_usage">Speichernutzung</string>
<string name="memory_usage_explanation">Größe des Speichers (in binären Bytes) der für die Schlüsselableitung genutzt wird.</string>
<string name="parallelism">Parallelismus</string>
<string name="parallelism_explanation">Grad des Parallelismus (i.d.F. Anzahl der Threads) der für die Schlüsselableitung genutzt wird.</string>
<string name="parallelism_explanation">Grad des Parallelismus (d.h. Anzahl der Threads) der für die Schlüsselableitung genutzt wird.</string>
<string name="sort_menu">Sortieren</string>
<string name="sort_ascending">Aufsteigend</string>
<string name="sort_groups_before">Gruppen davor</string>
@@ -254,7 +254,7 @@
<string name="autofill_sign_in_prompt">Mit KeePass DX anmelden</string>
<string name="set_autofill_service_title">Standard Autofill-Dienst auswählen</string>
<string name="set_autofill_service_summary">Dienst aktivieren, um automatisch Eingabefelder anderer Anwendungen auszufüllen</string>
<string name="notifications">Benachrichtigungen</string>
<string name="clipboard">Zwischenablage</string>
<string name="fingerprint_delete_all_title">Verschlüsselungskeys löschen</string>
<string name="fingerprint_delete_all_summary">Alle Verschlüsselungsschlüssel für Fingerabdruckerkennung löschen</string>
<string name="fingerprint_delete_all_warning">Sind Sie sicher, dass Sie alle Schlüssel für die Fingerabdruckerkennung löschen möchten?</string>
@@ -266,7 +266,7 @@
<string name="recycle_bin_title">Papierkorb verwenden</string>
<string name="recycle_bin_summary">Gruppe oder Eintrag in den Papierkorb verschieben bevor er gelöscht wird</string>
<string name="permission_external_storage_rationale_write_database">KeePass DX benötigt Berechtigungen für Speicherzugriff um Datenbanken zu schreiben</string>
<string name="permission_external_storage_rationale_read_database">KeePass DX benötigt externe Speicherzugriffsberechtigung um eine URI zu lesen, die nicht vom Content-Provider zur Verfügung gestellt wird</string>
<string name="permission_external_storage_rationale_read_database">KeePass DX benötigt externe Speicherzugriffsberechtigung um eine URI zu lesen, die nicht von einem Content-Provider zur Verfügung gestellt wird</string>
<string name="permission_external_storage_denied">Speicherzugriff verweigert</string>
<string name="permission_external_storage_never_ask">Aktion kann ohne Speicherzugriff nicht ausgeführt werden</string>
<string name="monospace_font_fields_enable_title">Felder Schriftart</string>
@@ -274,7 +274,7 @@
<string name="auto_open_file_uri_title">Ausgewählte Datei automatisch öffnen</string>
<string name="auto_open_file_uri_summary">Automatisch eine Datei vom Auswahlbildschirm öffnen nach Auswahl im Dateibrowser</string>
<string name="allow_copy_password_title">Kopie des Passworts</string>
<string name="allow_copy_password_summary">Koperen des Passworts in die Zwischenablage erlauben.</string>
<string name="allow_copy_password_summary">Kopieren des Passworts und der geschützten Felder in die Zwischenablage erlauben.</string>
<string name="warning_disabling_storage_access_framework">WARNUNG: Deaktivierung dieses Features könnte das Öffnen und Speichern von Datenbanken unmöglich machen</string>
<string name="open_link_database">Link der zu öffnenden Kdbx-Datei</string>
<string name="database_name_title">Datenbankname</string>
@@ -287,9 +287,9 @@
<string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_summary">Aktiviert eine eigene Tastatur, die Passwort- und Identitätsfelder ganz einfach ausfüllt.</string>
<string name="reset_education_screens_title">Hilfe-Anzeige zurücksetzen</string>
<string name="reset_education_screens_summary">Elemente hervorheben, um zu lernen wie die Anwendung funktioniert</string>
<string name="reset_education_screens_text">Hilfe-Anzeige zurückgesetzt</string>
<string name="reset_education_screens_title">Hilfe-Anzeige abschalten</string>
<string name="reset_education_screens_summary">Anzeige von Hilfe-Themen abschalten</string>
<string name="reset_education_screens_text">Hilfe-Anzeige beendet</string>
<string name="education_create_database_title">Ihre Datenbankdatei erstellen</string>
<string name="education_create_database_summary">Sie kennen KeePass DX noch nicht, erstellen Sie Ihre erste Passwortmanager-Datei.</string>
<string name="education_select_database_title">Existierende Datenbank öffnen</string>
@@ -299,7 +299,7 @@
<string name="education_new_node_title">Neue Einträge zu Ihrer Datenbank hinzufügen</string>
<string name="education_new_node_summary">Elemente hinzufügen um Ihre digitalen Identitäten zu verwalten.\n\nGruppen hinzufügen (wie Ordner) um Ihre Einträge und Datenbank zu ordnen.</string>
<string name="education_search_title">Ihre Einträge ganz einfach durchsuchen</string>
<string name="education_search_summary">Einträge via Titel, Benutzernamen oder anderen Feldern finden um ganz einfach Ihre Passwörter zu bekommen.</string>
<string name="education_search_summary">Einträge nach Titel, Benutzernamen oder anderen Feldern durchsuchen, um ganz einfach Ihre Passwörter wiederzufinden.</string>
<string name="education_fingerprint_title">Ihre Datenbank mit Ihrem Fingerabdruck entsperren</string>
<string name="education_fingerprint_summary">Verknüpfen Sie Ihr Passwort und Ihren Fingerabdruck um Ihre Datenbank im Handumdrehen zu entsperren.</string>
<string name="education_entry_edit_title">Eintrag bearbeiten</string>
@@ -341,4 +341,39 @@
<string name="icon_pack_choose_title">Icon-Paket auswählen</string>
<string name="icon_pack_choose_summary">Das Symbolpaket der Anwendung ändern</string>
</resources>
<string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string>
<string name="menu_copy">Kopieren</string>
<string name="menu_move">Verschieben</string>
<string name="menu_paste">Einfügen</string>
<string name="menu_cancel">Abbrechen</string>
<string name="clipboard_warning">Einige Geräte sind nicht in der Lage, die Zwischenablage automatisch zu leeren. Ist die Löschung durch den Gerätemanager nicht möglich, müssen die kopierten Elemente manuell aus der Zwischenablage gelöscht werden.</string>
<string name="allow_copy_password_warning">WARNUNG : Alle Anwendungen teilen sich die Zwischenablage. Wenn sensible Daten kopiert werden, kann andere Software darauf zugreifen.</string>
<string name="magic_keyboard_preference_title">Magikeyboard-Einstellungen</string>
<string name="magic_keyboard_configure_title">Wie funktioniert die Tastaturkonfiguration zum sicheren Ausfüllen von Formularen?</string>
<string name="magic_keyboard_activate_setting_text">Aktivieren Sie das Magikeyboard in den Geräteeinstellungen.</string>
<string name="magic_keyboard_activate_setting_path_1_text">Einstellungen -&gt; Sprachen &amp; Eingabe -&gt; Aktuelle Tastatur -&gt; TASTATUR ÄNDERN</string>
<string name="magic_keyboard_activate_setting_path_2_text">oder (Einstellungen -&gt; System -&gt; Sprachen &amp; Eingabe -&gt; Bildschirmtastatur -&gt; Tastaturen verwalten)</string>
<string name="keyboards_choose_magikeyboard_text">Wählen Sie das Magikeyboard aus, wenn Sie ein Formular ausfüllen müssen.</string>
<string name="keyboards_swicth_magikeyboard_text">Sie können leicht von Ihrer Haupttastatur auf Magikeyboard umschalten, entweder mit der Sprachentaste Ihrer Tastatur, durch einen langen Druck auf die Leertaste oder, wenn das nicht zur Verfügung steht, mit :</string>
<string name="keyboard_select_entry_text">Wählen Sie den Eintrag mit dem Schlüssel aus.</string>
<string name="keyboard_fill_field_text">Füllen Sie die Felder mit den Elementen des Eintrags aus.</string>
<string name="keyboard_lock_database_text">Sperren Sie die Datenbank.</string>
<string name="keyboard_back_main_keyboard_text">Kehren Sie zur Haupttastatur zurück.</string>
<string name="allow_no_password_title">Kein Passwort zulassen</string>
<string name="allow_no_password_summary">Öffnen-Taste aktivieren, wenn keine Passwort-Identifikation festgelegt ist.</string>
<string name="enable_education_screens_title">Hilfe-Anzeige</string>
<string name="enable_education_screens_summary">Bedienelemente hervorheben, um die Funktionsweise der Anwendung zu lernen</string>
<string name="menu_open_file_read_and_write">Lesen und Schreiben</string>
<string name="menu_file_selection_read_only">schreibgeschützt</string>
<string name="enable_read_only_title">Schreibgeschützt</string>
<string name="education_read_only_title">Schreibschutz aktivieren</string>
<string name="enable_read_only_summary">Standardmäßig wird die Datenbank im schreibgeschützten Modus geöffnet.</string>
<string name="education_read_only_summary">Ändern Sie den Modus bei Eröffnung einer Sitzung.
\n
\nIm schreibgeschützten Modus verhindern Sie unbeabsichtigte Änderungen an der Datenbank.
\n
\nIm Modus Lesen und Schreiben können Sie alle Elemente hinzufügen, löschen oder verändern, wie Sie möchten.</string>
</resources>

View File

@@ -229,7 +229,7 @@ Spanish translation by José I. Paños. Updated by David García-Abad (23-09-201
<string name="password_size_summary">Establecer el tamaño predeterminado de la contraseña generada</string>
<string name="list_password_generator_options_title">Caracteres de contraseña</string>
<string name="list_password_generator_options_summary">Establecer los caracteres predeterminados del generador de contraseñas</string>
<string name="notifications">Notificaciones</string>
<string name="clipboard">Portapapeles</string>
<string name="clipboard_notifications_title">Notificaciones del Portapapeles</string>
<string name="clipboard_notifications_summary">Habilitar las notificaciones del portapapeles para copiar el nombre de usuario y la contraseña</string>
<string name="lock">Bloquear</string>

View File

@@ -206,7 +206,7 @@
<string name="password_size_summary">Définir la taille par défaut du mot de passe généré</string>
<string name="list_password_generator_options_title">Caractères de mot de passe</string>
<string name="list_password_generator_options_summary">Définir les caractères par défaut du générateur de mot de passe</string>
<string name="notifications">Notifications</string>
<string name="clipboard">Presse-papiers</string>
<string name="clipboard_notifications_title">Notifications du presse-papiers</string>
<string name="clipboard_notifications_summary">Activer les notifications du presse-papiers pour copier les champs d\'entrées</string>
<string name="clipboard_warning">Certains appareils ne sont pas en mesure de supprimer automatiquement les éléments du presse-papiers. Si votre gestionnaire n\'autorise pas cette suppression, vous devrez supprimer manuellement l\'élément copié de l\'historique de votre presse-papiers.</string>
@@ -350,4 +350,35 @@
<string name="icon_pack_choose_title">Choisir un pack d\'icones</string>
<string name="icon_pack_choose_summary">Changer le pack d\'icones de l\'application</string>
</resources>
<string name="error_move_folder_in_itself">Impossible de déplacer un groupe en lui-même.</string>
<string name="menu_copy">Copier</string>
<string name="menu_move">Déplacer</string>
<string name="menu_paste">Coller</string>
<string name="menu_cancel">Annuler</string>
<string name="magic_keyboard_preference_title">Paramètres du Magikeyboard</string>
<string name="magic_keyboard_configure_title">Comment configurer le clavier pour le remplissage sécurisé de formulaire ?</string>
<string name="magic_keyboard_activate_setting_text">Activer le Magikeyboard dans les paramètres de l\'appareil.</string>
<string name="magic_keyboard_activate_setting_path_1_text">Paramètres -&gt; Langue et saisie -&gt; Clavier actuel -&gt; CHOISIR LES CLAVIERS</string>
<string name="magic_keyboard_activate_setting_path_2_text">ou (Paramètres -&gt; Système -&gt; Langues et saisie -&gt; Clavier virtuel -&gt; Gérer les claviers)</string>
<string name="keyboards_choose_magikeyboard_text">Choisir le Magikeyboard lorsque vous avez besoin de remplir un formulaire.</string>
<string name="keyboards_swicth_magikeyboard_text">Vous pouvez facilement passer de votre clavier principal à Magikeyboard avec le bouton langage de votre clavier, un appui long sur la barre d\'espace de votre clavier, ou, si ce n\'est pas disponible, avec :</string>
<string name="keyboard_select_entry_text">Sélectionnez votre entrée avec la clé.</string>
<string name="keyboard_fill_field_text">Remplissez vos champs avec les éléments de l\'entrée.</string>
<string name="keyboard_lock_database_text">Verrouiller la base de données.</string>
<string name="keyboard_back_main_keyboard_text">Retourner sur votre clavier principal.</string>
<string name="allow_no_password_title">Autoriser aucun mot de passe</string>
<string name="allow_no_password_summary">Activer le bouton d\'ouverture si aucune identification de mot de passe n\'est sélectionnée.</string>
<string name="menu_file_selection_read_only">Lecture seule</string>
<string name="menu_open_file_read_and_write">Lecture et écriture</string>
<string name="enable_read_only_title">Lecture seule</string>
<string name="enable_read_only_summary">Par défaut, ouvrir une base de données en mode lecture seule.</string>
<string name="education_read_only_title">Activer la lecture seule</string>
<string name="education_read_only_summary">Changez le mode d\'ouverture de la session.
\n
\nEn mode lecture seule, vous empêchez les modifications involontaires de la base de données.
\n
\nEn mode écriture, vous pouvez ajouter, supprimer ou modifier tous les éléments comme vous le souhaitez.</string>
</resources>

View File

@@ -0,0 +1,26 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources><string name="about_feedback">Comentarios:</string>
<string name="about_homepage">Páxina inicial:</string>
<string name="about_description">KeePass DX é unha implementación para Android do xestor de contrasinais KeePass.</string>
<string name="accept">Aceptar</string>
<string name="add_entry">Engadir entrada</string>
<string name="add_group">Engadir grupo</string>
<string name="add_string">Engadir texto</string>
<string name="encryption">Cifrado</string>
<string name="encryption_algorithm">Algoritmo de cifrado</string>
<string name="key_derivation_function">Función de derivación de chave</string>
<string name="app_timeout">Tempo de espera da aplicación</string>
<string name="app_timeout_summary">Tempo antes de bloquear a base de datos cando a aplicación está inactiva.</string>
<string name="application">Aplicación</string>
<string name="beta_dontask">Non amosar de novo</string>
<string name="brackets">Parénteses</string>
<string name="extended_ASCII">ASCII extendido</string>
<string name="cancel">Cancelar</string>
<string name="allow">Permitir</string>
<string name="clipboard_cleared">Portapapeis limpo</string>
<string name="clipboard_error_title">Erro do portapapeis</string>
<string name="clipboard_error_clear">Fallou a limpeza do portapapeis</string>
<string name="clipboard_timeout">Tempo límite para o portapapeis</string>
<string name="clipboard_timeout_summary">Tempo antes de limpar o portapapeis após copiar usuario ou contrasinal</string>
<string name="clipboard_swipe_clean">Deslice para limpar agora o portapapeis</string>
</resources>

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