mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
77 Commits
2.5.0.0bet
...
2.5.0.0bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57f71afb98 | ||
|
|
d8ec198f6f | ||
|
|
34deea5d32 | ||
|
|
edc7324c1d | ||
|
|
b9190e8254 | ||
|
|
534878ae99 | ||
|
|
99bfd21ecc | ||
|
|
cb5c324c9d | ||
|
|
55dc504f26 | ||
|
|
ecb0138c90 | ||
|
|
0860eeb87f | ||
|
|
f08bff61cf | ||
|
|
432aca6465 | ||
|
|
7cdb8db146 | ||
|
|
6ba9dedcb8 | ||
|
|
6d603608f4 | ||
|
|
64f4d9fb84 | ||
|
|
60ccc450ae | ||
|
|
ddb5f327a3 | ||
|
|
df90ea42eb | ||
|
|
a062d648b3 | ||
|
|
a59ae820b5 | ||
|
|
228831acdd | ||
|
|
bf15ee43da | ||
|
|
608f45677c | ||
|
|
1cb15f214b | ||
|
|
cd4a9e9b03 | ||
|
|
d6eae56d4f | ||
|
|
b5a87a63dc | ||
|
|
1eb17c4f34 | ||
|
|
8a6ce1f711 | ||
|
|
0f22f8af45 | ||
|
|
b2e81e6fd9 | ||
|
|
f82eab942d | ||
|
|
aa29aec40f | ||
|
|
181def52ab | ||
|
|
96d2bd63cc | ||
|
|
194021a957 | ||
|
|
9e307f94ea | ||
|
|
155b2de138 | ||
|
|
c76c3fd2be | ||
|
|
01c5554944 | ||
|
|
542cf65b41 | ||
|
|
f037561a67 | ||
|
|
379263a6d3 | ||
|
|
c420ca01f6 | ||
|
|
a1f9db6eee | ||
|
|
c9212174c4 | ||
|
|
0065336377 | ||
|
|
4f9625a3e1 | ||
|
|
7688ebd29b | ||
|
|
3f2a7f1eb3 | ||
|
|
b3d067d0c8 | ||
|
|
a3b4ad5ac1 | ||
|
|
e3b329d27f | ||
|
|
7d10c43822 | ||
|
|
fcb0d45d39 | ||
|
|
5492db0223 | ||
|
|
2207b05f5f | ||
|
|
f15a0c2591 | ||
|
|
92fb22129c | ||
|
|
ccca9c4400 | ||
|
|
0597cb4416 | ||
|
|
0602174e50 | ||
|
|
6fddc92ce7 | ||
|
|
aa30df6454 | ||
|
|
f6c61ab407 | ||
|
|
2ab81ed77c | ||
|
|
0ade035f43 | ||
|
|
73b62035d8 | ||
|
|
28837db308 | ||
|
|
85befef260 | ||
|
|
e7bbb47422 | ||
|
|
846bc7edb1 | ||
|
|
99a9842a1f | ||
|
|
33009138c3 | ||
|
|
c74b82ebd8 |
24
CHANGELOG
24
CHANGELOG
@@ -1,3 +1,27 @@
|
||||
KeepassDX (2.5.0.0beta16)
|
||||
* Fix font and search
|
||||
|
||||
KeepassDX (2.5.0.0beta16)
|
||||
* New search in a single fragment
|
||||
* Search suggestions
|
||||
* Added the display of usernames
|
||||
* Added translations
|
||||
* Fix read-only mode
|
||||
* Fix parcelable / toolbar / back
|
||||
|
||||
KeepassDX (2.5.0.0beta15)
|
||||
* Read only mode
|
||||
* Best group recovery for the navigation fragment
|
||||
* Fix copies in notifications
|
||||
* Fix orientation
|
||||
* Added translations
|
||||
|
||||
KeepassDX (2.5.0.0beta14)
|
||||
* Optimize all the memory with parcelables / fix search
|
||||
|
||||
KeepassDX (2.5.0.0beta13)
|
||||
* Fix memory issue with parcelable (crash in beta12 version)
|
||||
|
||||
KeepassDX (2.5.0.0beta12)
|
||||
* Added the Magikeyboard to fill the forms (settings still in development)
|
||||
* Added move and copy for groups and entries
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 27
|
||||
versionCode = 12
|
||||
versionName = "2.5.0.0beta12"
|
||||
versionCode = 17
|
||||
versionName = "2.5.0.0beta17"
|
||||
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
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,216 @@ 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) {
|
||||
if (mCurrentGroup != null)
|
||||
outState.putParcelable(GROUP_ID_KEY, mCurrentGroup.getId());
|
||||
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, oldGroupToUpdate);
|
||||
if (nodeToCopy != null)
|
||||
outState.putParcelable(NODE_TO_COPY_KEY, nodeToCopy);
|
||||
if (nodeToMove != null)
|
||||
outState.putParcelable(NODE_TO_MOVE_KEY, nodeToMove);
|
||||
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
// Assign the group icon depending of IconPack or custom icon
|
||||
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 +487,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 +542,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 +703,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 +720,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 +858,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 +872,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 +908,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 +941,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
return true;
|
||||
|
||||
case R.id.menu_search:
|
||||
onSearchRequested();
|
||||
//onSearchRequested();
|
||||
return true;
|
||||
|
||||
case R.id.menu_lock:
|
||||
@@ -703,8 +951,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 +979,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 +1001,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 +1030,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
class AfterAddNode extends AfterActionNodeOnFinish {
|
||||
|
||||
@Override
|
||||
public void run(PwNode oldNode, PwNode newNode) {
|
||||
super.run();
|
||||
|
||||
@@ -794,6 +1049,7 @@ public class GroupActivity extends ListNodesActivity
|
||||
|
||||
class AfterUpdateNode extends AfterActionNodeOnFinish {
|
||||
|
||||
@Override
|
||||
public void run(PwNode oldNode, PwNode newNode) {
|
||||
super.run();
|
||||
|
||||
@@ -879,14 +1135,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kunzisoft.keepass.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
public interface IntentBuildLauncher {
|
||||
void startActivityForResult(Intent intent);
|
||||
}
|
||||
@@ -1,290 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePass DX.
|
||||
*
|
||||
* KeePass DX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePass DX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.activities;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.assist.AssistStructure;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.adapters.NodeAdapter;
|
||||
import com.kunzisoft.keepass.app.App;
|
||||
import com.kunzisoft.keepass.autofill.AutofillHelper;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwGroup;
|
||||
import com.kunzisoft.keepass.database.PwNode;
|
||||
import com.kunzisoft.keepass.database.SortNodeEnum;
|
||||
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
|
||||
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
|
||||
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
|
||||
import com.kunzisoft.keepass.lock.LockingActivity;
|
||||
import com.kunzisoft.keepass.password.AssignPasswordHelper;
|
||||
import com.kunzisoft.keepass.utils.MenuUtil;
|
||||
|
||||
public abstract class ListNodesActivity extends LockingActivity
|
||||
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
|
||||
NodeAdapter.NodeClickCallback,
|
||||
SortDialogFragment.SortSelectionListener {
|
||||
|
||||
protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
|
||||
|
||||
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
|
||||
protected ListNodesFragment listNodesFragment;
|
||||
|
||||
protected PwGroup mCurrentGroup;
|
||||
protected TextView groupNameView;
|
||||
|
||||
protected boolean entrySelectionMode;
|
||||
protected AutofillHelper autofillHelper;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if ( isFinishing() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Likely the app has been killed exit the activity
|
||||
if ( ! App.getDB().getLoaded() ) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
invalidateOptionsMenu();
|
||||
|
||||
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
|
||||
|
||||
initializeListNodesFragment(mCurrentGroup);
|
||||
|
||||
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
autofillHelper = new AutofillHelper();
|
||||
autofillHelper.retrieveAssistStructure(getIntent());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Refresh the title
|
||||
assignToolbarElements();
|
||||
}
|
||||
|
||||
protected void initializeListNodesFragment(PwGroup currentGroup) {
|
||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
||||
if (listNodesFragment == null)
|
||||
listNodesFragment = ListNodesFragment.newInstance(currentGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the fragment's list of node.
|
||||
* <br />
|
||||
* <strong>R.id.nodes_list_fragment_container</strong> must be the id of the container
|
||||
*/
|
||||
protected void attachFragmentToContentView() {
|
||||
getSupportFragmentManager().beginTransaction().replace(
|
||||
R.id.nodes_list_fragment_container,
|
||||
listNodesFragment,
|
||||
LIST_NODES_FRAGMENT_TAG)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public void assignToolbarElements() {
|
||||
if (mCurrentGroup != null) {
|
||||
String title = mCurrentGroup.getName();
|
||||
if (title != null && title.length() > 0) {
|
||||
if (groupNameView != null) {
|
||||
groupNameView.setText(title);
|
||||
groupNameView.invalidate();
|
||||
}
|
||||
} else {
|
||||
if (groupNameView != null) {
|
||||
groupNameView.setText(getText(R.string.root));
|
||||
groupNameView.invalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
MenuUtil.contributionMenuInflater(inflater, menu);
|
||||
inflater.inflate(R.menu.default_menu, menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch ( item.getItemId() ) {
|
||||
default:
|
||||
// Check the time lock before launching settings
|
||||
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNodeClick(PwNode node) {
|
||||
|
||||
// Add event when we have Autofill
|
||||
AssistStructure assistStructure = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
assistStructure = autofillHelper.getAssistStructure();
|
||||
if (assistStructure != null) {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
// Build response with the entry selected
|
||||
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( assistStructure == null ){
|
||||
if (entrySelectionMode) {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (node.getType()) {
|
||||
case GROUP:
|
||||
openGroup((PwGroup) node);
|
||||
break;
|
||||
case ENTRY:
|
||||
EntryActivity.launch(this, (PwEntry) node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void openGroup(PwGroup group) {
|
||||
// Check Timeout
|
||||
if (checkTimeIsAllowedOrFinish(this)) {
|
||||
startRecordTime(this);
|
||||
|
||||
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group.getId());
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
|
||||
R.anim.slide_in_left, R.anim.slide_out_right)
|
||||
.replace(R.id.nodes_list_fragment_container,
|
||||
newListNodeFragment,
|
||||
LIST_NODES_FRAGMENT_TAG)
|
||||
.addToBackStack(LIST_NODES_FRAGMENT_TAG)
|
||||
.commit();
|
||||
listNodesFragment = newListNodeFragment;
|
||||
mCurrentGroup = group;
|
||||
assignToolbarElements();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogPositiveClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
AssignPasswordHelper assignPasswordHelper =
|
||||
new AssignPasswordHelper(this,
|
||||
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
|
||||
assignPasswordHelper.assignPasswordInDatabase(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAssignKeyDialogNegativeClick(
|
||||
boolean masterPasswordChecked, String masterPassword,
|
||||
boolean keyFileChecked, Uri keyFile) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
|
||||
if (listNodesFragment != null)
|
||||
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@Override
|
||||
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
|
||||
/*
|
||||
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
|
||||
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
|
||||
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
|
||||
*/
|
||||
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
|
||||
int flags = intent.getFlags();
|
||||
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
intent.setFlags(flags);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
super.startActivityForResult(intent, requestCode, options);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (checkTimeIsAllowedOrFinish(this)) {
|
||||
startRecordTime(this);
|
||||
|
||||
super.onBackPressed();
|
||||
|
||||
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
|
||||
// to refresh fragment
|
||||
listNodesFragment.rebuildList();
|
||||
mCurrentGroup = listNodesFragment.getMainGroup();
|
||||
assignToolbarElements();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,8 @@ import android.view.ViewGroup;
|
||||
import com.kunzisoft.keepass.R;
|
||||
import com.kunzisoft.keepass.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,21 @@ 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);
|
||||
if (currentGroup != null)
|
||||
mAdapter.rebuildList(currentGroup);
|
||||
assignListToNodeAdapter(listView);
|
||||
}
|
||||
|
||||
@@ -197,7 +196,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 +248,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 +281,7 @@ public class ListNodesFragment extends StylishFragment implements
|
||||
}
|
||||
|
||||
public PwGroup getMainGroup() {
|
||||
return mCurrentGroup;
|
||||
return currentGroup;
|
||||
}
|
||||
|
||||
public interface OnScrollListener {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.kunzisoft.keepass.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil;
|
||||
|
||||
public class ReadOnlyHelper {
|
||||
|
||||
public static final String READ_ONLY_KEY = "READ_ONLY_KEY";
|
||||
|
||||
public static final boolean READ_ONLY_DEFAULT = false;
|
||||
|
||||
public static boolean retrieveReadOnlyFromInstanceStateOrPreference(Context context, Bundle savedInstanceState) {
|
||||
boolean readOnly;
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
|
||||
} else {
|
||||
readOnly = PreferencesUtil.enableReadOnlyDatabase(context);
|
||||
}
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
public static boolean retrieveReadOnlyFromInstanceStateOrArguments(Bundle savedInstanceState, Bundle arguments) {
|
||||
boolean readOnly = READ_ONLY_DEFAULT;
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
|
||||
} else if (arguments != null
|
||||
&& arguments.containsKey(READ_ONLY_KEY)) {
|
||||
readOnly = arguments.getBoolean(READ_ONLY_KEY);
|
||||
}
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
public static boolean retrieveReadOnlyFromInstanceStateOrIntent(Bundle savedInstanceState, Intent intent) {
|
||||
boolean readOnly = READ_ONLY_DEFAULT;
|
||||
if (savedInstanceState != null
|
||||
&& savedInstanceState.containsKey(READ_ONLY_KEY)) {
|
||||
readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
|
||||
} else {
|
||||
if (intent != null)
|
||||
readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT);
|
||||
}
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
public static void putReadOnlyInIntent(Intent intent, boolean readOnly) {
|
||||
intent.putExtra(READ_ONLY_KEY, readOnly);
|
||||
}
|
||||
|
||||
public static void putReadOnlyInBundle(Bundle bundle, boolean readOnly) {
|
||||
bundle.putBoolean(READ_ONLY_KEY, readOnly);
|
||||
}
|
||||
|
||||
public static void onSaveInstanceState(Bundle outState, boolean readOnly) {
|
||||
outState.putBoolean(READ_ONLY_KEY, readOnly);
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ abstract class BasicViewHolder extends RecyclerView.ViewHolder {
|
||||
View container;
|
||||
ImageView icon;
|
||||
TextView text;
|
||||
TextView subText;
|
||||
|
||||
BasicViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ 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.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -33,16 +34,20 @@ 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 +55,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 +83,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 +100,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 +114,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().getInteger(R.integer.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 +146,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 +224,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 +332,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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,54 @@ 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();
|
||||
if (searchResult != null) {
|
||||
for (int i = 0; i < searchResult.numbersOfChildEntries(); i++) {
|
||||
PwEntry entry = searchResult.getChildEntryAt(i);
|
||||
if (!entry.isMetaStream()) { // TODO metastream
|
||||
try {
|
||||
switch (version) {
|
||||
case V3:
|
||||
cursor.addEntry((PwEntryV3) entry);
|
||||
continue;
|
||||
case V4:
|
||||
cursor.addEntry((PwEntryV4) entry);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Can't add PwEntry to the cursor", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public void populateEntry(PwEntry pwEntry, EntryCursor cursor) {
|
||||
PwIconFactory iconFactory = getPwDatabase().getIconFactory();
|
||||
try {
|
||||
switch (getPwDatabase().getVersion()) {
|
||||
case V3:
|
||||
cursor.populateEntry((PwEntryV3) pwEntry, iconFactory);
|
||||
break;
|
||||
case V4:
|
||||
// TODO invert field reference manager
|
||||
pwEntry.startToManageFieldReferences(getPwDatabase());
|
||||
cursor.populateEntry((PwEntryV4) pwEntry, iconFactory);
|
||||
pwEntry.stopToManageFieldReferences();
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "This version of PwGroup can't be populated", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveData(Context ctx) throws IOException, PwDbOutputException {
|
||||
saveData(ctx, mUri);
|
||||
}
|
||||
@@ -464,19 +518,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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -109,8 +109,8 @@ public enum SortNodeEnum {
|
||||
new EntryNameComparator(ascending),
|
||||
object1,
|
||||
object2,
|
||||
object1.getDisplayTitle()
|
||||
.compareToIgnoreCase(object2.getDisplayTitle()));
|
||||
object1.getTitle()
|
||||
.compareToIgnoreCase(object2.getTitle()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.kunzisoft.keepass.database.cursor;
|
||||
|
||||
import android.database.MatrixCursor;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwDatabase;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.PwEntryV3;
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.PwIconCustom;
|
||||
import com.kunzisoft.keepass.database.PwIconFactory;
|
||||
import com.kunzisoft.keepass.database.PwIconStandard;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class EntryCursor extends MatrixCursor {
|
||||
|
||||
private long entryId;
|
||||
public static final String _ID = BaseColumns._ID;
|
||||
public static final String COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUID_most_significant_bits";
|
||||
public static final String COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUID_least_significant_bits";
|
||||
public static final String COLUMN_INDEX_TITLE = "title";
|
||||
public static final String COLUMN_INDEX_ICON_STANDARD = "icon_standard";
|
||||
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS = "icon_custom_UUID_most_significant_bits";
|
||||
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS = "icon_custom_UUID_least_significant_bits";
|
||||
public static final String COLUMN_INDEX_USERNAME = "username";
|
||||
public static final String COLUMN_INDEX_PASSWORD = "password";
|
||||
public static final String COLUMN_INDEX_URL = "URL";
|
||||
public static final String COLUMN_INDEX_NOTES = "notes";
|
||||
|
||||
private ExtraFieldCursor extraFieldCursor;
|
||||
|
||||
public EntryCursor() {
|
||||
super(new String[]{ _ID,
|
||||
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
|
||||
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
|
||||
COLUMN_INDEX_TITLE,
|
||||
COLUMN_INDEX_ICON_STANDARD,
|
||||
COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS,
|
||||
COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS,
|
||||
COLUMN_INDEX_USERNAME,
|
||||
COLUMN_INDEX_PASSWORD,
|
||||
COLUMN_INDEX_URL,
|
||||
COLUMN_INDEX_NOTES});
|
||||
entryId = 0;
|
||||
extraFieldCursor = new ExtraFieldCursor();
|
||||
}
|
||||
|
||||
public void addEntry(PwEntryV3 entry) {
|
||||
addRow(new Object[] {entryId,
|
||||
entry.getUUID().getMostSignificantBits(),
|
||||
entry.getUUID().getLeastSignificantBits(),
|
||||
entry.getTitle(),
|
||||
entry.getIconStandard().getIconId(),
|
||||
PwDatabase.UUID_ZERO.getMostSignificantBits(),
|
||||
PwDatabase.UUID_ZERO.getLeastSignificantBits(),
|
||||
entry.getUsername(),
|
||||
entry.getPassword(),
|
||||
entry.getUrl(),
|
||||
entry.getNotes()});
|
||||
entryId++;
|
||||
}
|
||||
|
||||
public void addEntry(PwEntryV4 entry) {
|
||||
addRow(new Object[] {entryId,
|
||||
entry.getUUID().getMostSignificantBits(),
|
||||
entry.getUUID().getLeastSignificantBits(),
|
||||
entry.getTitle(),
|
||||
entry.getIconStandard().getIconId(),
|
||||
entry.getIconCustom().getUUID().getMostSignificantBits(),
|
||||
entry.getIconCustom().getUUID().getLeastSignificantBits(),
|
||||
entry.getUsername(),
|
||||
entry.getPassword(),
|
||||
entry.getUrl(),
|
||||
entry.getNotes()});
|
||||
|
||||
entry.getFields().doActionToAllCustomProtectedField((key, value) -> {
|
||||
extraFieldCursor.addExtraField(entryId, key, value);
|
||||
});
|
||||
|
||||
entryId++;
|
||||
}
|
||||
|
||||
private void populateEntryBaseVersion(PwEntry pwEntry, PwIconFactory iconFactory) {
|
||||
pwEntry.setUUID(
|
||||
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||
pwEntry.setTitle(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE)));
|
||||
|
||||
PwIconStandard iconStandard = iconFactory.getIcon(getInt(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
|
||||
pwEntry.setIconStandard(iconStandard);
|
||||
|
||||
pwEntry.setUsername(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME)));
|
||||
pwEntry.setPassword(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_PASSWORD)));
|
||||
pwEntry.setUrl(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_URL)));
|
||||
pwEntry.setNotes(getString(getColumnIndex(EntryCursor.COLUMN_INDEX_NOTES)));
|
||||
}
|
||||
|
||||
public void populateEntry(PwEntryV3 pwEntry, PwIconFactory iconFactory) {
|
||||
populateEntryBaseVersion(pwEntry, iconFactory);
|
||||
}
|
||||
|
||||
public void populateEntry(PwEntryV4 pwEntry, PwIconFactory iconFactory) {
|
||||
populateEntryBaseVersion(pwEntry, iconFactory);
|
||||
|
||||
// Retrieve custom icon
|
||||
PwIconCustom iconCustom = iconFactory.getIcon(
|
||||
new UUID(getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
|
||||
getLong(getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
|
||||
pwEntry.setIconCustom(iconCustom);
|
||||
|
||||
// Retrieve extra fields
|
||||
if (extraFieldCursor.moveToFirst()) {
|
||||
while (!extraFieldCursor.isAfterLast()) {
|
||||
// Add a new extra field only if entryId is the one we want
|
||||
if (extraFieldCursor.getLong(extraFieldCursor.getColumnIndex(ExtraFieldCursor.FOREIGN_KEY_ENTRY_ID))
|
||||
== getLong(getColumnIndex(EntryCursor._ID))) {
|
||||
extraFieldCursor.populateExtraFieldInEntry(pwEntry);
|
||||
}
|
||||
extraFieldCursor.moveToNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.kunzisoft.keepass.database.cursor;
|
||||
|
||||
import android.database.MatrixCursor;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.kunzisoft.keepass.database.PwEntryV4;
|
||||
import com.kunzisoft.keepass.database.security.ProtectedString;
|
||||
|
||||
public class ExtraFieldCursor extends MatrixCursor {
|
||||
|
||||
private long fieldId;
|
||||
public static final String _ID = BaseColumns._ID;
|
||||
public static final String FOREIGN_KEY_ENTRY_ID = "entry_id";
|
||||
public static final String COLUMN_LABEL = "label";
|
||||
public static final String COLUMN_PROTECTION = "protection";
|
||||
public static final String COLUMN_VALUE = "value";
|
||||
|
||||
public ExtraFieldCursor() {
|
||||
super(new String[]{ _ID,
|
||||
FOREIGN_KEY_ENTRY_ID,
|
||||
COLUMN_LABEL,
|
||||
COLUMN_PROTECTION,
|
||||
COLUMN_VALUE});
|
||||
fieldId = 0;
|
||||
}
|
||||
|
||||
public synchronized void addExtraField(long entryId, String label, ProtectedString value) {
|
||||
addRow(new Object[] {fieldId,
|
||||
entryId,
|
||||
label,
|
||||
(value.isProtected()) ? 1 : 0,
|
||||
value.toString()});
|
||||
fieldId++;
|
||||
}
|
||||
|
||||
public void populateExtraFieldInEntry(PwEntryV4 pwEntry) {
|
||||
|
||||
pwEntry.addExtraField(getString(getColumnIndex(ExtraFieldCursor.COLUMN_LABEL)),
|
||||
new ProtectedString((getInt(getColumnIndex(ExtraFieldCursor.COLUMN_PROTECTION)) > 0),
|
||||
getString(getColumnIndex(ExtraFieldCursor.COLUMN_VALUE))));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.database.iterator;
|
||||
import com.kunzisoft.keepass.database.PwEntry;
|
||||
import com.kunzisoft.keepass.database.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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) ) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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()) {
|
||||
@@ -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
|
||||
@@ -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();
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,22 @@ 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 Float.parseFloat(listSize);
|
||||
}
|
||||
|
||||
public static int getDefaultPasswordLength(Context ctx) {
|
||||
@@ -139,12 +153,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 +186,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 +200,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 +280,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.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,10 @@ public class Util {
|
||||
applyFontVisibilityTo(context, (TextView) editText);
|
||||
}
|
||||
|
||||
public static float getListTextDefaultSize(Context context) {
|
||||
return Float.parseFloat(context.getString(R.string.list_size_default));
|
||||
}
|
||||
|
||||
public static void lockScreenOrientation(Activity activity) {
|
||||
if (activity != null) {
|
||||
int currentOrientation = activity.getResources().getConfiguration().orientation;
|
||||
@@ -86,7 +93,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
24
app/src/main/res/anim/slide_in_bottom.xml
Normal file
24
app/src/main/res/anim/slide_in_bottom.xml
Normal 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>
|
||||
24
app/src/main/res/anim/slide_in_top.xml
Normal file
24
app/src/main/res/anim/slide_in_top.xml
Normal 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>
|
||||
24
app/src/main/res/anim/slide_out_bottom.xml
Normal file
24
app/src/main/res/anim/slide_out_bottom.xml
Normal 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>
|
||||
24
app/src/main/res/anim/slide_out_top.xml
Normal file
24
app/src/main/res/anim/slide_out_top.xml
Normal 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>
|
||||
19
app/src/main/res/drawable/ic_read_only_white_24dp.xml
Normal file
19
app/src/main/res/drawable/ic_read_only_white_24dp.xml
Normal 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>
|
||||
25
app/src/main/res/drawable/ic_read_write_white_24dp.xml
Normal file
25
app/src/main/res/drawable/ic_read_write_white_24dp.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
65
app/src/main/res/layout/search_entry.xml
Normal file
65
app/src/main/res/layout/search_entry.xml
Normal 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>
|
||||
27
app/src/main/res/menu/open_file.xml
Normal file
27
app/src/main/res/menu/open_file.xml
Normal 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>
|
||||
@@ -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 -> Sicherheit -> 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 -> Sprachen & Eingabe -> Aktuelle Tastatur -> TASTATUR ÄNDERN</string>
|
||||
<string name="magic_keyboard_activate_setting_path_2_text">oder (Einstellungen -> System -> Sprachen & Eingabe -> Bildschirmtastatur -> 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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -> Langue et saisie -> Clavier actuel -> CHOISIR LES CLAVIERS</string>
|
||||
<string name="magic_keyboard_activate_setting_path_2_text">ou (Paramètres -> Système -> Langues et saisie -> Clavier virtuel -> 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>
|
||||
|
||||
26
app/src/main/res/values-gl/strings.xml
Normal file
26
app/src/main/res/values-gl/strings.xml
Normal 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
Reference in New Issue
Block a user